antony@notes:~/kubernetes$ cat "洞悉-Cilium-如何傳遞-K8s-Pod-網路封包.md"
洞悉 Cilium 如何傳遞 K8s Pod 網路封包
洞悉 Cilium 如何傳遞 K8s Pod 網路封包
1. Preface
本篇文章會在 CNI 使用 Cilium 的 Kubernetes 環境驗證,pod 跟 pod 之間互相溝通時,在同節點或在不同節點,網路的封包會如何傳遞,並搭配 tcpdump + wireshark 分析封包。
2. 環境資訊
K8s 節點及版本資訊
kubectl get nodes -o wide執行結果:
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME kubeadm-c1.bbg.com Ready control-plane 27d v1.34.1 172.20.6.11 <none> Ubuntu 24.04.3 LTS 6.8.0-85-generic containerd://2.1.4 kubeadm-c2.bbg.com Ready control-plane 27d v1.34.1 172.20.6.12 <none> Ubuntu 24.04.3 LTS 6.8.0-85-generic containerd://2.1.4 kubeadm-c3.bbg.com Ready control-plane 26d v1.34.1 172.20.6.13 <none> Ubuntu 24.04.3 LTS 6.8.0-85-generic containerd://2.1.4 kubeadm-w1.bbg.com Ready worker 26d v1.34.1 172.20.6.14 <none> Ubuntu 24.04.3 LTS 6.8.0-85-generic containerd://2.1.4 kubeadm-w2.bbg.com Ready worker 26d v1.34.1 172.20.6.15 <none> Ubuntu 24.04.3 LTS 6.8.0-85-generic containerd://2.1.4 kubeadm-w3.bbg.com Ready worker 26d v1.34.1 172.20.6.16 <none> Ubuntu 24.04.3 LTS 6.8.0-85-generic containerd://2.1.4cilium 設定資訊
helm -n kube-system get values cilium執行結果:
USER-SUPPLIED VALUES: # 忽略此行 bpf: masquerade: true hubble: relay: enabled: true ui: enabled: true service: type: NodePort ipam: mode: kubernetes ipv4NativeRoutingCIDR: 10.244.0.0/16 k8sServiceHost: 127.0.0.1 k8sServicePort: 6443 kubeProxyReplacement: true確認 cilium 版本、routing 模式和 device mode
kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep -iE "^cilium:|^routing|^device mode"執行結果:
Cilium: Ok 1.18.2 (v1.18.2-5bd307a8) Routing: Network: Tunnel [vxlan] Host: BPF Device Mode: veth本篇文章測試環境 routing-mode 為
tunnel,tunnel-protocol 使用vxlan,沒有特別 tunning。建立 NGINX pods
kubectl create deploy nginx --image=docker.io/library/nginx:latest --replicas=2建立 Client pods,replicas 設成
2,並指定跑在 worker nodeecho 'apiVersion: apps/v1 kind: Deployment metadata: labels: app: client name: client spec: replicas: 2 selector: matchLabels: app: client template: metadata: labels: app: client spec: nodeSelector: node-role.kubernetes.io/worker: "" containers: - image: quay.io/cooloo9871/debug.alp name: debug-alp securityContext: privileged: true' | kubectl apply -f -檢視 pods 各至在哪一個節點
kubectl get pods -o wide執行結果類似以下螢幕輸出:
NAME STATUS NODE client-65766844b7-72zg2 Running kubeadm-w1.bbg.com client-65766844b7-c4w29 Running kubeadm-w2.bbg.com nginx-66686b6766-6vd9x Running kubeadm-w2.bbg.com nginx-66686b6766-s25sh Running kubeadm-w3.bbg.com
3. 檢視單一 Pod 的網路架構
查看 client pod 的 ip address 和 subnet mask
kubectl exec -it client-65766844b7-c4w29 -- ip a執行結果:
...以上省略 34: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP link/ether d6:c4:d2:00:8c:2e brd ff:ff:ff:ff:ff:ff inet 10.244.7.250/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::d4c4:d2ff:fe00:8c2e/64 scope link valid_lft forever preferred_lft forever得知 subnet mask 為
/32,所以 pod 要上網一定會走 Default Gateway查看 client Pod 的 Default Gateway
kubectl exec -it client-65766844b7-c4w29 -- ip r執行結果:
default via 10.244.7.247 dev eth0 10.244.7.247 dev eth0 scope link確認 Default Gateway 的值為
10.244.7.247查看 client Pod 裡的 Neighbour Table
kubectl exec -it client-65766844b7-c4w29 -- ip n執行結果:
10.244.7.247 dev eth0 lladdr 1e:49:e0:67:ec:e7 used 0/0/0 probes 4 STALE得知 Default Gateway
10.244.7.247對應的 Mac Address 為1e:49:e0:67:ec:e7到 pod 所在的節點查找 pod 內看到 Default Gateway 對應在主機是哪張網卡
ip a s | grep -A 3 -B 2 10.244.7.247執行結果:
4: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether ba:af:34:f7:09:12 brd ff:ff:ff:ff:ff:ff inet 10.244.7.247/32 scope global cilium_host valid_lft forever preferred_lft forever inet6 fe80::b8af:34ff:fef7:912/64 scope link valid_lft forever preferred_lft forever發現
10.244.7.247這張網卡對應的 Mac Address 竟然是ba:af:34:f7:09:12而不是在 client pod 裡面看到的1e:49:e0:67:ec:e7繼續在 pod 所在的節點查找
1e:49:e0:67:ec:e7是哪張網卡ip a s | grep -A 3 -B 1 '1e:49:e0:67:ec:e7'執行結果:
35: lxc7904b406cec6@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 1e:49:e0:67:ec:e7 brd ff:ff:ff:ff:ff:ff link-netns cni-a59f663a-3aed-32da-7743-287142c3ccda inet6 fe80::1c49:e0ff:fe67:ece7/64 scope link valid_lft forever preferred_lft forever由網卡名稱可以得知是 veth pair,並且另一端連接在
cni-a59f663a-3aed-32da-7743-287142c3ccdaLinux Network Namespace進入這個 Linux Network Namespace 查看網卡資訊
sudo ip netns exec cni-a59f663a-3aed-32da-7743-287142c3ccda ip a執行結果:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 34: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether d6:c4:d2:00:8c:2e brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.244.7.250/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::d4c4:d2ff:fe00:8c2e/64 scope link valid_lft forever preferred_lft forever確認 client pod
eth0在主機側的網卡名稱是lxc7904b406cec6確認初始狀態下 pod 內部 arp cache
kubectl exec -it client-65766844b7-c4w29 -- arp -a執行結果
得知 pod 在初始狀態下 arp cache 應為空
小結論
- Client Pod
eth0網卡對應在 host 主機這側的網卡是lxc7904b406cec6 - 在 Client Pod 內部 Neighbour Table 紀錄 Default Gateway 的 IP 是在 host 主機
cilium_host網卡的 ip,但是 Mac Address 卻是 pod 在 host 主機側網卡的 mac address - pod 在初始狀態下無任何 arp cache
- 網路架構如下圖

4. 檢視同一台節點內 Pods 之間網路封包如何傳遞
查詢 nginx pod 網卡資訊,在 pod 所在的節點執行以下命令
pod_name="nginx-66686b6766-6vd9x" sudo ip netns exec $(sudo ip netns identify $(sudo crictl inspect $(sudo crictl ps -a | grep ${pod_name} | cut -d " " -f 1) | jq -r '.info.pid // .status.pid')) ip a s eth0執行結果:
32: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 9a:08:1c:60:8e:77 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.244.7.203/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::9808:1cff:fe60:8e77/64 scope link valid_lft forever preferred_lft forever得知 nginx pod 網卡資訊:
- 名稱為
eth0 - ip address 為
10.244.7.203/32 - mac address 為
9a:08:1c:60:8e:77
- 名稱為
找到 nginx pod 在主機側的網卡,在 pod 所在的節點執行以下命令
pod_name="nginx-66686b6766-6vd9x" ip a s | grep -A 3 ^$(sudo ip netns exec $(sudo ip netns identify $(sudo crictl inspect $(sudo crictl ps -a | grep ${pod_name} | cut -d " " -f 1) | jq -r '.info.pid // .status.pid')) ip a s eth0 | head -n 1 | cut -d ":" -f 2 | tail -c 3)執行結果:
33: lxcdad8b6341b55@if32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 2a:e0:31:24:98:d1 brd ff:ff:ff:ff:ff:ff link-netns cni-90146bc6-03b3-1ff1-72c8-61b61819bb95 inet6 fe80::28e0:31ff:fe24:98d1/64 scope link valid_lft forever preferred_lft forever得知 nginx pod 在主機側網卡資訊:
- 名稱為
lxcdad8b6341b55 - mac address 為
2a:e0:31:24:98:d1
- 名稱為
額外開啟 4 個終端機,分別都 ssh 到 client 和 nginx pod 所在的同一台節點上,並依序執行以下 4 個收集封包的動作
- 在 client pod 主機側的網卡上,透過
tcpdump命令收集 ARP 或 ICMP 封包sudo tcpdump -n -i lxc7904b406cec6 arp or icmp -vvv -w client-pod-host.pcap - 在 nginx pod 主機側的網卡上,透過
tcpdump命令收集 ARP 或 ICMP 封包sudo tcpdump -n -i lxcdad8b6341b55 arp or icmp -vvv -w nginx-pod-host.pcap - 在 client pod Container 側的網卡上,透過
tcpdump命令收集 ARP 或 ICMP 封包sudo ip netns exec cni-a59f663a-3aed-32da-7743-287142c3ccda tcpdump -n -i eth0 arp or icmp -vvv -w client-pod.pcap - 在 nginx pod Container 側的網卡上,透過
tcpdump命令收集 ARP 或 ICMP 封包sudo ip netns exec cni-90146bc6-03b3-1ff1-72c8-61b61819bb95 tcpdump -n -i eth0 arp or icmp -vvv -w nginx-pod.pcap
- 在 client pod 主機側的網卡上,透過
切換到可執行
kubectl主機的終端機,進到clientpod 去 pingnginxpodkubectl exec -it client-65766844b7-c4w29 -- ping -c 1 10.244.7.203執行結果:
PING 10.244.7.203 (10.244.7.203): 56 data bytes 64 bytes from 10.244.7.203: seq=0 ttl=63 time=0.198 ms --- 10.244.7.203 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max = 0.198/0.198/0.198 ms
小總結

- client Pod 去
ping同一節點的 nginx pod,因 subnet mask 為/32,網路封包一定往 Default Gateway10.244.7.247送,由於初始化 pod 內部 arp cache 為空,故觸發 arp 通訊協定來去得到 Default Gateway 的 mac address。
封包詳細資訊:

- 由 arp 通訊協定得知 Default Gateway 的 mac address 為
1e:49:e0:67:ec:e7,這 mac address 對應的網卡是 client pod 在主機側的網卡lxc7904b406cec6。
封包詳細資訊:

- 更詳細的運作流程:
- client Pod 以廣播的方式發送 arp request 封包
- arp request 被在主機側的網卡收到,此時 cilium bpf
cil_from_container程式會立刻接手 - 經過一連串判斷,會用自己的 MAC address 假裝自己是 gateway 來回復這個 arp request,詳情請看 shiun 大佬的源碼分析
cil_from_container在做什麼? 簡單一句話來解釋就是:「cil_from_container是 application container 網路的出口警衛,專門檢查所有從 app container 送出的網路封包,並決定它們的下一步該怎麼走。」 - client pod 收到被偽造 Mac address 的 arp reply
- 此時 icmp echo request 封包從 pod 內發送到在主機側的
lxc7904b406cec6網卡
封包詳細資訊:

- 立刻又被 Cilium 掛在該網卡上的 BPF 程式
cil_from_container接住,並作出以下判斷- 如果目標 pod 是同一個 Node → 找到目標 pod 在主機側的網卡 → 然後將封包送進 Container 側的網卡
- 更詳細運作流程:
- 拿到封包後,把目的 MAC Address 和來源的 MAC Address 的換掉,確保能送到正確的 Pod
- 直接送達:它不把包裹交給大樓的收發室 (Linux Network Stack),而是直接搭電梯(tail_call_policy)衝到收件 Pod 的門口(在主機側的網卡),把包裹交給門口的警衛 (Ingress BPF 程式) 做安全檢查 (Policy Enforcement)
- 以下是從 client pod 在主機側網卡收到的 icmp echo request 封包資訊 (為更換 mac address 前)
封包詳細資訊:
遺憾的是沒收到更換完 mac address 的 icmp echo request 封包
- 更詳細運作流程:
- 如果目標 pod 是跨 Node → 會轉交
cilium_vxlan/ens33網卡
- 如果目標 pod 是同一個 Node → 找到目標 pod 在主機側的網卡 → 然後將封包送進 Container 側的網卡
- 很明顯此次的目標 pod 跟來源 pod 是在同一個 node,經過 cilium bpf 程式一番查找得知要傳給目標 pod 在主機側的網卡,所以 icmp echo request 封包會發送到 nginx pod 在主機側的網卡
lxcdad8b6341b55 - Nginx pod 在主機側的網卡收到 icmp echo request 封包後,cilium bpf 程式
cil_from_container立馬接手,執行 nginx Pod 的 Ingress Policy 檢查。 - 檢查沒問題後,BPF 對 kernel 說:「我檢查完了,這封包沒問題,你接手處理吧!」。
- 此時封包會送進 nginx pod 的 Linux Network Namspace,由於之前已經修改了目的地和來源的 mac address,封包會被正確路由到 nginx Pod 在 Container 側的網卡。
封包詳細資訊:

- nginx pod 收到 icmp echo request 封包後,會需要回傳 icmp echo reply 的網路封包,同樣會因為 subnet mask 為
/32,網路封包一定往 Default Gateway10.244.7.247送,由於初始化 pod 內部 arp cache 為空,故又觸發 arp 通訊協定來去得到 Default Gateway 的 mac address(實際上會得到的是 nginx pod 在主機側網卡的 mac address) (arp 詳細運作流程不再重複描述)
- 透過 arp 通訊協定得知 nginx 在主機側網卡 mac 位址後,icmp echo reply 封包會發送至 nginx pod 在主機側的網卡
封包詳細資訊:

- icmp echo reply 封包傳送到在 nginx pod 主機側的網卡後,又又又被 cilium bpf 程式
cil_from_container接手,並作出以下判斷 :- 目標 pod 是同一個 Node
- 把目的 MAC Address 和來源的 MAC Address 的換掉,確保能送回 client pod
- 直接將網路封包送達 client pod 在主機側的網卡
- client pod 在主機側的網卡收到 icmp echo reply 封包後,cilium bpf 程式
cil_from_container接手,執行 client Pod 的 Ingress Policy 檢查,確認沒問題後最後傳回給 client pod 在 container 側的網卡。
封包詳細資訊:

補充說明
安裝 cilium 後,會在主機的 os 裡面,看到 cilium_host, cilium_net, 和 cilium_vxlan 這 3 個網路介面,目前根據上面的驗證結果可得知在同節點內 pod 跟 pod 之間互相溝通,並不會經過這 3 張網卡。
如果想驗證,可在 pods 之間互 ping 之前,執行以下收集封包的命令
收集
cilium_host網路介面的封包sudo tcpdump -n -i cilium_host arp or icmp -vvv -w cilium_host.pcap收集
cilium_net網路介面的封包sudo tcpdump -n -i cilium_net arp or icmp -vvv -w cilium_net.pcap收集
cilium_vxlan網路介面的封包sudo tcpdump -n -i cilium_net arp or icmp -vvv -w cilium_vxlan
收集結果會看到 icmp 封包,但 icmp 封包內要傳遞的 data 一定會跟 pod 之間互 ping 的 data 不一樣

所以可以確定在同節點內 pod 跟 pod 之間互相溝通,並不會經過 cilium_host, cilium_net, 和 cilium_vxlan 這 3 張網卡。