Kubernetes 上的 ExternalTrafficPolicy
前言
最近公司改用 Nginx ingress controller 配合一個 L4 Load Balancer 來處理進站流量,取代過去使用 Application Gateway,在部屬時發現官方預設在 Service 上設定 ExternalTrafficPolicy = Local (Ingress-nginx Azure deploy.yaml),AKS 文件上也提到如果想保留客戶端來源 IP 的話,必須要這樣設定。
經果一番探索後,發現和 Kube-proxy 如何處理進進站流量有關,也算是解答了我對 Service 實作的問題,這篇文章來記錄一下研究成果 🎉
Kube Proxy
kube-proxy 運行在每一個 Node 上,負責實作 Service,依照不同的 Mode 有不同的行為:
在 Mode 為 iptables 設定下,Kube Proxy 會 Watch API Server 並修改 Node 上的 iptables 來達到封包轉發的目的,也就是因為他只負責修改設定,實際上是由 Linux Core 來處理封包的關係,效能比 userspace mode 好上許多。
Kube-proxy 部分可以參考 GKE 的說明文件。
接下來我們來看看 kube-proxy 在不同型態的 Service 建立時會做哪些動作:
Cluster IP
在 Service type = ClusterIP 時,內部可以藉由 Service DNS 或 IP 存取到對應的 Pod,例如:
apiVersion: v1
kind: Service
metadata:
name: test
spec:
selector:
app: test
type: ClusterIP
ports:
- protocol: TCP
port: 8080
targetPort: 8080
kube-proxy 會建立像這樣的 iptables:
KUBE-SVC-IOIC7CRUMQYLZ32S tcp -- 0.0.0.0/0 10.109.69.11 /* default/test: cluster IP */ tcp dpt:8080
Chain KUBE-SVC-IOIC7CRUMQYLZ32S (1 references)
target prot opt source destination
KUBE-SEP-DZ6OGOAFZ2YMFV35 all -- 0.0.0.0/0 0.0.0.0/0 /* default/test: */ statistic mode random probability 0.50000000000
KUBE-SEP-PHU2ZXK3DXEO46Q2 all -- 0.0.0.0/0 0.0.0.0/0 /* default/test: */
Chain KUBE-SEP-DZ6OGOAFZ2YMFV35 (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.244.1.2 0.0.0.0/0 /* default/test: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/test: */ tcp to:10.244.1.2:8080
Chain KUBE-SEP-PHU2ZXK3DXEO46Q2 (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.244.2.2 0.0.0.0/0 /* default/test: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/test: */ tcp to:10.244.2.2:8080
當我們從 Cluster 內部向 Service (10.109.69.11:8080) 發送資料時,會進入 KUBE-SVC-IOIC7CRUMQYLZ32S Chain ,接著有 50% 機率進 KUBE-SEP-DZ6OGOAFZ2YMFV35 和 KUBE-SEP-PHU2ZXK3DXEO46Q2 (假設後端有兩個 Pod) ,最後經由 DNAT 進入到真正的 Pod IP。
這種狀況如果不是自己打自己的話就不會被標記為需要 SNAT,Application 端看到的就會是原始的 Pod IP,這種情況簡單很多。
由外部 NodePort 進入
這裡我們討論兩種 Policy,分別是 ExternalTrafficPolicy 為 Cluster (預設) 和 Local。
假設我們有 3 個 Node (Node1, Node2, Node3) 和兩個 Pod (Pod1, Pod2),Pod1 跑在 Node1、Pod2 跑在 Node2。