昨天我們聊了 Cilium 的原理,理解了 eBPF 如何取代 iptables/kube-proxy、VXLAN 如何幫助跨節點溝通,並且看到 SNAT/masquerade 為什麼對封包回程如此重要。
不過光有理論還不夠,如果要在 EKS 上實際跑起來,我們還必須在 安裝步驟 上做一些調整,才能避免和 AWS 預設的元件打架。這部分我也特別整理了一個「EKS with Cilium valid steps」,讓它能銜接之前 Day 8–14 所建立的 EKS → Argo CD → Helm Manager 的流程。
aws_eks_cluster.bootstrap_self_managed_addons
必須設為 false
。eks.var.node_groups
裡面先把 create = false
,等 Cilium 安裝好之後再打開。node.cilium.io/agent-not-ready=NoSchedule
,確保 Pod 不會排到尚未 ready 的節點上。eni.iamRole
。k8sServiceHost
,讓 Cilium 能正確跟 API Server 溝通。hostNetwork: true
,否則 API Server 找不到它。(後面會詳細說明原因)不過,安裝流程就算照著這樣跑,也不代表一切能順利。實際上,我在部署過程中還是踩了不少坑,從 VXLAN 封包不通、CoreDNS 無法解析,到 Webhook 卡在 overlay 的限制,幾乎把 EKS + Cilium 的各種問題都遇過一輪。接下來就分享這些 debug 的過程。
一開始部署時,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。
最後才發現問題根本不是 Cilium 本身,而是 Security Group 沒有放行 UDP 8472 ingress。
👉 解法:在 node 的 Security Group 新增 inbound rule:
當集群需要自動擴容時,Karpenter 開出來的新節點上竟然沒有 /etc/cni/net.d/05-cilium.conflist
,也沒有跑 Cilium Pod,導致 Pod 一直卡在 Pending。
原因有兩個:
node.cilium.io/agent-not-ready=true:NoSchedule
的 taint。👉 解法:
在 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
加上適當的 nodeAffinity
限制,讓 Cilium 能正確部署到 Karpenter node。
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: eks.amazonaws.com/nodegroup
operator: Exists
- matchExpressions:
- key: karpenter.sh/registered
operator: Exists
另一個大坑出現在 CoreDNS:Pod 啟動後 DNS 查詢 timeout,log 顯示:HINFO: read udp 192.168.17.223:40853 → 10.0.0.2:53: i/o timeout
具體問題描述如下:
我有一個 pod ip 192.168.17.223
想要查 DNS,對 VPC DNS server(10.0.0.2:53
)送封包
沒有做 masquerade 的情境下,封包會長這樣:
From: 192.168.17.223:40853 # 有用 masquerade 會使用 node ip: 10.0.0.102:40853
To: 10.0.0.2:53
封包會進入 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,保證能正確收到回覆。
這是踩坑過程中最核心的觀察。
這解釋了為什麼很多 Helm Chart(像 ExternalSecret、cert-manager)安裝時會直接失敗,因為註冊 CRD 的同時就觸發了 webhook 校驗,而 webhook Pod 在 overlay 裡面,API Server 打不到它。我們也可以看到官網上就有明確的說明:
👉 解法:
hostNetwork: true
。類型 | 建議 | 原因 |
---|---|---|
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 觸發時限制 |
為了釐清各種情境下要不要 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 確實成功幫我突破 VPC CIDR 的限制,但它同時也讓我見識到 overlay 模式下控制平面與資料平面的隔閡:
所以說,Cilium 是一把雙刃劍。它讓我們能「解放 Pod IP」,但代價是要更深入理解 Kubernetes 網路的內幕,並且花時間在正確配置 hostNetwork 與 SNAT。
Day 26 我們理解了 Cilium 的原理,Day 27 則完整回顧了實際部署時的踩坑經驗。這些問題讓我一度懷疑「Cilium 到底能不能用」,但逐一解掉之後,我反而更有信心,也更清楚知道該在什麼情境下選擇正確的設計。
而當我們把網路議題從單一 CNI 拉高到更廣的層面,就會發現 流量治理 其實還有另一套思維:Service Mesh。
明天(Day 28),我會先介紹 Istio 與 Service Mesh 的基本概念,聊聊它們是如何在應用層面提供更進階的網路能力,補足 CNI 難以處理的部分。