Kubernetes學習筆記之kube-proxy service實現原理

image

Overview

咱們生產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 calicodocker

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

externalip-nodeport-service.png

iptables寫自定義規則

當數據包經過node_ip:port或者cluster_ip:port訪問服務時,會在當前worker節點被內核DNAT(Destination Network Address Translation)爲pod ip,反向packet又會被SNAT(Source Network Address Translation)。這裏借用calico官網的很是生動的兩張圖說明 About Kubernetes Servicesapp

cluster-ip service 訪問流程:

kube-proxy-cluster-ip.png

node-port service 訪問流程:

kube-proxy-node-port.png

因爲咱們生產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進過內核時通過五鏈四表流程圖以下:

tables_traverse.jpg

而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:

clusterip-externalip-service.png

而後繼續查找 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

pod_ip.png

總之,無論是經過cluster_ip:port、external_ip:port仍是node_ip:port方式訪問業務服務,packet經過kube-proxy進程自定義的各類chain找到了下一跳pod ip地址。

可是,packet如何知道這個pod ip在哪一個節點呢?

calico寫自定義routes和virtual interface

上文已經說過,咱們部署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

veth_pair.png

以上路由表規則和虛擬網卡是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疑問也算是搞明白了。

參考文獻

About Kubernetes Services

Kube-Proxy 的設計與實現

Kube-Proxy Iptables 的設計與實現

Kube-Proxy IPVS模式的原理與實現

相關文章
相關標籤/搜索