kube-proxy 实现原理(iptables方式)
Service是k8s中的一个概念,是对一组pod的服务抽象,主要负责将请求分发给对应的pod,完成反向代理和负载均衡(负载均衡一般采用Round Robin算法)。
kube-proxy来具体实现Service。
kube-proxy实现转发的方式有两种方式:Userspace,iptables。k8s1.2版本后默认用iptables的方式,实现一系列的包过滤、转发、nat操作。(1.8版本后增加了IPVS方式)
kube-proxy监听 Kubernetes Master 增加和删除 Service 以及 Endpoint 的消息。对于每一个 Service,Kube Proxy 创建相应的 IPtables 规则,发送到 Service Cluster IP 的流量转发到 Service 后端提供服务的 Pod 的相应端口上。
在创建Service对象时,会为其分配一个虚拟IP(VIP),称为Cluster IP,Cluster IP在外部无法访问,主要作用是供内部的pod之间通信使用。确切来说,Cluster IP 只是 IPtables 中的规则,并不对应到一个任何网络设备。(个人认为Cluster IP只是为了实现负载均衡。)
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d2h
[root@master ~]# ping 10.96.0.1
PING 10.96.0.1 (10.96.0.1) 56(84) bytes of data.
From 10.0.30.10 icmp_seq=1 Time to live exceeded
From 10.0.30.10 icmp_seq=2 Time to live exceeded
From 10.0.30.10 icmp_seq=3 Time to live exceeded
From 10.0.30.10 icmp_seq=5 Time to live exceeded
From 10.0.30.10 icmp_seq=9 Time to live exceeded
^C
--- 10.96.0.1 ping statistics ---
10 packets transmitted, 0 received, +5 errors, 100% packet loss, time 9011ms
具体来说,对Service的访问主要分为:
- 内部从pod到service,进而转发到具体的pod;
- 外部从node port到service,进而转发到具体的pod。
测试环境
web服务。
首先定义一个提供web服务的RC,由2个tomcat容器副本组成,每个容器通过containerPort设置提供服务的端口号为8080:
[root@master ~]# vi webapp-RC.yaml
apiVersion: v1
kind: ReplicationController
metadata:
name: webapp
spec:
replicas: 2
template:
metadata:
name: webapp
labels:
app: webapp
spec:
containers:
- name: webapp
image: tomcat
ports:
- containerPort: 8080
[root@master ~]# kubectl create -f webapp-RC.yaml
replicationcontroller "webapp" created
然后创建一个service,绑定这两个pod:
[root@master ~]# vi webapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
ports:
- port: 8081
targetPort: 8080
selector:
app: webapp
[root@master ~]# kubectl create -f webapp-svc.yaml
service "webapp" created
内部访问service
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
...
webapp ClusterIP 10.107.237.254 <none> 8081/TCP 34m
[root@master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webapp-74bxv 1/1 Running 0 46m 10.244.2.7 node1 <none> <none>
webapp-ln5nh 1/1 Running 0 46m 10.244.1.6 node2 <none> <none>
webapp的Cluster IP为10.107.237.254
,pod的ip为10.244.1.6
、10.224.2.7
。
node节点(node ip: 10.0.0.201
)访问service(cluster ip: 10.107.237.254
)。
OUTPUT链
首先流量会到达OUTPUT
链:
[root@master ~]# iptables-save -t nat | grep -- '-A OUTPUT'
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
然后该链跳转到KUBE-SERVICES
子链里:
[root@master ~]# iptables-save -t nat | grep -- '-A KUBE-SERVICES'
...
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.107.237.254/32 -p tcp -m comment --comment "default/webapp cluster IP" -m tcp --dport 8081 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.107.237.254/32 -p tcp -m comment --comment "default/webapp cluster IP" -m tcp --dport 8081 -j KUBE-SVC-2IRACUALRELARSND
有两条规则:
- 第一条负责打标记MARK
0x4000/0x4000
,后面会用到这个标记; - 第二条规则跳到
KUBE-SVC-2IRACUALRELARSND
子链。
KUBE-SVC-2IRACUALRELARSND
子链的规则:
[root@master ~]# iptables-save -t nat | grep -- '-A KUBE-SVC-2IRACUALRELARSND'
-A KUBE-SVC-2IRACUALRELARSND -m comment --comment "default/webapp" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-EA2C3KXU4TFQDFBN
-A KUBE-SVC-2IRACUALRELARSND -m comment --comment "default/webapp" -j KUBE-SEP-ELK6VM6EZTBKIB4X
发现有两条子链:
- 1/2的概率跳转到子链
KUBE-SEP-EA2C3KXU4TFQDFBN
; - 剩下1/2概率跳转到子链
KUBE-SEP-ELK6VM6EZTBKIB4X
。
首先看KUBE-SEP-EA2C3KXU4TFQDFBN
子链:
[root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-SEP-EA2C3KXU4TFQDFBN'
-A KUBE-SEP-EA2C3KXU4TFQDFBN -s 10.244.1.6/32 -m comment --comment "default/webapp" -j KUBE-MARK-MASQ
-A KUBE-SEP-EA2C3KXU4TFQDFBN -p tcp -m comment --comment "default/webapp" -m tcp -j DNAT --to-destination 10.244.1.6:8080
可见这条规则的目的是做了一次DNAT,DNAT目标为其中一个Endpoint,即Pod服务。
而另一个子链KUBE-SEP-ELK6VM6EZTBKIB4X
:
[root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-SEP-ELK6VM6EZTBKIB4X'
-A KUBE-SEP-ELK6VM6EZTBKIB4X -s 10.244.2.7/32 -m comment --comment "default/webapp" -j KUBE-MARK-MASQ
-A KUBE-SEP-ELK6VM6EZTBKIB4X -p tcp -m comment --comment "default/webapp" -m tcp -j DNAT --to-destination 10.244.2.7:8080
可见,KUBE-SVC-2IRACUALRELARSND
的功能就是按照等概率的原则DNAT到其中的一个endpoint。
即:
10.0.0.201:xxxx(node) --> 10.107.237.254:8081
↓ DNAT
10.0.0.201:xxxx(node) --> 10.244.1.6:8080(例如pod1)
完成DNAT后,接着到POSTROUTING
链。
POSTROUTING链
[root@node1 ~]# iptables-save -t nat | grep -- '-A POSTROUTING'
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
KUBE-POSTROUTING
:
[root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-POSTROUTING'
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
这两条规则只做一件事就是只要标记了0x4000/0x4000
的包就一律做MASQUERADE(SNAT),由于10.244.1.2默认是从flannel.1转发出去的,因此会把源IP改为flannel.1的IP10.244.0.0
。
10.0.0.201:xxxx(node) --> 10.107.237.254:8081
↓ DNAT
10.0.0.201:xxxx(node) --> 10.244.1.6:8080(例如pod1)
↓ SAT
10.244.0.0:xxxx --> 10.244.1.6:8080(例如pod1)
外部访问service
重新创建NodePort类型的service:
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
ports:
- port: 8081 # Cluster IP虚拟端口
targetPort: 8080 # 服务端口
nodePort: 30080 # NodePort端口
type: NodePort
selector:
app: webapp
外部访问node节点的30080端口,转发到Cluster IP的8080端口,进一步转发到容器的8081端口。
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webapp NodePort 10.96.187.75 <none> 8081:30080/TCP 13m
假设10.0.0.1访问10.0.0.201:30080。
PREROUTING链
首先到达PREROUTING
链:
[root@node1 ~]# iptables-save -t nat | grep -- '-A PREROUTING'
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
KUBE-SERVICES
:
[root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-SERVICES'
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
PREROUTING的规则非常简单,凡是发给自己的包,则交给子链KUBE-NODEPORTS
处理。注意前面省略了判断ClusterIP的部分规则。
[root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-NODEPORTS'
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/webapp" -m tcp --dport 30080 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/webapp" -m tcp --dport 30080 -j KUBE-SVC-2IRACUALRELARSND
这个规则首先给包打上标记0x4000/0x4000
,然后交给子链KUBE-SVC-2IRACUALRELARSND
处理,KUBE-SVC-2IRACUALRELARSND
就是按照概率均等的原则DNAT到其中一个Endpoint IP,即Pod IP,假设为10.244.1.6。
10.0.0.1:xxxx(node) --> 10.0.0.201:30080
↓ DNAT
10.0.0.201:xxxx(node) --> 10.244.1.6:8080(例如pod1)
此时发现10.244.1.6不是自己的IP,于是经过路由判断目标为10.244.1.6需要从flannel.1发出去。
FORWARD链
接着到了FORWARD
链:
[root@node1 ~]# iptables-save -t filter | grep -- '-A FORWARD'
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
KUBE-FORWARD
:
[root@node1 ~]# iptables-save -t filter | grep -- '-A KUBE-FORWARD'
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
FORWARD表在这里只是判断下,只允许打了标记0x4000/0x4000
的包才允许转发。
最后来到POSTROUTING
链,这里和ClusterIP就完全一样了,在KUBE-POSTROUTING
中做一次MASQUERADE
(SNAT),最后结果:
10.0.0.1:xxxx(node) --> 10.0.0.201:30080
↓ DNAT
10.0.0.201:xxxx(node) --> 10.244.1.6:8080(例如pod1)
↓ SNAT
10.244.0.0:xxxx --> 10.244.1.6:8080(例如pod1)
补充
service的三种端口
- port:service暴露在cluster ip上的端口,是提供给集群内部客户访问service的入口。
- nodePort:nodePort是k8s提供给集群外部客户访问service入口的一种方式,nodePort 是提供给集群外部客户访问service的入口。
- targetPort:targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。
具体k8s的flannel网络参考:https://www.jianshu.com/p/2f91907b2aba
具体iptables链参考:
当我们创建pod时,仅仅是创建了pod,要为其创建rc(ReplicationController),他才会有固定的副本,然后为其创建service,集群内部才能访问该pod,使用 NodePort 或者 LoadBalancer 类型的 Service,外部网络也可以访问该pod;每个 service 会创建出来一个虚拟 ip,通过访问 vip:port 就能获取服务的内容(内部访问,因为这是一个vip,外部无法访问的)
参考
https://www.xiexianbin.cn/kubernetes/2016-07-25-kubernetes-proxy/index.html
https://zhuanlan.zhihu.com/p/94418251?from_voters_page=true
http://www.voidcn.com/article/p-fzkcdsqq-bqu.html
https://blog.csdn.net/liyingke112/article/details/76022267
https://www.jianshu.com/p/f28534fe507a
https://www.jianshu.com/p/2f91907b2aba
https://www.jianshu.com/p/3beb4336e251