iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0
Cloud Native

EKS in Practice:IaC × GitOps 實戰 30 天系列 第 27

[Day 27] EKS Networking: Cilium 實戰踩坑紀錄

  • 分享至 

  • xImage
  •  

昨天我們聊了 Cilium 的原理,理解了 eBPF 如何取代 iptables/kube-proxy、VXLAN 如何幫助跨節點溝通,並且看到 SNAT/masquerade 為什麼對封包回程如此重要。

不過光有理論還不夠,如果要在 EKS 上實際跑起來,我們還必須在 安裝步驟 上做一些調整,才能避免和 AWS 預設的元件打架。這部分我也特別整理了一個「EKS with Cilium valid steps」,讓它能銜接之前 Day 8–14 所建立的 EKS → Argo CD → Helm Manager 的流程。


EKS with Cilium Valid Steps

  1. 建立 EKS 時不要安裝預設 addons
    • aws_eks_cluster.bootstrap_self_managed_addons 必須設為 false
    • 預設會自動安裝 VPC CNI、kube-proxy、CoreDNS 等,但我們要讓 Cilium 接手 VPC CNI 的角色,所以這些 addon 一開始不能裝。
  2. 只安裝必要的 addon
    • CoreDNS 還是需要,但可以等到 Cilium 安裝完成之後再處理。
    • CSI Driver 也建議在 Cilium 完成安裝後再部署。
  3. 先不建立 node group
    • Node group 要等 Cilium 部署完成後再建立,否則節點起來時沒有 CNI 設定檔,Pod 會無法分配 IP。
    • eks.var.node_groups 裡面先把 create = false,等 Cilium 安裝好之後再打開。
    • 這時 node group 會帶有一個預設 taint:node.cilium.io/agent-not-ready=NoSchedule,確保 Pod 不會排到尚未 ready 的節點上。
  4. 新增 Cilium Terraform module,安裝 Cilium Helm Chart
    • 部署 Cilium 時需要把原本給 VPC CNI 的 IRSA role 傳給它,指定在 eni.iamRole
    • 同時要把 EKS endpoint 填入 k8sServiceHost,讓 Cilium 能正確跟 API Server 溝通。
    • 設定時可以參考:Helm Chart default values
  5. 安裝 Argo CD 與 External Secrets
    • External Secret 的 webhook Pod 必須設成 hostNetwork: true,否則 API Server 找不到它。(後面會詳細說明原因)
  6. 安裝 Day 14 的 Helm Manager 幫我們管理剩下的 infra-charts
    • 確認 repo 可以被正常讀取,代表整個 Cilium 架構與前置整合流程已經跑通。

不過,安裝流程就算照著這樣跑,也不代表一切能順利。實際上,我在部署過程中還是踩了不少坑,從 VXLAN 封包不通、CoreDNS 無法解析,到 Webhook 卡在 overlay 的限制,幾乎把 EKS + Cilium 的各種問題都遇過一輪。接下來就分享這些 debug 的過程。


Debug 踩坑紀錄

1. VXLAN Overlay 不通

一開始部署時,cilium-health status 就顯示大多數節點都是 unreachable,只剩本機能通。檢查 log 時看到這樣的輸出:

Cluster health:               1/12 reachable   (2025-04-19T09:41:11Z)
Name                           IP               Node   Endpoints
	ip-10-0-0-101 (localhost)   10.0.0.101      1/1    1/1
	ip-10-0-0-107               10.0.0.107      0/1    0/1
	ip-10-0-0-112               10.0.0.112      0/1    0/1

進一步用 tcpdump 抓 VXLAN port(8472),發現只有 outbound 沒有 inbound:

$ tcpdump -ni ens5 udp port 8472

# 只有 out bound packet 沒有 inbound
IP ip-10-0-0-226 > ip-10-0-0-244.otv: OTV, flags [I] (0x08), overlay 0, instance 6
IP ip-192-168-7-216 > ip-192-168-3-215: ICMP echo request, id 20614, seq 0, length 32
IP ip-10-0-0-226 > ip-10-0-0-244.otv: OTV, flags [I] (0x08), overlay 0, instance 6
IP ip-192-168-7-216> ip-192-168-3-215: ICMP echo request, id 20614, seq 1, length 32
IP ip-10-0-0-226 > ip-10-0-0-244.otv: OTV, flags [I] (0x08), overlay 0, instance 6
IP ip-192-168-7-216 > ip-192-168-3-215: ICMP echo request, id 20614, seq 2, length 32
IP ip-10-0-0-226 > ip-10-0-0-244.otv: OTV, flags [I] (0x08), overlay 0, instance 6

再看 VPC Flow Logs,甚至能清楚看到 UDP 8472 封包被 reject

https://ithelp.ithome.com.tw/upload/images/20250927/20119667ZVe4nBxkqy.jpg

最後才發現問題根本不是 Cilium 本身,而是 Security Group 沒有放行 UDP 8472 ingress

👉 解法:在 node 的 Security Group 新增 inbound rule:

  • Type: Custom UDP
  • Port: 8472
  • Source: Self (SG 自引用)

2. Karpenter 節點缺少 Cilium CNI 設定

當集群需要自動擴容時,Karpenter 開出來的新節點上竟然沒有 /etc/cni/net.d/05-cilium.conflist,也沒有跑 Cilium Pod,導致 Pod 一直卡在 Pending。

原因有兩個:

  • 沒有加上 node.cilium.io/agent-not-ready=true:NoSchedule 的 taint。
  • Cilium 有 affinity 限制,導致只會跑在特定 node group。

👉 解法:

  1. 在 NodePool 加入 startupTaints,確保新節點在 Cilium 還沒 ready 前不會被排程:

    startupTaints:
      - key: node.cilium.io/agent-not-ready
        value: "true"
        effect: NoSchedule
    

    如果加在 taints 的話,Karpenter 會因為 cilium not ready 而誤判所有 pod 都無法 schedule,導致無法順利開啟新的 node,因此要加在 startupTaints

  2. 加上適當的 nodeAffinity 限制,讓 Cilium 能正確部署到 Karpenter node。

    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
            - matchExpressions:
                - key: eks.amazonaws.com/nodegroup
                  operator: Exists
            - matchExpressions:
                - key: karpenter.sh/registered
                  operator: Exists
    

3. CoreDNS 無法解析 DNS

另一個大坑出現在 CoreDNS:Pod 啟動後 DNS 查詢 timeout,log 顯示:HINFO: read udp 192.168.17.223:40853 → 10.0.0.2:53: i/o timeout

具體問題描述如下:

  1. 我有一個 pod ip 192.168.17.223 想要查 DNS,對 VPC DNS server(10.0.0.2:53)送封包

  2. 沒有做 masquerade 的情境下,封包會長這樣:

    From: 192.168.17.223:40853 # 有用 masquerade 會使用 node ip: 10.0.0.102:40853
    To:   10.0.0.2:53
    
  3. 封包會進入 VPC,但 10.0.0.2 要回封時,找不到 192.168.17.223 是誰,因此 timeout

檢查封包後才發現,問題出在 Cilium 沒有正確設置 masquerade

Pod 封包原始來源是 overlay IP(例如 192.168.x.x),傳到 VPC DNS server(10.0.0.2)後,回程時找不到對應 IP,直接被丟掉。

iptables -t nat -L 也印證了,沒有任何 MASQUERADE 規則:

[root@ip-10-0-0-102 /]# sudo iptables -t nat -L -n | grep CILIUM
CILIUM_PRE_nat     all  --  0.0.0.0/0            0.0.0.0/0            /* cilium-feeder: CILIUM_PRE_nat */
CILIUM_OUTPUT_nat  all  --  0.0.0.0/0            0.0.0.0/0            /* cilium-feeder: CILIUM_OUTPUT_nat */
CILIUM_POST_nat    all  --  0.0.0.0/0            0.0.0.0/0            /* cilium-feeder: CILIUM_POST_nat */
Chain CILIUM_OUTPUT_nat (1 references)
Chain CILIUM_POST_nat (1 references)
Chain CILIUM_PRE_nat (1 references)

👉 解法:

開啟 Cilium 的 bpf.masquerade: true,讓 SNAT 行為由 eBPF 接手。這樣封包出去時就會被換成 Node IP,保證能正確收到回覆。


4. Webhook 與 Overlay 的限制

這是踩坑過程中最核心的觀察。

  • Pod → 外部(例如 VPC DNS、RDS、S3):SNAT 可以處理,回覆能正確回來。
  • API Server → Pod(例如 CRD Webhook):SNAT 完全沒用,因為這是入站流量,控制平面只認得 Node IP 或 Service IP,根本找不到 overlay Pod。

這解釋了為什麼很多 Helm Chart(像 ExternalSecret、cert-manager)安裝時會直接失敗,因為註冊 CRD 的同時就觸發了 webhook 校驗,而 webhook Pod 在 overlay 裡面,API Server 打不到它。我們也可以看到官網上就有明確的說明:

https://ithelp.ithome.com.tw/upload/images/20250927/20119667Dsbxu4L4Qz.png

👉 解法:

  • CRD webhook → 必須 hostNetwork: true
  • 一般 workload webhook → 可以透過 Service proxy,不需要 hostNetwork。
類型 建議 原因
External Secrets webhook hostNetwork: true API Server 安裝 CRD 時會即刻觸發 webhook
cert-manager webhook ✅ or use Ingress 同上
CSI Daemon 要用到 IMDS,hostNetwork 才能 reach metadata endpoint
workload webhook 可透過 ClusterIP,不受 API Server 觸發時限制

5. Overlay / 出網 SNAT / 控制平面流量整理

為了釐清各種情境下要不要 SNAT、要不要 hostNetwork,我整理了一張表:

類型 舉例 是否需要 SNAT 是否需 hostNetwork 解釋
Pod → Pod (overlay) n8n → PostgreSQL overlay tunnel 處理,不經 VPC route
Pod → VPC DNS / RDS / S3 CoreDNS → 10.0.0.2 SNAT 可轉換 IP 避免回覆失敗
Pod → Internet curl github.com 同上,SNAT 後出網
API Server → webhook CRD → webhook ❌(SNAT 無效) 為入站,僅能打 node IP
Ingress → Pod 使用 ALB/NLB ingress controller 已處理 routing 與 proxy

這讓我在 debug 的時候更快對症下藥,例如 CoreDNS 出問題就立刻想到「是不是沒開 SNAT masquerade」,而 webhook 卡住時就馬上檢查「是不是缺 hostNetwork」。


小結:Cilium 到底能不能用?

回顧這一路下來,Cilium 確實成功幫我突破 VPC CIDR 的限制,但它同時也讓我見識到 overlay 模式下控制平面與資料平面的隔閡

  • Pod 出去找外部服務,SNAT 可以解決。
  • 外部要進來找 Pod(尤其是 API Server → Webhook Pod),SNAT 完全幫不上忙。

所以說,Cilium 是一把雙刃劍。它讓我們能「解放 Pod IP」,但代價是要更深入理解 Kubernetes 網路的內幕,並且花時間在正確配置 hostNetwork 與 SNAT。


結語

Day 26 我們理解了 Cilium 的原理,Day 27 則完整回顧了實際部署時的踩坑經驗。這些問題讓我一度懷疑「Cilium 到底能不能用」,但逐一解掉之後,我反而更有信心,也更清楚知道該在什麼情境下選擇正確的設計。

而當我們把網路議題從單一 CNI 拉高到更廣的層面,就會發現 流量治理 其實還有另一套思維:Service Mesh。

明天(Day 28),我會先介紹 Istio 與 Service Mesh 的基本概念,聊聊它們是如何在應用層面提供更進階的網路能力,補足 CNI 難以處理的部分。


上一篇
[Day 26] EKS Networking: 用 Cilium 解放 Pod IP 限制
下一篇
[Day 28] K8s Networking: 認識 Service Mesh
系列文
EKS in Practice:IaC × GitOps 實戰 30 天29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言