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 的相应端口上。

微信截图_20210328152515.png

在创建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

20170724161410421.png

具体来说,对Service的访问主要分为:

  1. 内部从pod到service,进而转发到具体的pod;
  2. 外部从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.610.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

有两条规则:

  1. 第一条负责打标记MARK0x4000/0x4000,后面会用到这个标记;
  2. 第二条规则跳到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链参考:
iptables.png


当我们创建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

posted @ 2021-03-28 14:02  _STAyy  阅读(583)  评论(0编辑  收藏  举报