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来拜访更高效,也更容易落地生产。
然而,traffic packet是怎么依据集群外worker节点的node_ip:port或者集群内cluster_ip:port拜访形式找到pod ip的?
并且,咱们生产k8s应用calico来作为cni插件,采纳 Peered with TOR (Top of Rack) routers
形式部署,每一个worker node和其置顶交换机建设bgp peer配对,置顶替换机会持续和下层外围交换机建设bgp peer配对,这样能够保障pod ip在公司内网能够间接被拜访。
然而,traffic packet晓得了pod ip,又是怎么跳到pod的呢?
以上问题能够归并为一个问题:数据包是怎么一步步跳转到pod的?很长时间以来,始终在思考这些问题。
原理解析
实际上答案很简略:拜访业务服务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 :
minikube start --network-plugin=cni --cni=calico# 或者minikube start --network-plugin=cnikubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
而后部署个业务pod,这里应用nginx为例,正本数为2,并创立ClusterIP Service with ExternalIPs和NodePort Service:
---apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-demo-1 labels: app: nginx-demo-1spec: 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: v1kind: Servicemetadata: name: nginx-demo-1spec: 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: v1kind: Servicemetadata: name: nginx-demo-2spec: selector: app: nginx-demo-1 ports: - port: 8089 targetPort: 80 type: NodePort---
部署实现后,就能够通过 ExternalIP ClusterIP Service或者NodePort Service两种形式拜访业务服务:
iptables写自定义规定
当数据包通过node_ip:port或者cluster_ip:port拜访服务时,会在以后worker节点被内核DNAT(Destination Network Address Translation)为pod ip,反向packet又会被SNAT(Source Network Address Translation)。这里借用calico官网的十分活泼的两张图阐明 About Kubernetes Services :
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-SERVICESsudo iptables -v -n -t nat -L KUBE-SERVICESsudo 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-JKOCBQALQGD3X3RTsudo iptables -v -n -t nat -L KUBE-SEP-CRT5ID3374EWFAWNsudo iptables -v -n -t nat -L KUBE-SVC-6JCCLZMUQSW27LLDsudo 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写自定义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 nginxexport CONTAINER_ID=f2ece695e8b9 # 这里是nginx container的container id# nicolaka/netshoot:latest镜像地址github.com/nicolaka/netshootdocker run -it --network=container:$CONTAINER_ID --pid=container:$CONTAINER_ID --ipc=container:$CONTAINER_ID nicolaka/netshoot:latest ip -c addrip -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疑难也算是搞明确了。
参考文献
About Kubernetes Services
Kube-Proxy 的设计与实现
Kube-Proxy Iptables 的设计与实现
Kube-Proxy IPVS模式的原理与实现