总体流程先参考下面这张图
(侵删)
ClusterIP模式的流量主要来自于集群内部,所以PREROUTING部分可以先忽略不看,我们从OUTPUT链开始看起。
上面图片简单总结一下就是:所有发出去的请求先经过OUTPUT链,然后被KUBE-SERVICES链拦截处理,一部分不是集群内部的请求(! -s 10.244.0.0/16)会被发送到KUBE-MASK-MARQ链打上标记。再经过KUBE-SVC-链做负载均衡,而后到达真正后端所对应的KUBE-SEP链,KUBE-SEP链会先把源地址是自身IP的报文送去KUBE-MASK-MARQ链打上标记,然后对报文做DNAT转换。最后报文被送给POSTROUTING链,依据报文有没有被KUBE-MASK-MARQ链标记来确定是否对报文做SNAT转换。
下面我们通过查看Iptables规则,一步一步解析里面的详细过程。
先看一下master节点的iptables的OUTPUT链。
[root@master ~]#iptables -t nat -S OUTPUT
-P OUTPUT ACCEPT
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
由上面可以看出,所有经过OUTPUT链的报文需要先经过KUBE-SERVICES链处理。
我们继续查看一下KUBE-SERVICES链。
[root@master homework]#iptables -t nat -S KUBE-SERVICES
-N KUBE-SERVICES
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.109.56.98/32 -p tcp -m comment --comment "default/svc-wp:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.109.56.98/32 -p tcp -m comment --comment "default/svc-wp:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-WF4SMZZJD7RVRLEC
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.97.113.239/32 -p tcp -m comment --comment "default/svc-demo:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.97.113.239/32 -p tcp -m comment --comment "default/svc-demo:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-3K5T3MDPJQWLFXU2
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.98.48.225/32 -p tcp -m comment --comment "default/external-mysql:mysql cluster IP" -m tcp --dport 3306 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.98.48.225/32 -p tcp -m comment --comment "default/external-mysql:mysql cluster IP" -m tcp --dport 3306 -j KUBE-SVC-RMOXSRCCJ27QSW5A
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-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
这里会发现有很多规则,其实,每个Seriver在这条链里都会生成两条规则。
这里过滤一下,查看其中一个Service即可,这里选择svc-demo。
[root@master ~]#iptables -t nat -S KUBE-SERVICES | grep svc-demo
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.97.113.239/32 -p tcp -m comment --comment "default/svc-demo:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.97.113.239/32 -p tcp -m comment --comment "default/svc-demo:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-3K5T3MDPJQWLFXU2
第一条链:如果源地址不是10.244.0.0/16(集群内Pod网段地址),而目标地址是10.97.113.239/32(本Service地址)的,跳转到KUBE-MARK-MASQ链。
第二条链:如果目标地址是10.97.113.239/32(本Service地址)的,跳转到KUBE-SVC-3K5T3MDPJQWLFXU2链。
这里从KUBE-SVC-3K5T3MDPJQWLFXU2链的名称来看,一下就能猜出是HASH的结果,Service文件内容不一样,生成KUBE-SVC链的名称就不一样。
先来查看下KUBE-MARK-MASQ链。
[root@master ~]#iptables -t nat -S KUBE-MARK-MASQ
-N KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
可以看出KUBE-MARK-MASQ链仅仅只是对报文打了个标记,本身明没有继续处理报文,所以打完标记后,又会跳回到原来的KUBE-SERVICES链,继续向下匹配,因此,所有的报文其实都要经过KUBE-SERVICES链的第二条规则,区别在于如果源地址不是集群内Pod网段地址的报文会被KUBE-MARK-MASQ链打上一个16进制的标记。标记的作用是:在最后报文出去的时候经过POSTROUTING链,会依据标记的有无来确定是否对报文做SNAT转换。
然后再来看一下KUBE-SVC-3K5T3MDPJQWLFXU2链。
[root@master ~]#iptables -t nat -S KUBE-SVC-3K5T3MDPJQWLFXU2
-N KUBE-SVC-3K5T3MDPJQWLFXU2
-A KUBE-SVC-3K5T3MDPJQWLFXU2 -m comment --comment "default/svc-demo:http" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-Z5GFFHZQEMOZPZUT
-A KUBE-SVC-3K5T3MDPJQWLFXU2 -m comment --comment "default/svc-demo:http" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-H77X75P5C2QIH26Z
-A KUBE-SVC-3K5T3MDPJQWLFXU2 -m comment --comment "default/svc-demo:http" -j KUBE-SEP-6KXJAK22WDIAXLZY
KUBE-SVC链其实是用来对后端Pod做负载均衡的一个链,从上面可以看到,每条链里都有 -m statistic --mode random --probability 0.33333333349 这样类似的一段,statistic是用来进行报文数量收集的一个模块,这里使用random模式,然后指定权重为0.33333333349。因为我们一共在Service中创建了三个Pod,所以第一个Pod的流量比例为三分之一,当第一个Pod承载的流量到达三分之一后,剩余流量的二分之一由第二个Pod承担,最后剩下的流量由最后一个Pod承担。
通过KUBE-SVC链随机选择后,会跳转到KUBE-SEP链,也就是真正Service后端的Pod对应的链,这里以匹配到第一条链为例,继续查看对应的KUBE-SEP-Z5GFFHZQEMOZPZUT。
[root@master ~]#iptables -t nat -S KUBE-SEP-Z5GFFHZQEMOZPZUT
-N KUBE-SEP-Z5GFFHZQEMOZPZUT
-A KUBE-SEP-Z5GFFHZQEMOZPZUT -s 10.244.1.29/32 -m comment --comment "default/svc-demo:http" -j KUBE-MARK-MASQ
-A KUBE-SEP-Z5GFFHZQEMOZPZUT -p tcp -m comment --comment "default/svc-demo:http" -m tcp -j DNAT --to-destination 10.244.1.29:80
可以看到这里也有两条链。
第一条链:如果源地址是10.244.1.29/32(Pod对应的自身Ip),跳转到KUBE-MARK-MASQ链去打个标记。
第二条链:对匹配到的报文做DNAT转换。
这里解释一下第一条链,当集群内的某个Pod请求自身对应的Service后,结果又被调转回自己。这样的报文无法被处理,所以为了防止这种情况,需要给这种报文也打上标记,最后经由POSTROUTING出去的时候,对其做SNAT转换。
最后,我们查看下POSTROUTING链
[root@master ~]#iptables -t nat -S POSTROUTING
-P POSTROUTING ACCEPT
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 10.244.0.0/16 -d 10.244.0.0/16 -j RETURN
-A POSTROUTING -s 10.244.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE
-A POSTROUTING ! -s 10.244.0.0/16 -d 10.244.0.0/24 -j RETURN
-A POSTROUTING ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE
和OUTPUT链一样,所有经过POSTROUTING的,都要先经过KUBE-POSTROUTING链。
而KUBE-POSTROUTING链就是专门用来对那些被打过标记的请求做SNAT转换的。
这里先看下这里各条链都做了什么:
第一条链:跳转到KUBE-POSTROUTING链。
第二条链:如果源地址是172.17.0.0/16(docker默认网段),目标地址不是docker0网段,做SNAT转换。这里对应的情况是:docker网段容器对外进行通信,所以要做SNAT伪装。
第三条链:如果源地址是10.244.0.0/16(集群内Pod网段地址),目标地址也是10.244.0.0/16(集群内Pod网段地址)的,执行Return动作,而POSTROUTING为主链,Policy为ACCEPT,所以最终执行ACCEPT。这里对应的情况是:pod于pod之间的通信,所以直接放行。
第四条链:如果源地址是10.244.0.0/16(集群内Pod网段地址),目标地址不是10.244.0.0/16(集群内Pod网段地址)的,做SNAT转换。这里对应的情况是:pod对集群外部进行通信,所以要做SNAT伪装。
第五条链:如果源地址不是10.244.0.0/16(集群内Pod网段地址),目标地址是10.244.0.0/24(集群内Pod网段地址)的,执行Return动作,原因同第三条链,所以最终执行ACCEPT。这里对应的情况是:本机的其他服务要访问本机的pod。
第六条链:如果源地址不是10.244.0.0/16(集群内Pod网段地址),目标地址是10.244.0.0/16(集群内Pod网段地址)的,做SNAT转换。这里对应的情况是:本机的其他服务要访问其他节点的pod,要做SNAT伪装。
最后查看下KUBE-POSTROUTING链。
[root@master ~]#iptables -t nat -S KUBE-POSTROUTING
-N 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
第一条链:如果没有被打标记,就跳转回POSTROUTING去。
第二条链:重新打个标记。
第三条链:做动态SNAT地址伪装。
感兴趣的话可以试着抓几个包看看。