Skip to content

antony@notes:~/kubernetes$ cat "洞悉-Cilium-如何傳遞-K8s-Pod-網路封包.md"

洞悉 Cilium 如何傳遞 K8s Pod 網路封包

2026-05-08· kubernetes

洞悉 Cilium 如何傳遞 K8s Pod 網路封包

1. Preface

本篇文章會在 CNI 使用 Cilium 的 Kubernetes 環境驗證,pod 跟 pod 之間互相溝通時,在同節點或在不同節點,網路的封包會如何傳遞,並搭配 tcpdump + wireshark 分析封包。

2. 環境資訊

  1. 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.4
  2. cilium 設定資訊

    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
  3. 確認 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。

  4. 建立 NGINX pods

    kubectl create deploy nginx --image=docker.io/library/nginx:latest --replicas=2
  5. 建立 Client pods,replicas 設成 2,並指定跑在 worker node

    echo '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 -
  6. 檢視 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 的網路架構

  1. 查看 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

  2. 查看 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

  3. 查看 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

  4. 到 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

  5. 繼續在 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-287142c3ccda Linux Network Namespace

  6. 進入這個 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

  7. 確認初始狀態下 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
  • 網路架構如下圖 image

4. 檢視同一台節點內 Pods 之間網路封包如何傳遞

  1. 查詢 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
  2. 找到 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
  3. 額外開啟 4 個終端機,分別都 ssh 到 client 和 nginx pod 所在的同一台節點上,並依序執行以下 4 個收集封包的動作

    1. 在 client pod 主機側的網卡上,透過 tcpdump 命令收集 ARP 或 ICMP 封包
      sudo tcpdump -n -i lxc7904b406cec6 arp or icmp -vvv -w client-pod-host.pcap
    2. 在 nginx pod 主機側的網卡上,透過 tcpdump 命令收集 ARP 或 ICMP 封包
      sudo tcpdump -n -i lxcdad8b6341b55 arp or icmp -vvv -w nginx-pod-host.pcap
    3. 在 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
    4. 在 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
  4. 切換到可執行 kubectl 主機的終端機,進到 client pod 去 ping nginx pod

    kubectl 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

小總結

image

  1. client Pod 去 ping 同一節點的 nginx pod,因 subnet mask 為 /32,網路封包一定往 Default Gateway 10.244.7.247 送,由於初始化 pod 內部 arp cache 為空,故觸發 arp 通訊協定來去得到 Default Gateway 的 mac address。 image 封包詳細資訊: image
  2. 由 arp 通訊協定得知 Default Gateway 的 mac address 為 1e:49:e0:67:ec:e7,這 mac address 對應的網卡是 client pod 在主機側的網卡 lxc7904b406cec6image 封包詳細資訊: image
  3. 更詳細的運作流程:
    1. client Pod 以廣播的方式發送 arp request 封包
    2. arp request 被在主機側的網卡收到,此時 cilium bpf cil_from_container 程式會立刻接手
    3. 經過一連串判斷,會用自己的 MAC address 假裝自己是 gateway 來回復這個 arp request,詳情請看 shiun 大佬的源碼分析

      cil_from_container 在做什麼? 簡單一句話來解釋就是:「cil_from_container 是 application container 網路的出口警衛,專門檢查所有從 app container 送出的網路封包,並決定它們的下一步該怎麼走。」

    4. client pod 收到被偽造 Mac address 的 arp reply
  4. 此時 icmp echo request 封包從 pod 內發送到在主機側的 lxc7904b406cec6 網卡 image 封包詳細資訊: image
  5. 立刻又被 Cilium 掛在該網卡上的 BPF 程式 cil_from_container 接住,並作出以下判斷
    • 如果目標 pod 是同一個 Node → 找到目標 pod 在主機側的網卡 → 然後將封包送進 Container 側的網卡
      • 更詳細運作流程:
        1. 拿到封包後,把目的 MAC Address 和來源的 MAC Address 的換掉,確保能送到正確的 Pod
        2. 直接送達:它不把包裹交給大樓的收發室 (Linux Network Stack),而是直接搭電梯(tail_call_policy)衝到收件 Pod 的門口(在主機側的網卡),把包裹交給門口的警衛 (Ingress BPF 程式) 做安全檢查 (Policy Enforcement)
      • 以下是從 client pod 在主機側網卡收到的 icmp echo request 封包資訊 (為更換 mac address 前) image 封包詳細資訊: image 遺憾的是沒收到更換完 mac address 的 icmp echo request 封包
    • 如果目標 pod 是跨 Node → 會轉交 cilium_vxlan / ens33 網卡
  6. 很明顯此次的目標 pod 跟來源 pod 是在同一個 node,經過 cilium bpf 程式一番查找得知要傳給目標 pod 在主機側的網卡,所以 icmp echo request 封包會發送到 nginx pod 在主機側的網卡 lxcdad8b6341b55
  7. Nginx pod 在主機側的網卡收到 icmp echo request 封包後,cilium bpf 程式 cil_from_container 立馬接手,執行 nginx Pod 的 Ingress Policy 檢查。
  8. 檢查沒問題後,BPF 對 kernel 說:「我檢查完了,這封包沒問題,你接手處理吧!」。
  9. 此時封包會送進 nginx pod 的 Linux Network Namspace,由於之前已經修改了目的地和來源的 mac address,封包會被正確路由到 nginx Pod 在 Container 側的網卡。 image 封包詳細資訊: image
  10. nginx pod 收到 icmp echo request 封包後,會需要回傳 icmp echo reply 的網路封包,同樣會因為 subnet mask 為 /32,網路封包一定往 Default Gateway 10.244.7.247 送,由於初始化 pod 內部 arp cache 為空,故又觸發 arp 通訊協定來去得到 Default Gateway 的 mac address(實際上會得到的是 nginx pod 在主機側網卡的 mac address) (arp 詳細運作流程不再重複描述) image
  11. 透過 arp 通訊協定得知 nginx 在主機側網卡 mac 位址後,icmp echo reply 封包會發送至 nginx pod 在主機側的網卡 image 封包詳細資訊: image
  12. icmp echo reply 封包傳送到在 nginx pod 主機側的網卡後,又又又被 cilium bpf 程式 cil_from_container 接手,並作出以下判斷 :
    1. 目標 pod 是同一個 Node
    2. 把目的 MAC Address 和來源的 MAC Address 的換掉,確保能送回 client pod
    3. 直接將網路封包送達 client pod 在主機側的網卡
  13. client pod 在主機側的網卡收到 icmp echo reply 封包後,cilium bpf 程式 cil_from_container 接手,執行 client Pod 的 Ingress Policy 檢查,確認沒問題後最後傳回給 client pod 在 container 側的網卡。 image 封包詳細資訊: image

補充說明

安裝 cilium 後,會在主機的 os 裡面,看到 cilium_host, cilium_net, 和 cilium_vxlan 這 3 個網路介面,目前根據上面的驗證結果可得知在同節點內 pod 跟 pod 之間互相溝通,並不會經過這 3 張網卡。

如果想驗證,可在 pods 之間互 ping 之前,執行以下收集封包的命令

  1. 收集 cilium_host 網路介面的封包

    sudo tcpdump -n -i cilium_host arp or icmp -vvv -w cilium_host.pcap
  2. 收集 cilium_net 網路介面的封包

    sudo tcpdump -n -i cilium_net arp or icmp -vvv -w cilium_net.pcap
  3. 收集 cilium_vxlan 網路介面的封包

    sudo tcpdump -n -i cilium_net arp or icmp -vvv -w cilium_vxlan

收集結果會看到 icmp 封包,但 icmp 封包內要傳遞的 data 一定會跟 pod 之間互 ping 的 data 不一樣

image

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

5. 參考文章