简介
随着云原生技术的大规模应用,可观察性也越来越重要。对于运维来说,一个比较麻烦的事情就是在有需要的时候,如何快速对某个 K8S 服务或者容器进行抓包。Kubeshark 由 2021 年 UP9 公司开源的 K8S API 流量查看器 Mizu 发展而来,试图成为一款 K8S 全过程流量监控工具,解决 K8S 上 API 流程可观察性的问题。Kubeshark 通过 libpcap、AF_PACKET、PF_RING 和 eBPF 等抓取 K8S 内部网络数据包,保存成 PCAP 格式,然后按照对应的应用协议进行解析,直观呈现给用户,相当于 K8S 版本的 TCPDump 和 Wireshark。目前支持的协议有:
- HTTP/1.0
- HTTP/1.1
- HTTP/2
- AMQP
- Apache Kafka
- Redis
- DNS
此外,Kubeshark 也会解析 gRPC 和 GraphQL 协议的数据。
整体架构
Kubeshark 由 cli、font-end、hub、worker 四部分组成:
- cli:命令行客户端,Go 语言编写的,通过和 K8S 集群交互部署 font-end、hub、worker。
- font-end:一个 React 前端应用,通过 WebSocket 和 hub 通讯,以流的方式展示抓取到的数据包。按照应用协议,而不是单个单个网络包的方式展示的
- hub:作为 worker 的一个聚合网关,通过 HTTP 对外提供以下功能
- 接收 WebSocket 请求和用户设置的过滤器
- 和 worker 建立 WebSocket 连接
- 从 worker 获取解析之后的流量数据
- 将流量数据发给请求者
- 通过 HTTP 配置 worker
- worker:Kubeshark 以 DaemonSet 方式部署 worker,确保覆盖到集群每个节点。worker 会抓取机器上所有网卡的网络包,组装成 TCP 流,并且尝试解析这些流量,解析成功就保存成 PCAP 文件。worker 保存的是原始流量数据,当用户请求的时候才按照应用层协议进行解析。woker 具有分布式特点,网络包的保存和解析都是分散到集群各个节点上的。并且为了降低额外的网络影响,只有在需要的时候,worker 才会发送相关数据。
底层实现
Kubeshark 使用 Linux 内核中的各种内置方法和 API,抓取集群中加密和未加密的流量。对网络数据的收集主要有直接抓包法和基于 eBPF 的数据包获取。直接抓包法涉及 libpcap、AF_PACKET 和 PF_RING 获取集群 TCP 流量。eBPF 主要用于抓取加密流量 (TLS),通过给 OpenSSL 库和 Go 的 crypto/tls 库的某些函数的入口点和出口点添加 hook 方式实现,而不是进行数据的加解密,抓取 TLS 加密流量需要加上 --tls
选项(kubeshark tap --tls -n sock-shop
)。
不足之处
- 从实验来看,只有可以被解析的流量才会被抓取,也就是无法抓取连接失败和非 Kubeshark 支持的协议流量,无法帮助分析一些连接上的问题。
- 目前在添加和清理过滤器的时候,经常会报 Hub is down。
实验
实验一:监听简单的 HTTP 服务
- 安装 Kubeshark
1
2
3
4
5
6# 方法一:自动根据系统和 CPU 架构安装对应的版本
sh <(curl -Ls https://kubeshark.co/install)
# 方法二:在 Mac 系统上可以使用 brew 工具直接安装
brew tap kubeshark/kubeshark
brew install kubeshark - 使用 kind 创建 demo 使用的 K8S 集群
1
kind create cluster --name demo --config kind-cluster.yaml
1
2
3
4
5
6
7
8# kind-cluster.yaml
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker - 部署 nginx 和 netshoot 作为测试服务(nginx 作为服务端,在 netshoot 上进行调用测试)
1
2
3
4
5
6
7
8# 设置使用 demo 集群
kubectl cluster-info --context kind-demo
# 部署 nginx deplyment 和 service
kubectl apply -f nginx.yml
# 部署 netshoot deployment
kubectl apply -f netshoot.yml
# 检查启动成功
kubectl get pods -o wide1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32# nginx.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 801
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# netshoot.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: netshoot
spec:
selector:
matchLabels:
app: netshoot
replicas: 1
template:
metadata:
labels:
app: netshoot
spec:
containers:
- name: netshoot
image: nicolaka/netshoot:latest
command: ["sleep"]
args: ["1000000"] - 运行 Kubeshark,通过 http://localhost:8898 可以直接打开 UI 界面。默认情况下只抓 default 命名空间的 pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23⋊> ~/2/c/k/demo kubeshark tap 10:10:18
2023-02-02T10:22:26+08:00 INF tapRunner.go:45 > Using Docker: registry=docker.io/kubeshark/ tag=latest
2023-02-02T10:22:26+08:00 INF versionCheck.go:23 > Checking for a newer version...
2023-02-02T10:22:26+08:00 INF tapRunner.go:53 > Kubeshark will store the traffic up to a limit (per node). Oldest TCP streams will be removed once the limit is reached. limit=200MB
2023-02-02T10:22:26+08:00 INF common.go:69 > Using kubeconfig: path=/Users/longhua/.kube/config
2023-02-02T10:22:26+08:00 INF tapRunner.go:74 > Targeting pods in: namespaces=["default"]
2023-02-02T10:22:26+08:00 INF tapRunner.go:129 > Targeted pod: netshoot-694f4cdf-s86lg
2023-02-02T10:22:26+08:00 INF tapRunner.go:129 > Targeted pod: nginx-7fb96c846b-6mcbv
2023-02-02T10:22:26+08:00 INF tapRunner.go:129 > Targeted pod: nginx-7fb96c846b-f9df9
2023-02-02T10:22:26+08:00 INF tapRunner.go:84 > Waiting for the creation of Kubeshark resources...
2023-02-02T10:22:26+08:00 INF createResources.go:110 > Successfully created a pod. pod=kubeshark-hub
2023-02-02T10:22:26+08:00 INF createResources.go:122 > Successfully created a pod. pod=kubeshark-front
2023-02-02T10:22:26+08:00 ERR versionCheck.go:28 > Failed to get the latest release.
2023-02-02T10:22:26+08:00 INF createResources.go:71 > Successfully created a service. service=kubeshark-hub
2023-02-02T10:22:26+08:00 INF createResources.go:78 > Successfully created a service. service=kubeshark-front
2023-02-02T10:22:26+08:00 INF tapRunner.go:160 > Added: pod=kubeshark-hub
2023-02-02T10:22:26+08:00 INF tapRunner.go:240 > Added: pod=kubeshark-front
2023-02-02T10:22:40+08:00 INF proxy.go:29 > Starting proxy... namespace=kubeshark service=kubeshark-hub src-port=8898
2023-02-02T10:22:40+08:00 INF workers.go:32 > Creating the worker DaemonSet...
2023-02-02T10:22:40+08:00 INF workers.go:51 > Successfully created the worker DaemonSet.
2023-02-02T10:22:41+08:00 INF tapRunner.go:402 > Hub is available at: url=http://localhost:8898
2023-02-02T10:22:42+08:00 INF proxy.go:29 > Starting proxy... namespace=kubeshark service=kubeshark-front src-port=8899
2023-02-02T10:22:44+08:00 INF tapRunner.go:418 > Kubeshark is available at: url=http://localhost:8899 - 在 netshoot 容器上多次请求 nginx 服务
1
2
3⋊> ~/2/c/k/demo kubectl exec -it netshoot-694f4cdf-s86lg bash 10:26:20
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
bash-5.2# curl nginx-service - 从 Kubeshark 界面可以查看到每个请求详细信息,并可以重放请求或者下载具体的包做更加详细的分析
- 使用过滤条件:
http and request.headers["user"] == "longhua”
- 通过 Service Map 查看服务之间依赖及调用情况
实验二:部署官方的 sock-shop
官方的 sock-shop 例子做得比较完整,包括了多种支持的协议。
- 下载 sock-shop-demo 代码
1
git clone https://github.com/kubeshark/sock-shop-demo.git
- 部署 sock-shop 服务,并且等待服务启动完成
1
kubectl apply -f sock-shop-demo/deploy/kubernetes/complete-demo.yaml
- 抓取 sock-shop 命名空间下的流量
1
kubeshark tap -n sock-shop
- 通过 Kubeshark UI 查看各个协议的流量