antony@notes:~/kubernetes$ cat "理解-K8s-如何處理不同類型-service-的封包來源-IP.md"
理解 K8s 如何處理不同類型 service 的封包來源 IP
理解 K8s 如何處理不同類型 service 的封包來源 IP
在 Kubernetes 叢集中執行的應用程式會透過服務抽象(Service abstraction)來尋找彼此並與外界溝通。 本文件將說明傳送至不同類型服務的封包的來源 IP 會如何處理,以及您如何根據需求切換此行為。
在 Kubernetes 叢集中運行的應用程式,透過 Service(服務)的抽象概念來找到彼此並進行溝通,也可以和外部世界連接。這份文件將解釋,當封包發送到不同類型的 Service 時,來源 IP(Source IP)會發生什麼變化,以及如何根據需求調整這種行為。
術語
NAT 網路位址轉換
Source NAT 取代封包上的來源 IP;在本頁中,通常是指取代為 node 的 IP 位址。
Destination NAT 取代封包上的目的地 IP;在本頁中,這通常是指取代為 Pod 的 IP 位址
VIP 虛擬 IP 位址,例如指定給 Kubernetes 中每個 Service 的虛擬 IP 位址
一個網路背景程序,負責在每個 node 上協調管理 Service VIP(虛擬 IP)。
目標
- 透過各種不同類型的 Service 將簡單應用程式對外公開
- 瞭解每種 Service 類型如何處理來源 IP 的 NAT(網路地址轉換)
- 瞭解在保留來源 IP 時涉及的權衡取捨
建立測試的應用程式
$ kubectl create deployment source-ip-app --image=registry.k8s.io/echoserver:1.10執行結果 :
deployment.apps/source-ip-app createdSource IP for Services with Type=ClusterIP
如果您在叢集中運行 kube-proxy 並使用預設的 iptables mode,從叢集內部發送到 ClusterIP 的封包,不會進行來源 NAT(Source NAT)。您可以透過查詢 kube-proxy 所在節點上的網址 http://localhost:10249/proxyMode,來確認 kube-proxy 的運行模式。
$ curl http://localhost:10249/proxyMode執行結果 :
ipvs如果使用 Cilium 取代 kube-proxy,則此命令將不適用。
您可以透過為來源 IP 測試應用程式建立一個 Service,來測試來源 IP 的保留功能 :
$ kubectl expose deployment source-ip-app --name=clusterip --port=80 --target-port=8080執行結果 :
service/clusterip exposed查看 Service 資訊
$ kubectl get svc clusterip執行結果 :
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
clusterip ClusterIP 172.100.53.4 <none> 80/TCP 28s在同一個 K8s 叢集中,從某個 Pod 發送請求到 ClusterIP 的 Service
$ kubectl run busybox -it --image=busybox:1.28 --restart=Never --rm輸出與此類似:
If you don't see a command prompt, try pressing enter.
/ #然後,您可以在 Pod 內執行指令:
# Run this inside the terminal from "kubectl run"
ip addr執行結果 :
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
3: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1480 qdisc noqueue qlen 1000
link/ether 2e:2b:01:66:08:1b brd ff:ff:ff:ff:ff:ff
inet 172.99.236.23/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::2c2b:1ff:fe66:81b/64 scope link
valid_lft forever preferred_lft forever…然後使用 wget 查詢本機 webserver
# Replace "172.100.53.4" with the IPv4 address of the Service named "clusterip"
wget -qO - 172.100.53.4執行結果 :
...
Request Information:
client_address=172.99.236.23
method=GET
...
無論客戶端 Pod 和 server 端 Pod 是否位於同一個節點,client_address(客戶端位址)始終會顯示為客戶端 Pod 的 IP 地址。這表示,當您從叢集中的一個 Pod 向 ClusterIP 發送請求時,來源 IP 並未被修改。
在 node 使用 wget 查詢本機 webserver
curl 172.100.53.4執行結果 :
...
Request Information:
client_address=172.20.1.11
method=GET
...Source IP for Services with Type=NodePort
傳送至 Type=NodePort 服務的封包預設為來源 NAT。 您可以建立一個 NodePort 服務來測試:
$ kubectl expose deployment source-ip-app --name=nodeport --port=80 --target-port=8080 --type=NodePort執行結果 :
service/nodeport exposed現在您可以嘗試從群集外部透過上述分配的節點連接埠連線到服務。
$ NODEPORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services nodeport)
$ NODES=$(kubectl get nodes -o jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }')
$ for node in $NODES; do curl -s $node:$NODEPORT | grep -i client_address; done輸出類似於:
client_address=172.20.22.11請注意,這些不是正確的用戶端 IP,而是群集內部 IP。 這就是所發生的事:
- 用戶端傳送封包到
node2:nodePort - 節點 2 以自己的 IP 位址取代封包中的來源 IP 位址 (SNAT)
- 節點 2 以 pod IP 取代封包上的目的地 IP
- 封包會傳送到節點 1,然後傳送到端點
- pod 的回覆會傳回到節點 2
- pod 的回覆會傳回到用戶端

為了避免這種情況,Kubernetes 有一個保留 Client 端 source IP 的功能。 如果您將 service.spec.externalTrafficPolicy 設定為 Local 值,kube-proxy 只會 proxies proxy requests to local endpoints,而不會將流量轉送至其他節點。 此方法可保留原始來源 IP 位址。 如果沒有 local endpoints,傳送到節點的封包會被丟棄,因此您可以在任何封包處理規則中依賴正確的原始 IP 位址,這些規則可能會應用於傳送到端點的封包。
設定 service.spec.externalTrafficPolicy 欄位如下:
$ kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'輸出類似於:
service/nodeport patched現在,重新執行測試:
$ for node in $NODES; do curl --connect-timeout 1 -s $node:$NODEPORT | grep -i client_address; done輸出類似於:
client_address=172.20.22.11請注意,您只從端點 Pod 執行的節點得到一個回覆,並且是正確的 Client IP。
這就是發生的事情:
- 客戶端傳送封包到
node2:nodePort,而node2:nodePort沒有任何 endpoints - 封包被丟棄
- 用戶端傳送封包到
node1:nodePort,而node1:nodePort確實有 endpoints - node1 路由封包到具有正確來源 IP 的端點

Source IP for Services with Type=LoadBalancer
傳送至 Type=LoadBalancer 的服務的封包,預設會進行來源 NAT,因為所有處於 Ready 狀態的可排程 Kubernetes 節點,都符合負載平衡流量的資格。 因此,如果封包到達沒有端點的節點,系統會將其代理到有端點的節點,並以節點的 IP 取代封包上的來源 IP (如上一節所述)。
您可以透過負載平衡器暴露來源 IP 應用程式來測試:
$ kubectl expose deployment source-ip-app --name=loadbalancer --port=80 --target-port=8080 --type=LoadBalancer輸出為 :
service/loadbalancer exposed列印服務的 IP 位址:
$ kubectl get svc loadbalancer輸出與此類似 :
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
loadbalancer LoadBalancer 10.43.205.52 172.20.22.199 80:30729/TCP 8m24s接下來,傳送一個要求到此服務的 external-ip:
curl 172.20.22.199輸出與此類似:
client_address=172.20.22.11但是,如果您在 Google Kubernetes Engine/GCE 上執行,將相同的 service.spec.externalTrafficPolicy 欄位設定為 Local,就會強制沒有 Service 端點的節點,透過故意使健康檢查失敗的方式,將自己從符合負載平衡流量資格的節點清單中移除。

您可以透過設定註解來測試:
$ kubectl patch svc loadbalancer -p '{"spec":{"externalTrafficPolicy":"Local"}}'您應該會立即看到 Kubernetes 分配的 service.spec.healthCheckNodePort 欄位:
$ kubectl get svc loadbalancer -o yaml | grep -i healthCheckNodePort輸出與此類似:
healthCheckNodePort: 30865service.spec.healthCheckNodePort 欄位會指向每個節點上為 /healthz 提供健康檢查服務的連接埠。 您可以測試這一點:
$ kubectl get pod -o wide -l app=source-ip-app輸出與此類似:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
source-ip-app-75c58c94c6-m57pb 1/1 Running 0 99m 10.42.0.132 antony-rancher <none> <none>使用 curl 在不同節點上取得 /healthz endpoint :
# Run this locally on a node you choose
$ curl localhost:30865/healthz輸出與此類似:
{
"service": {
"namespace": "default",
"name": "loadbalancer"
},
"localEndpoints": 1,
"serviceProxyHealthy": true
}在不同的節點上,您可能會得到不同的結果:
# Run this locally on a node you choose
$ curl localhost:30865/healthz輸出與此類似:
No Service Endpoints FoundCleaning up
# Delete the Services:
$ kubectl delete svc -l app=source-ip-app
# Delete the Deployment, ReplicaSet and Pod:
$ kubectl delete deployment source-ip-app