問題背景
大部分使用公有雲的團隊比較關注 Ingress(流量如何進入容器),但對 Egress(流量如何從 Kubernetes 出去)往往不太在意,畢竟能出去就好。
然而在某些場景下,外部服務會限制來源 IP 的白名單(通常是資安要求),這時就需要確保所有從 Kubernetes 叢集送出的封包都使用可控且固定的來源 IP。問題在於:正常情況下封包會經過所在節點的 SNAT 處理,而節點可能會動態增減,來源 IP 自然也無法預測。
Kubernetes 內建並沒有 Egress 的概念,通常需要依靠 Istio 等 Service Mesh 方案來將封包導向特定節點。本文示範一種更輕量的做法:用 Linux 原生的 iptables + iproute 工具,將封包轉發到固定的 Gateway 節點,再由該節點進行 SNAT 轉發。
環境假設
| 項目 | 設定 |
|---|---|
| CNI | Flannel 或 Canal |
| Kubernetes 版本 | 1.24.x |
| Cluster CIDR | 172.16.0.0/14 |
| Private Network CIDR | 10.0.0.0/8 |
| Gateway Node 的 flannel.1 IP | 172.16.4.0 |
流量分類
從 Pod 出發的流量,依據目的地可以分為三類:
- Pod → 172.16.0.0/14:In-cluster 流量(叢集內部通訊)
- Pod → 10.0.0.0/8:Cluster Egress 流量,目的地在內網
- Pod → 其他:Cluster Egress 流量,目的地在外網
我們的目標是將「目的地在內網的 Egress 流量」固定為特定的來源 IP。只需要三個步驟即可完成。
步驟一:新增路由表
新增一條靜態路由到自訂路由表 56,作用是:凡是目的地為 10.0.0.0/8 的封包,下一跳必須經過 Gateway Node 的 flannel.1 介面(172.16.4.0):
ip route add 10.0.0.0/8 via 172.16.4.0 dev flannel.1 onlink table 56
其中 onlink 表明此 Gateway 是在鏈路上可直接到達。驗證結果:
$ ip route show table 56
10.0.0.0/8 via 172.16.4.0 dev flannel.1 onlink
步驟二:設定路由規則(Policy Routing)
新增兩條路由規則來區分 in-cluster 流量和 egress 流量。順序很重要 — in-cluster 流量需要有更高的優先權,確保它走預設路由表(main):
# In-cluster 流量:優先權 5000,走 main 路由表(保持預設行為)
ip rule add from 172.16.0.0/14 to 172.16.0.0/14 pref 5000 table main
# Egress 流量:優先權 5566,走自訂路由表 56
ip rule add from 172.16.0.0/14 pref 5566 table 56
驗證結果:
$ ip rule show
0: from all lookup local
5000: from 172.16.0.0/14 to 172.16.0.0/14 lookup main
5566: from 172.16.0.0/14 lookup 56
32766: from all lookup main
32767: from all lookup default
這樣一來,Pod 發出的封包如果目的地也在 Cluster CIDR 內(in-cluster),會優先匹配 pref 5000 走 main 表;其餘來自 Cluster CIDR 的封包則走 table 56,被導向 Gateway Node。
步驟三:調整 iptables POSTROUTING 規則
正常情況下,Flannel CNI 會在 POSTROUTING chain 對 Cluster CIDR 來源的封包做 SNAT(MASQUERADE)。我們需要在 Flannel 的 MASQUERADE 規則之前,為 Egress 流量加一條 RETURN,跳過 SNAT 處理:
iptables -t nat -I POSTROUTING 3 -s 172.16.0.0/14 -d 10.0.0.0/8 -j RETURN
驗證結果:
$ iptables -t nat -L POSTROUTING --line-numbers
Chain POSTROUTING (policy ACCEPT)
num target prot opt source destination
1 KUBE-POSTROUTING all -- anywhere anywhere
2 RETURN all -- 172.16.0.0/14 10.0.0.0/8 # 新增
3 RETURN all -- 172.16.0.0/14 172.16.0.0/14 # flanneld masq
4 MASQUERADE all -- 172.16.0.0/14 !base-address.mcast.net/4
5 RETURN all -- !172.16.0.0/14 ...
這樣 Egress 封包在離開節點前就不會被 SNAT,會保持原始的 Pod IP 經過 flannel tunnel 到達 Gateway Node,再由 Gateway Node 統一做 SNAT。
部署建議
以上三個步驟需要在每個需要固定 Egress IP 的節點上執行。手動操作容易出錯,建議的做法是:
- 將以上步驟寫成 Shell Script 或開發成專用程式
- 透過 DaemonSet 部署到目標節點上,這樣新加入叢集的節點也會自動套用設定
- 不需要所有節點都執行 — 可以規劃特定的節點集合來運行這些 DaemonSet
- 作為 Gateway 的節點本身不需要做這些設定,它只負責接收轉發過來的封包並做 SNAT
方案特點
| 優勢 | 限制 |
|---|---|
| 不需要 Service Mesh 等重量級方案 | 僅在 Flannel/Canal CNI 上驗證過 |
| 使用 Linux 原生工具,學習成本低 | Gateway Node 成為單點,需考慮高可用 |
| 透過 DaemonSet 可自動化部署 | 需要對 Linux 路由有基本理解 |
| 對叢集效能影響極小 | iptables 規則順序必須正確 |
這個方案的核心思路是「用 Policy Routing 將特定目的地的封包導向固定節點」,概念簡單但效果顯著。如果你的場景只需要固定內網 Egress IP,這比導入整套 Service Mesh 要省事得多。

發佈留言