咱們生產k8s對外暴露服務有多種方式,其中一種使用 external-ips clusterip service ClusterIP Service方式對外暴露服務,kube-proxy使用iptables mode。這樣external ips能夠指定固定幾臺worker節點的IP地址(worker節點服務已經被驅逐,做爲流量轉發節點不做爲計算節點),並做爲lvs vip下的rs來負載均衡。根據vip:port來訪問服務,而且根據port不一樣來區分業務。相比於NodePort Service那樣能夠經過全部worker節點的node_ip:port來訪問更高效,也更容易落地生產。html
可是,traffic packet是怎麼根據集羣外worker節點的node_ip:port或者集羣內cluster_ip:port訪問方式找到pod ip的?node
而且,咱們生產k8s使用calico來做爲cni插件,採用 Peered with TOR (Top of Rack) routers
方式部署,每個worker node和其置頂交換機創建bgp peer配對,置頂交換機會繼續和上層核心交換機創建bgp peer配對,這樣能夠保證pod ip在公司內網能夠直接被訪問。nginx
可是,traffic packet知道了pod ip,又是怎麼跳到pod的呢?git
以上問題能夠歸併爲一個問題:數據包是怎麼一步步跳轉到pod的?很長時間以來,一直在思考這些問題。github
實際上答案很簡單:訪問業務服務vip:port或者說node_ip:port,當packet到達node_ip所在機器如worker A節點時,會根據iptable rules一步步找到pod ip;找到了pod ip後,因爲使用calico bgp部署方式,核心交換機和置頂交換機都有該pod ip所在的ip段的路由,packet最後會跳轉到某一個worker節點好比worker B,而worker B上有calico早就寫好的路由規則route和虛擬網卡virtual interface,再根據veth pair從而由host network namespace跳轉到pod network namespace,從而跳轉到對應的pod。算法
首先能夠本地部署個k8s集羣模擬測試下,這裏使用 install minikube with calico :docker
minikube start --network-plugin=cni --cni=calico # 或者 minikube start --network-plugin=cni kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
而後部署個業務pod,這裏使用nginx爲例,副本數爲2,並建立ClusterIP Service with ExternalIPs和NodePort Service:shell
--- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo-1 labels: app: nginx-demo-1 spec: replicas: 2 template: metadata: name: nginx-demo-1 labels: app: nginx-demo-1 spec: containers: - name: nginx-demo-1 image: nginx:1.17.8 imagePullPolicy: IfNotPresent livenessProbe: httpGet: port: 80 path: /index.html failureThreshold: 10 initialDelaySeconds: 10 periodSeconds: 10 restartPolicy: Always selector: matchLabels: app: nginx-demo-1 --- apiVersion: v1 kind: Service metadata: name: nginx-demo-1 spec: selector: app: nginx-demo-1 ports: - port: 8088 targetPort: 80 protocol: TCP type: ClusterIP externalIPs: - 192.168.64.57 # 這裏worker節點ip能夠經過 minikube ip 查看,這裏填寫你本身的worker節點ip地址 --- apiVersion: v1 kind: Service metadata: name: nginx-demo-2 spec: selector: app: nginx-demo-1 ports: - port: 8089 targetPort: 80 type: NodePort ---
部署完成後,就能夠經過 ExternalIP ClusterIP Service或者NodePort Service兩種方式訪問業務服務:api
當數據包經過node_ip:port或者cluster_ip:port訪問服務時,會在當前worker節點被內核DNAT(Destination Network Address Translation)爲pod ip,反向packet又會被SNAT(Source Network Address Translation)。這裏借用calico官網的很是生動的兩張圖說明 About Kubernetes Services :app
cluster-ip service 訪問流程:
node-port service 訪問流程:
因爲咱們生產k8s的kube-proxy使用iptables mode,因此這些snat/dnat規則是kube-proxy進程經過調用iptables命令來實現的。iptables使用各類chain來管理大量的iptable rules,主要是五鏈四表,五鏈包括:prerouting/input/output/forward/postrouting chain,四表包括:
raw/mangle/nat/filter table,同時也能夠用戶自定義chain。數據包packet進過內核時通過五鏈四表流程圖以下:
而kube-proxy進程會在nat table內自定義KUBE-SERVICES chain,並在PREROUTING內生效,能夠經過命令查看,而後在查看KUBE-SERVICES chain中的規則:
sudo iptables -v -n -t nat -L PREROUTING | grep KUBE-SERVICES sudo iptables -v -n -t nat -L KUBE-SERVICES sudo iptables -v -n -t nat -L KUBE-NODEPORTS
能夠看到,若是在集羣內經過cluster_ip:port即10.196.52.1:8088,或者在集羣外經過external_ip:port即192.168.64.57:8088方式訪問服務,都會在內核裏匹配到 KUBE-SVC-JKOCBQALQGD3X3RT
chain的規則,這個對應nginx-demo-1 service;若是是在集羣內經過cluster_ip:port即10.196.89.31:8089,或者集羣外經過nodeport_ip:port即192.168.64.57:31755方式訪問服務,會匹配到 KUBE-SVC-6JCCLZMUQSW27LLD
chain的規則,這個對應nginx-demo-2 service:
而後繼續查找 KUBE-SVC-JKOCBQALQGD3X3RT
chain和 KUBE-SVC-6JCCLZMUQSW27LLD
chain的規則,發現每個 KUBE-SVC-xxx
都會跳轉到 KUBE-SEP-xxx
chain上,而且由於pod副本數是2,這裏就會有兩個 KUBE-SEP-xxx
chain,而且以50%機率跳轉到任何一個 KUBE-SEP-xxx
chain,即rr(round robin)負載均衡算法,這裏kube-proxy使用iptables statistic module來設置的,最後就會跳轉到pod ip 10.217.120.72:80(這裏假設訪問這個pod)。總之,通過kube-proxy調用iptables命令,根據service/endpoint設置對應的chain,最終一步步跳轉到pod ip,從而數據包packet下一跳是該pod ip:
sudo iptables -v -n -t nat -L KUBE-SVC-JKOCBQALQGD3X3RT sudo iptables -v -n -t nat -L KUBE-SEP-CRT5ID3374EWFAWN sudo iptables -v -n -t nat -L KUBE-SVC-6JCCLZMUQSW27LLD sudo iptables -v -n -t nat -L KUBE-SEP-SRE6BJUIAABTZ4UR
總之,無論是經過cluster_ip:port、external_ip:port仍是node_ip:port方式訪問業務服務,packet經過kube-proxy進程自定義的各類chain找到了下一跳pod ip地址。
可是,packet如何知道這個pod ip在哪一個節點呢?
上文已經說過,咱們部署calico方式能夠保證pod ip在集羣外是能夠被路由的,這是由於交換機上會有node level的路由規則,在交換機上執行 dis bgp routing-table
會有相似以下路由規則。表示若是訪問 10.20.30.40/26
pod網段下一跳是worker B的IP地址。這些路由規則是部署在每個worker節點的bird進程(bgp client)分發的,交換機經過BGP學習來的:
# 這裏是隨機編造的地址 Network NextHop ... 10.20.30.40/26 10.203.30.40 ...
因此,packet在知道了pod ip 10.217.120.72:80
後(這裏假設訪問了pod nginx-demo-1-7f67f8bdd8-fxptt),很容易找到了worker B節點,本文章示例便是minikube節點。查看該節點的路由表和網卡,找到了在host network namespace這一側是網卡 cali1087c975dd9
,編號是13,這個編號很重要,能夠經過編號知道這個veth pair的另外一端在哪一個pod network namespace。發現 pod nginx-demo-1-7f67f8bdd8-fxptt
的網卡eth0就是veth pair的另外一端,而且編號也是13,:
# 由於該nginx容器沒有ifconfig命令和ip命令,能夠建立 nicolaka/netshoot:latest 容器並加入到該nginx container的namespace中 docker ps -a | grep nginx export CONTAINER_ID=f2ece695e8b9 # 這裏是nginx container的container id # nicolaka/netshoot:latest鏡像地址github.com/nicolaka/netshoot docker run -it --network=container:$CONTAINER_ID --pid=container:$CONTAINER_ID --ipc=container:$CONTAINER_ID nicolaka/netshoot:latest ip -c addr ip -c addr
以上路由表規則和虛擬網卡是calico cni的calico network plugin建立的,而pod ip以及每個node的pod ip cidr網段都是由calico ipam plugin建立管理的,而且這些數據會寫入calico datastore內。
至於calico network plugin和calico ipam plugin具體是如何作的,後續有時間再記錄學習。
無論集羣內cluster_ip:port,仍是集羣外external_ip:port或node_ip:port方式訪問服務,都是會經過kube-proxy進程設置的各類iptables rules後跳轉到對應的pod ip,而後藉助於calico bgp部署方式跳轉到目標pod所在worker節點,並經過該節點的路由表和虛擬網卡,找到對應的那個pod,packet由host network namespace再跳轉到pod network namespace。一直以來的有關service和calico疑問也算是搞明白了。