一文告訴你怎麼在 Kubernetes 下做網絡抓包

在 Kubernetes 的實際使用過程中,我們經常會碰到一些業務上的異常問題,一般情況下通過日誌監控和鏈路追蹤足以能夠對問題做出排查與診斷了。但是,在某些場景下,只靠這些手段往往是不夠的,一些和網絡相關的問題有時候非常棘手。

我通過身邊的案例觀察到,服務上線出現問題時,通常具備網絡排查能力的工程師,不論是在定位問題,還是在解決問題上,都會比不具備該能力的工程師快上很多。

所以。。。對開發工程師來說,學會在 Kubernetes 裏抓包 是一項非常重要的技能,下面我來和大家分享一下,在 Kubernetes 的世界裏,處於不同條件下的幾種常見抓包方法。

#1. 宿主機上捕獲

我們知道應用其實是運行在 Pod 內的 Container 裏的,所以只要定位到 Container 被調度到了哪個 Node 上,在相應的 Node 裏,對容器進行抓包即可。

0) 先決條件

需要有宿主機的訪問權限

1) 定位 Pod 的 containerID 以及它所運行的宿主機 IP

在 Kubernetes 集羣內執行下面這個指令,並從返回的結果中拿到兩個信息

  1. 宿主機 IP = 172.18.0.xxx

  2. container ID = 78e91175699f.....

# 注: 需要替換 namespace 和 pod name
➜ kubectl get pod \
  -n ${NAMESPACE}${POD_NAME} \
  -o json|jq '.status|{hostIP: .hostIP, container: [.containerStatuses[]|{name: .name, containerID: .containerID}]}'
{
  "hostIP": "172.18.0.xxx",
  "container": [
    {
      "name": "linkerd-proxy",
      "containerID": "docker://78e91175699f8cc0a3b0ff87da97407c19c7a86706a5b74e2d86f4428a4de75a"
    },
    {
      "name": "nginx",
      "containerID": "docker://7a6f7eabc2d5112437d30ee8ec1aa7ef963e97c3d09c3bc63613a70d106d7d01"
    }
  ]
}

2) 查找網絡接口索引

通過 ssh 登陸到 Pod 所在的宿主機上,然後在容器內執行 cat /sys/class/net/eth0/iflink,查找容器中的網卡與宿主機的 veth 網卡之間的對應關係

# 注: 需要替換 container id
docker exec -it ${CONTAINER_ID} /bin/bash -c 'cat /sys/class/net/eth0/iflink'
10227

3) 查找網絡接口信息

在宿主機上做 ip link 操作

ip link |grep 10227
10227: calicf227ed888a@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP mode DEFAULT group default

4) 在宿主機上抓包

最後,我們通過 tcpdump 指令,將報文保存到文件中

tcpdump -i calicf227ed888a@if4 -w /root/tcpdump.pcap

思考

通常情況下,並不是所有的開發者都能觸碰到宿主機的權限的,所以這種方式只適合集羣運維人員,而且這種操作確實也過於繁瑣了。

對於開發者來說,離我們最近的還是 Pod,下面我們來看下直接通過 Pod 是如何進行抓包的。

#2. 在 Pod 內抓包

0) 先決條件

  1. 目標 Pod 內的容器裏必須要有 tcpdump 工具

  2. 本地安裝 wireshark

1) 執行以下命令對目標 Pod 抓包

# 注: -c ${CONTAINER_NAME} 是可選擇的。如果 Pod 中僅包含一個容器,就可以忽略它
kubectl exec${POD_NAME} -c ${CONTAINER_NAME} -- tcpdump -i eth0 -w - | wireshark -k -i -

思考

不難發現,這種方法比第一種在執行層面上少了很多步驟。

只要滿足了 2 個前置條件,執行一條指令即可通過 wireshark 來解析報文,而且不需要宿主機的權限,但是它會存在一個問題:就是容器裏必須要有 tcpdump 這個工具

一般來說你可能會想到兩個解決辦法:

  1. 將 tcpdump 打包到鏡像裏。但是這樣做,它不僅會增加鏡像包的大小,也會有一定的風險,另外你不可能要求三方的鏡像也必須默認裝上對吧?

  2. 有同學說,直接進入容器裏做現場安裝咯?但是,你有沒有想過,很多客戶部署的環境都是要求隔離外網的,甚至有些 base image 用的是 無發行版鏡像 [1],你是根本沒機會做這件事的。

所以... 上面兩個方法不是說不能用,只能說不是最好的解決方案罷了。

#3. 通過 ephemeral containers 抓包

值得一提的是 Kubernetes 在 v1.16 [Alpha] 開始支持 Ephemeral Containers[2],它正好可以解決上面提的 2 個痛點,臨時容器對於排除交互式故障非常有用,Kubernetes 在 v1.23 [beta] 已經默認開啓該功能了。

比如我使用 nicolaka/netshoot[3] 鏡像用來調試,用法如下

# 注: 需要替換 pod name 和 container name
kubectl debug -i ${POD_NAME} \
  --image=nicolaka/netshoot \
  --target=${CONTAINER_NAME} -- tcpdump -i eth0 -w - | wireshark -k -i -

思考

但是,如果你的集羣版本比較低,在 v1.23 版本以下,且集羣管理員並沒有開啓 Ephemeral Containers 這個特性,咋辦咧?
如果是在生產環境,你不可能讓管理員升級集羣版本,或者開啓某一個特性來做 Debug 吧?

那麼問題來了,有沒有一種工具,它既可以做到不用觸碰宿主機,又可以不必在目標 Pod 內的容器裏提前安裝 tcpdump 呢?

非常好的一個問題,社區正好提供了 ksniff[4] 這個插件替我們解決了這個難題,下面我們一起來看一下吧。

#4. 通過 Ksniff 抓包

ksniff 是一個 kubectl 的插件,它利用 tcpdump 和 Wireshark 對 Kubernetes 集羣中的任何 Pod 啓動遠程抓包。

0) 先決條件

安裝 Krew
(
  set -x; cd"$(mktemp -d)" &&
  curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew.tar.gz" &&
  tar zxvf krew.tar.gz &&
  KREW=./krew-"$(uname | tr '[:upper:]' '[:lower:]')_$(uname -m | sed -e 's/x86_64/amd64/' -e 's/arm.*$/arm/')" &&
  "$KREW" install krew
)
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
安裝 sniff 插件

一旦安裝完 Krew 後,就可以通過以下指令完成 sniff 的安裝

kubectl krew install sniff

1) 執行 kubectl sniff 命令抓包

# 注:需要替換 pod name 和 namespace
kubectl sniff ${POD_NAME} -n ${NAMESPACE}

執行 sniff 命令後,本地會自動啓動 Wireshark 進行抓包,如下圖

以下是 sniff 運行的日誌,我只提取了日誌的關鍵部分

➜ kubectl sniff httpbin -n default

time="2022-02-20T19:56:13+08:00" level=info msg="using tcpdump path at: '/Users/linqiong/.krew/store/sniff/v1.6.1/static-tcpdump'"
time="2022-02-20T19:56:13+08:00" level=info msg="selected container: 'httpbin'"
time="2022-02-20T19:56:13+08:00" level=info msg="uploading file: '/Users/linqiong/.krew/store/sniff/v1.6.1/static-tcpdump' to '/tmp/static-tcpdump' on container: 'httpbin'"
....
time="2022-02-20T19:56:14+08:00" level=info msg="tcpdump uploaded successfully"
time="2022-02-20T19:56:14+08:00" level=info msg="spawning wireshark!"
time="2022-02-20T19:56:14+08:00" level=info msg="start sniffing on remote container"
time="2022-02-20T19:56:14+08:00" level=info msg="executing command: '[/tmp/static-tcpdump -i any -U -w - ]' on container: 'httpbin', pod: 'httpbin', namespace: 'default'"

從運行的日誌來看,sniff 是將本地的 static-tcpdump 文件上傳到 Pod 的指定容器的 /tmp 目錄下,然後在容器內,通過運行以下命令來達到抓包的目的

/tmp/static-tcpdump -i any -U -w -

另外需要說明的是,如果你是在服務器上執行,且服務器並沒有安裝 wireshark,你可以將報文輸出到文件中,然後用本地的 wireshark 來解析報文。

kubectl sniff httpbin -n default -o httpbin.pcap

其餘參數大家可以自行摸索做一下嘗試。

推薦閱讀

總結

文章主要分享了在 Kubernetes 下是如何進行網絡抓包的,並不涉及抓包分析技術。說到底,抓包技術主要還是要學會 tcpdumpWireshark 這兩個工具的用法,抓包分析技術其實是一門實踐課,理論看得再多,用處也不大。

它需要的是實踐... 再實踐...

參考資料

[1]

無發行版鏡像: https://github.com/GoogleContainerTools/distroless

[2]

Ephemeral Containers: https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/

[3]

nicolaka/netshoot: https://github.com/nicolaka/netshoot

[4]

ksniff: https://github.com/eldadru/ksniff

[5]

Ksniff: packet capture at pod level for k8s: https://kubesandclouds.com/index.php/2021/01/20/ksniff/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/pxa8-W5WY9ZoscZkWktiIw