你必須擁有一個正常工做的 Kubernetes 1.5 集羣,用來運行本文中的示例。該示例使用一個簡單的 nginx webserver 回送它接收到的請求的 HTTP 頭中的源 IP 地址。你能夠像下面這樣建立它:node
$ kubectl run ``source``-ip-app --image=k8s.gcr.io``/echoserver``:1.4``deployment ``"source-ip-app"` `created
若是你的 kube-proxy 運行在 iptables 模式下,從集羣內部發送到 ClusterIP 的包永遠不會進行源地址 NAT,這從 Kubernetes 1.2 開始是默認選項。Kube-proxy 經過一個 proxyMode endpoint 暴露它的模式。nginx
$ kubectl get nodes``NAME STATUS AGE VERSION``kubernetes-minion-group-6jst Ready 2h v1.6.0+fff5156``kubernetes-minion-group-cx31 Ready 2h v1.6.0+fff5156``kubernetes-minion-group-jj1t Ready 2h v1.6.0+fff5156``kubernetes-minion-group-6jst $ curl localhost:10249``/proxyMode``iptables
你能夠經過在 source IP 應用上建立一個服務來測試源 IP 保留。web
$ kubectl expose deployment ``source``-ip-app --name=clusterip --port=80 --target-port=8080``service ``"clusterip"` `exposed $ kubectl get svc clusterip``NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE``clusterip 10.0.170.92 <none> 80``/TCP` `51s
從相同集羣中的一個 pod 訪問這個 ClusterIP:json
$ kubectl run busybox -it --image=busybox --restart=Never --``rm``Waiting ``for` `pod default``/busybox` `to be running, status is Pending, pod ready: ``false``If you don't see a ``command` `prompt, try pressing enter.``# ip addr``1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue`` ``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``3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc noqueue`` ``link``/ether` `0a:58:0a:f4:03:08 brd ff:ff:ff:ff:ff:ff`` ``inet 10.244.3.8``/24` `scope global eth0`` ``valid_lft forever preferred_lft forever`` ``inet6 fe80::188a:84ff:feb0:26a5``/64` `scope link`` ``valid_lft forever preferred_lft forever
# wget -qO - 10.0.170.92``CLIENT VALUES:``client_address=10.244.3.8``command``=GET``...
若是客戶端 pod 和 服務端 pod 在相同的節點上,client_address 就是客戶端 pod 的 IP 地址。可是,若是它們在不一樣的節點上, client_address 將會是客戶端 pod 所在節點的 flannel IP 地址。後端
對於 Kubernetes 1.5,發送給類型爲 Type=NodePort Services 的數據包默認進行源地址 NAT。你能夠建立一個 NodePort Service 來進行測試:app
$ 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=="ExternalIP")].address }'``)
若是你的集羣運行在一個雲服務上,你可能須要爲上面報告的 nodes:nodeport 開啓一條防火牆規則。 如今,你能夠經過上面分配的節點端口從外部訪問這個 Service。負載均衡
$ ``for` `node ``in` `$NODES; ``do` `curl -s $node:$NODEPORT | ``grep` `-i client_address; ``done``client_address=10.180.1.1``client_address=10.240.0.5``client_address=10.240.0.3
請注意,這些並非正確的客戶端 IP,它們是集羣的內部 IP。這是所發生的事情:curl
一、客戶端發送數據包到 node2:nodePortide
二、node2 使用它本身的 IP 地址替換數據包的源 IP 地址(SNAT)oop
三、node2 使用 pod IP 地址替換數據包的目的 IP 地址
四、數據包被路由到 node 1,而後交給 endpoint
五、Pod 的回覆被路由回 node2
六、Pod 的回覆被髮送回給客戶端
形象的:
爲了防止這種狀況發生,Kubernetes 提供了一個特性來保留客戶端的源 IP 地址(點擊此處查看可用特性)。設置 service.spec.externalTrafficPolicy 的值爲 Local,請求就只會被代理到本地 endpoints 而不會被轉發到其它節點。這樣就保留了最初的源 IP 地址。若是沒有本地 endpoints,發送到這個節點的數據包將會被丟棄。這樣在應用到數據包的任何包處理規則下,你都能依賴這個正確的 source-ip 使數據包經過併到達 endpoint。
設置 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=104.132.1.79
請注意,你只從 endpoint pod 運行的那個節點獲得了一個回覆,這個回覆有正確的客戶端 IP。
這是發生的事情:
一、客戶端發送數據包到 node2:nodePort,它沒有任何 endpoints
二、數據包被丟棄
三、客戶端發送數據包到 node1:nodePort,它有endpoints
四、node1 使用正確的源 IP 地址將數據包路由到 endpoint
形象的:
對於 Kubernetes 1.5,發送給類型爲 Type=LoadBalancer Services 的數據包默認進行源地址 NAT,這是因爲全部處於 Ready 狀態的 Kubernetes 節點對於負載均衡的流量都是符合條件的。因此若是數據包到達一個沒有 endpoint 的節點,系統將把這個包代理到有 endpoint 的節點,並替換數據包的源 IP 爲節點的 IP(如前面章節所述)。
你能夠經過在一個 loadbalancer 上暴露這個 source-ip-app 來進行測試。
$ kubectl expose deployment ``source``-ip-app --name=loadbalancer --port=80 --target-port=8080 --``type``=LoadBalancer``service ``"loadbalancer"` `exposed``$ kubectl get svc loadbalancer``NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE``loadbalancer 10.0.65.118 104.198.149.140 80``/TCP` `5m``$ curl 104.198.149.140``CLIENT VALUES:``client_address=10.240.0.5``...
然而,若是你的集羣運行在 Google Kubernetes Engine/GCE 上,設置 service.spec.externalTrafficPolicy 字段值爲 Local 能夠強制使沒有 endpoints 的節點把他們本身從負載均衡流量的可選節點名單中刪除。這是經過故意使它們健康檢查失敗達到的。
形象的:
你能夠設置 annotation 來進行測試:
$ kubectl patch svc loadbalancer -p ``'{"spec":{"externalTrafficPolicy":"Local"}}'
你應該可以當即看到 Kubernetes 分配的 service.spec.healthCheckNodePort 字段:
$ kubectl get svc loadbalancer -o yaml | ``grep` `-i healthCheckNodePort`` ``healthCheckNodePort: 32122
service.spec.healthCheckNodePort 字段指向每一個節點在 /healthz 路徑上提供的用於健康檢查的端口。你能夠這樣測試:
$ kubectl get pod -o wide -l run=``source``-ip-app``NAME READY STATUS RESTARTS AGE IP NODE``source``-ip-app-826191075-qehz4 1``/1` `Running 0 20h 10.180.1.136 kubernetes-minion-group-6jst``kubernetes-minion-group-6jst $ curl localhost:32122``/healthz``1 Service Endpoints found``kubernetes-minion-group-jj1t $ curl localhost:32122``/healthz``No Service Endpoints Found
主節點運行的 service 控制器負責分配 cloud loadbalancer。在這樣作的同時,它也會分配指向每一個節點的 HTTP 健康檢查的 port/path。等待大約 10 秒鐘以後,沒有 endpoints 的兩個節點的健康檢查會失敗,而後 curl 負載均衡器的 ip:
$ curl 104.198.149.140``CLIENT VALUES:``client_address=104.132.1.79``...
因爲 Kubernetes 1.5 在類型爲 Type=LoadBalancer 的 Services 中支持源 IP 保存的特性僅在 cloudproviders 的子集中實現(GCP and Azure)。你的集羣運行的 cloudprovider 可能以某些不一樣的方式知足 loadbalancer 的要求:
一、使用一個代理終止客戶端鏈接並打開一個到你的 nodes/endpoints 的新鏈接。在這種狀況下,源 IP 地址將永遠是雲負載均衡器的地址而不是客戶端的。
二、使用一個包轉發器,所以從客戶端發送到負載均衡器 VIP 的請求在擁有客戶端源 IP 地址的節點終止,而不被中間代理。
第一類負載均衡器必須使用一種它和後端之間約定的協議來和真實的客戶端 IP 通訊,例如 HTTP X-FORWARDED-FOR 頭,或者 proxy 協議。 第二類負載均衡器能夠經過簡單的在保存於 Service 的 service.spec.healthCheckNodePort 字段上建立一個 HTTP 健康檢查點來使用上面描述的特性。
原文地址:
http://www.damonyi.cc/kubernetes%e4%b8%ad%e7%9a%84source-ip%e6%9c%ba%e5%88%b6/