如何優(yōu)雅的在 Kubernetes Pod 內(nèi)進(jìn)行網(wǎng)絡(luò)抓包
使用 Kubernetes 時(shí),經(jīng)常會(huì)遇到一些棘手的網(wǎng)絡(luò)問(wèn)題需要對(duì) Pod 內(nèi)的流量進(jìn)行抓包分析。然而所使用的鏡像一般不會(huì)帶有 tcpdump 命令,過(guò)去常用的做法簡(jiǎn)單直接暴力:登錄到節(jié)點(diǎn)所在節(jié)點(diǎn),使用 root 賬號(hào)進(jìn)入容器,然后安裝 tcpdump。抓到的包有時(shí)還需要拉回本地,使用 Wireshark 進(jìn)行分析。而且整個(gè)過(guò)程非常繁瑣,跨越幾個(gè)環(huán)境。
正好前幾天也做了一次抓包問(wèn)題排查,這次就介紹一下快速進(jìn)行網(wǎng)絡(luò)抓包的幾種方法。
TL;DR
幾種方法各有優(yōu)缺點(diǎn),且都不建議在生產(chǎn)環(huán)境使用。假如必須使用,個(gè)人傾向于 kubectl debug 臨時(shí)容器的方案,但這個(gè)方案也有不足。
- 使用額外容器:這種方案為了 Pod 添加一個(gè)額外的容器,使用了靜態(tài)編譯的 tcpdump 進(jìn)行抓取,借助了多容器共享網(wǎng)絡(luò)空間的特性,適合 distroless 容器。缺點(diǎn)是需要修改原來(lái)的 Pod,調(diào)式容器重啟會(huì)引起 Pod 重啟。
- kubectl plugin ksniff:一個(gè) kubectl 插件。支持特權(quán)和非特權(quán)容器,可以將捕獲內(nèi)容重定向到 wireshark 或者 tshark。非特權(quán)容器的實(shí)現(xiàn)會(huì)稍微復(fù)雜。
- kubectl debug 臨時(shí)容器:該方案對(duì)于 distroless 容器有很好的支持,臨時(shí)容器退出后也不會(huì)導(dǎo)致 Pod 重啟。缺點(diǎn)是 1.23 的版本臨時(shí)容器才進(jìn)入 beta 階段;而且筆者在將捕獲的數(shù)據(jù)重定向到本地的 Wireshark 時(shí)會(huì)報(bào)數(shù)據(jù)格式不支持的錯(cuò)誤。
環(huán)境
使用 k3d 創(chuàng)建 k3s 集群,這里版本選擇 1.23:
$ k3d cluster create test --image rancher/k3s:v1.23.4-k3s1
抓包的對(duì)象使用 Pipy[1] 運(yùn)行的一個(gè) echo 服務(wù)(返回請(qǐng)求的 body 內(nèi)容):
$ kubectl run echo --image addozhang/echo-server --image-pull-policy IfNotPresent
為了方便訪問(wèn),創(chuàng)建一個(gè) NodePort Service:
$ kubectl expose pod echo --name echo --port 8080 --type NodePort
掛載容器
在之前的文章我們介紹調(diào)試 distroless 容器的幾種方法時(shí)曾用過(guò)修改 Pod 添加額外容器的方式,新的容器使用鏡像 addozhang/static-dump 鏡像。這個(gè)鏡像中加入了靜態(tài)編譯的 tcpdump。
修改后的 Pod:
apiVersion: v1
kind: Pod
metadata:
labels:
run: echo
name: echo
spec:
containers:
- image: addozhang/echo-server
imagePullPolicy: IfNotPresent
name: echo
resources: {}
- image: addozhang/static-dump
imagePullPolicy: IfNotPresent
name: sniff
command: ['sleep', '1d']
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
重新部署后,就可以使用下面命令將抓取網(wǎng)絡(luò)包并重定向到本地的 Wireshark:
$ kubectl exec -i echo -c sniff -- /static-tcpdump -i eth0 -U -w - | wireshark -k -i -
debug-container
kubectl plugin ksniff
ksniff[2] 是一個(gè) kubectl 插件,利用 tcpdump 和 Wireshark 對(duì) Pod 中的網(wǎng)絡(luò)包實(shí)現(xiàn)遠(yuǎn)程抓取。使用這種方法既可以借助 Wireshark 的強(qiáng)大功能,又能降低對(duì) Pod 的影響。
ksniff 的實(shí)現(xiàn)是上傳一個(gè)靜態(tài)編譯的tcpdump 到 Pod 中,然后將 tcpdump 的輸出重定向到本地的 Wireshark 進(jìn)行調(diào)試。
核心可以理解成 tcpdump -w - | wireshark -k -i -,與前面使用 debug 容器的方案類似。
安裝
通過(guò) krew[3] 安裝:
$ kubectl krew install sniff
或者下載發(fā)布包,手動(dòng)安裝:
$ unzip ksniff.zip
$ make install
特權(quán)模式容器
使用說(shuō)明參考 ksniff 官方說(shuō)明[4],這里我們只需要執(zhí)行如下命令,默認(rèn)就會(huì)重定向到 Wireshark,不需要顯示地指定:
$ kubectl sniff echo -n default -f "port 8080"
ksniff
除了使用 Wireshark,可以使用其命令行模式的 tshark:
$ kubectl sniff echo -n default -f "port 8080" -o - | tshark -r -
非特權(quán)模式容器
對(duì)于無(wú)特權(quán)的容器,就無(wú)法使用上面的方法了,會(huì)收到如下的錯(cuò)誤提示:
INFO[0000] command: '[/tmp/static-tcpdump -i any -U -w - port 8080]' executing successfully exitCode: '1', stdErr :'static-tcpdump: any: You don't have permission to capture on that device
(socket: Operation not permitted)
不過(guò),Ksniff 對(duì)此類容器也提供了支持。通過(guò)添加 -p 參數(shù),ksniff 會(huì)創(chuàng)建一個(gè)新的可以訪問(wèn)節(jié)點(diǎn)上 Docker Daemon 的 pod,然后將容器附加到目標(biāo)容器的網(wǎng)絡(luò)命名空間,并執(zhí)行報(bào)文捕獲。
注意,筆者使用的是 k3s 的環(huán)境,執(zhí)行命令時(shí)需要通過(guò)參數(shù)指定 Docker Daemon 的 socket 地址 --socket /run/k3s/containerd/containerd.sock
$ kubectl sniff echo -n default -f "port 8080" --socket /run/k3s/containerd/containerd.sock -p | wireshark -k -i -
ksniff-priviledged
kubectl debug 臨時(shí)容器
接下來(lái)也是之前介紹過(guò)的 kubectl debug ,也就是為 Pod 添加臨時(shí)容器[5]。
同樣我們可以通過(guò)這種方法對(duì) Pod 的網(wǎng)絡(luò)進(jìn)行抓包,臨時(shí)容器我們使用 addozhang/static-dump 鏡像。
$ kubectl debug -i echo --image addozhang/static-dump --target echo -- /static-tcpdump -i eth0
原本臨時(shí)容器應(yīng)該是其中最接近完美的方案:不需上傳任何文件目標(biāo)容器、無(wú)需修改 Pod、無(wú)需重啟、無(wú)需特權(quán)、支持 distroless 容器。然而,當(dāng)嘗試重定向到 Wireshark 或者 tshark 的時(shí)候,會(huì)遇到 Data written to the pipe is neither in a supported pcap format nor in pcapng format. 問(wèn)題。
最后經(jīng)過(guò)一番折騰,也未能解決該問(wèn)題。