人家都在抓神奇寶貝,而我在抓 Kubernetes 封包
Wireshark: Packet Don't Lie.

文章 永續性軟體工程: 遠端抓封包實錄 提到如何遠端抓單體 VM 封包,那同樣的招數可不可以去抓 Azure Kubernetes Service 特定 Pod 裡面的封包? 答案是可以,搭配 ksniff 來做就好
來抓 Kubernetes 封包
架構圖

- 因為我不能直接從家裡連線到 Azure Kuberentes Service,所有操作都需要透過 Bastion VM 進行操作連線,所以需要先用 SSH 連線到 Bastion VM 才能做事
 - kubectl 一定要能對 Private AKS 操作,不然 ksniff 或程式你都無法部署下去
 - 整張圖我沒提的部分就是 Private AKS 和 VM 的 UDR 設定,但這個太 Azure Networking 先跳過
 - 一如既往,我是強烈建議人人都要有一個 debug container 好去做一些觀落陰的事情,詳見 當遇到 Distroless Container 除錯要什麼沒什麼該怎麼辦? 你的好朋友 kubectl debug,這邊不贅述
 
0. 安裝 ksniff
請直接參考 eldadru/ksniff 文件,另外它的前置作業是要裝 krew,務必要把 krew PATH 設定好
1. 部署 httpbin-re 服務
# 確定 kubectl 運作正常且可以連線到 Private AKS
$ kubectl cluster-info
Kubernetes control plane is running at https://dns-aoai.private.eastus2.azmk8s.io:443
CoreDNS is running at https://dns-aoai.private.eastus2.azmk8s.io:443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://dns-aoai.private.eastus2.azmk8s.io:443/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy
# 跑一個 http server 服務起來
$ kubectl apply -f https://raw.githubusercontent.com/pichuang/httpbin-re/master/k8s/httpbin-re.yaml
deployment.apps/httpbin-re created
service/httpbin-re created
# 確認服務有揭露出來
$ kubectl get svc httpbin-rek
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
httpbin-re   ClusterIP   10.0.133.240   <none>        8080/TCP   7h26m
主要是要部署一個 HTTP Server,待會要透過 Ksniff 去觀察這個服務的狀況,如果你有其他服務也可以自行替換,這邊就是 Demo 方便
2. 部署陪測用 Pod
# 部署陪測用 Pod,用順手的就好,或者是你可以考慮用 nicolaka/netshoot
$ kubectl apply -f https://raw.githubusercontent.com/pichuang/debug-container/master/debug-container.yaml
pod/debug-container created
# 測試可以跟 httpbin-re 連線,同 namespace,不同 Pod
# kubectl exec --it <debug pod> -- curl http://<svc name>/path
$ kubectl exec --it debug-container -- curl http://httpbin-re:8080 | head -n 5
$ kubectl exec --it debug-container -- curl http://httpbin-re.default:8080 | head -n 5
$ kubectl exec --it debug-container -- curl http://httpbin-re.default.svc:8080 | head -n 5
<!DOCTYPE html>
<html lang="en">
...omit...
# 測試可以跟 metrics-server 連線,不同 namespace,不同 Pod
# kubectl exec --it <debug pod> -- curl --insecure https://<svc name>.<namespace>:<svc port>/path
$ kubectl exec --it debug-container -- curl --insecure https://metrics-server.kube-system:443/metrics | head -n 5
$ kubectl exec --it debug-container -- curl --insecure https://metrics-server.kube-system.svc:443/metrics | head -n 5
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "forbidden: User \"system:anonymous\" cannot get path \"/metrics\"",
  "reason": "Forbidden",
  "details": {},
  "code": 403
}%
關於這個以 Pod 為出發點,服務之間的連線議題,以前有寫過為什麼我佈署的 Kubernetes 服務不會動!? 個人除錯思路分享 一文,裡面有比較仔細的說明,這邊就不贅述了
3. 遠端抓 httpbin-re Pod 的封包
# 列舉出 httpbin-re Pod 的名稱
$ kubectl get pod -n default -owide
NAME                          READY   STATUS    RESTARTS   AGE     IP            NODE                                NOMINATED NODE   READINESS GATES
debug-container               1/1     Running   0          15m     `10.0.129.18`   aks-agentpool-14864487-vmss000000   <none>           <none>
httpbin-re-866499b564-blc95   1/1     Running   0          7h38m   10.0.129.6    aks-agentpool-14864487-vmss000000   <none>           <none>
httpbin-re-866499b564-xl726   1/1     Running   0          7h38m   `10.0.129.9`    aks-agentpool-14864487-vmss000000   <none>           <none>
ksniff-grlzn                  1/1     Running   0          82s     10.0.129.33   aks-agentpool-14864487-vmss000000   <none>           <none>
ksniff-n9ps6                  1/1     Running   0          36m     10.0.129.29   aks-agentpool-14864487-vmss000000   <none>           <none>
# 透過 ksniff 來抓封包
# ssh <Target username>@<Target IP> -p <Target Port> -i <SSH Private Key if have> "/bin/bash --login -c 'kubectl sniff -n <namespace> <pod name> -p -o -'" | /mnt/c/Program\ Files/Wireshark/Wireshark.exe -k -i -
ssh [email protected] -p 5566 -i ~/.ssh/wolala-rsa "/bin/bash --login -c 'kubectl sniff -n default httpbin-re-866499b564-xl726 -p -o -'" | /mnt/c/Program\ Files/Wireshark/Wireshark.exe -k -i -
這邊要注意的是 /bin/bash --login -c,因為 SSH 登入的時候吃的 $PATH 預設是不會去吃 ~/.bashrc,可以用 env 驗證看看,所以需要先用 --login 後才能正確咬到 $PATH 裡面的 krew 路徑,其餘就是 pipeline 和 I/O 的串流處理了

此外,原先應該要填 Ethernet Header (12 bytes) 的欄位,會變成 Linux cooked capture v2 (SLL_v2),按照 Linux cooked-mode capture (SLL) 的說明,這是一個偽造協議,對分析 Kubernetes 上的服務比較沒什麼影響,因為主要都是看 L3 / L4 層級居多,包含 Pod IP / Pod Port / Service IP / Service Port / Node Port 等,同場加映一下,
HWCHIU 學習筆記 - Kubernetes 之封包去哪兒
4. 清除 ksniff Pod
$ kubectl get pod -l app=ksniff -A
NAME           READY   STATUS    RESTARTS   AGE
ksniff-g4bjz   1/1     Running   0          11m
ksniff-j7gnc   1/1     Running   0          16m
ksniff-kxfl5   1/1     Running   0          9m54s
ksniff-lb4xg   1/1     Running   0          6m29s
ksniff-n9ps6   1/1     Running   0          68m
ksniff-sph6l   1/1     Running   0          9m17s
ksniff-v2vd5   1/1     Running   0          14m
ksniff-vl627   1/1     Running   0          16m
$ kubectl delete pod -l app=ksniff -A
pod "ksniff-g4bjz" deleted
pod "ksniff-j7gnc" deleted
pod "ksniff-kxfl5" deleted
pod "ksniff-lb4xg" deleted
pod "ksniff-n9ps6" deleted
pod "ksniff-sph6l" deleted
pod "ksniff-v2vd5" deleted
pod "ksniff-vl627" deleted
退出的時候看起來是不會順便把 ksniff 的 Pod 移除掉,記得要手動移除,不然會有資源浪費的問題
後話
ksniff 沒有參數可以讓我去掐封包大小,沒意外的話,還需要再跳板機多一段 tcpdump pipeline 處理,但這樣就會收到一堆有的沒的封包,還沒想到怎麼用
Q&A
Q1: 如果我是 Red Hat OpenShift Container Platform 或 Azure Red Hat OpenShift 的使用者,但權限鎖超嚴的怎麼辦?
這個只能說 Red Hat 一如既往地已經先幫妳整好到 oc 去了,你不用特別裝 ksniff,權限就是照你拿到的 kubeconfig 為主
$ oc version
Client Version: 4.12.36
Kustomize Version: v4.5.7
Kubernetes Version: v1.27.3
$ oc sniff
Usage:
  sniff pod [-n namespace] [-c container] [-f filter] [-o output-file] [-l local-tcpdump-path] [-r remote-tcpdump-path] [flags]
Examples:
kubectl sniff hello-minikube-7c77b68cff-qbvsd -c hello-minikube
Flags:
  -c, --container string                container (optional)
  -x, --context string                  kubectl context to work on (optional)
  -f, --filter string                   tcpdump filter (optional)
  -h, --help                            help for sniff
      --image string                    the privileged container image (optional)
  -i, --interface string                pod interface to packet capture (optional) (default "any")
  -l, --local-tcpdump-path string       local static tcpdump binary path (optional)
  -n, --namespace string                namespace (optional)
  -o, --output-file string              output file path, tcpdump output will be redirect to this file instead of wireshark (optional) ('-' stdout)
      --pod-creation-timeout duration   the length of time to wait for privileged pod to be created (e.g. 20s, 2m, 1h). A value of zero means the creation never times out. (default 1m0s)
  -p, --privileged                      if specified, ksniff will deploy another pod that have privileges to attach target pod network namespace
  -r, --remote-tcpdump-path string      remote static tcpdump binary path (optional) (default "/tmp/static-tcpdump")
      --socket string                   the container runtime socket path (optional)
      --tcpdump-image string            the tcpdump container image (optional)
  -v, --verbose                         if specified, ksniff output will include debug information (optional)