当前的集群中给2个apache pod注册了一个service,这个地址是10.152.183.151,

 

在ubuntu的pod中测试这个ip是可以通信的,it work来源于本机的pod,多访问几次发现会随机的把请求定向到本机的pod或者远端的pod。

当前节点上所有pod、网桥、flannel都是10.1.62这个网段,

 

10.211.55.12是对外的ip地址,

 

当前node上是没有这个service的10.152.183这个网段的,

 

这个ip地址也是ping不通的,

 

查看apiserver的配置信息,发现配置的这个网段和apache service ip的网段是同一个,这个网段包括ip都是由k8s虚拟的进行控制,这个ip不是实际存在的。

 

k8s通过iptables实现service,iptables分为表和列,表代表可以完成特定功能的一组步骤,列是每个步骤;

为了实现service,k8s用到了iptables中的nat表和filter表,基本上都是nat表在工作,

图中橘黄色是和nat表相关的步骤,首先外部报文请求进来之后,如果访问本机进程,会先走PREROUTING步骤,它属于nat表 ,它来决定需不需要做DNAT(NAT是网络地址转换,D是目标),即要不要做目标地址转换,这个地方可以自行决定,如果转换了之后,不在本机的话,可以直接走FORWARD这个步骤,然后到nat的POSTROUTING就直接出去了;如果和当前的进程相关,就会进入filter表的INPUT这个步骤,当本机的进程有报文输出的话,会先走nat这张表的OUTPUT步骤,在这个步骤中可能再去做一个DNAT来决定这个报文的目的地址是原封不动的还是要做一些改动,然后再经过filter的OUTPUT链再走POSTROUTING就出去了,在POSTROUTING的时候还可以做一个SNAT(源网络地址的NAT),决定这个报文出去的ip地址是不是需要变化,源网络地址就是别人可以看到的这个地址,表示报文从哪里来的。

上图中有2个地方可以做DNAT,有一个地方可以做SNAT。

 

当在curl这个service的时候,这个service有一个ip地址,

 

151这个ip地址实际上是不存在的,当pod使用curl访问这个ip地址的时候,

 

它就会到OUTPUT这个地方来,OUTPUT这个地方,k8s在iptables里面插入了一些指令,当它发现是指向某一个service的时候,通过DNAT去把这个报文的目标ip转换成实际提供这个服务的pod ip,然后再把这个报文路由出去;同理当从外部访问某一个service的时候,它也通过第一个DNAT(图中左上角的那个),把service这个不存在的ip替换掉,替换成一个具体的pod的ip,然后再经过路由,路由到相应的pod上,要么是本地要么是别的机器,再来去回应这个service的请求。

看下curl经历的过程

 

在pod中的本地进程curl对应着图中的这一链路,

 

 

看下nat这张表的OUTPUT这个链,在OUTPUT链中有2个target,和service最相关的就是kube-service,

kube-service也是一个链(步骤),这个是OUTPUT链的下一步,也就是说k8s会在iptables插入新的链来解决这个问题,在OUTPUT这个标准的步骤中,所有的源和目的地址都会进入这个步骤,

 

这个是k8s插入的新的链(新的步骤), 查看这个链的内容,

 

 

都跟service相关,

 

筛选和apache service相关的,

创建的这个apache service其实就是k8s在kube-service这个链中插入了2条与apache service相关的信息,这两条又是新的链,其中KUBE-MARK-MASQ的主要作用是给ip的这个报文加个标记,加标记之后,未来会在PREROUTING做snat用。

 

KUBE-SVCxxxx的内容是2个KUBE-SEPxxxx,SEP就是service endpoint,有50%的概率走第一个endpoint,

 

另外50%走第二个endpoint,

 

查看这个SEP,发现有一个IP地址10.1.62.174,这个endpoint做了一个dnat,也就是说它在报文当中把apache的service ip改掉了,本来是service目标ip地址改成了实际的pod的ip地址10.1.62.174;

 

另外的一个endpoint也是一个dnat,也是将apache service的ip改成了10.1.71.22即另外一个pod的ip;

 


通过iptables里的这个kube-service的规则的路由概率,把访问apache service的ip包,平均的分发到了2个不同的pod上,

 

pod用了flannel的地址空间,就会通过flannel的网络设备,如果需要的话,就会转发到另外的node上,从而就路由到了最终的pod上。

 

这个其实就是做了一个mark,打了一个16进制的标签,这个标签会在POSTROUTING步骤中做一个snat

 

看下nat表的POSTROUTING,

 

这里有一个KUBE-POSTROUTING,这也是k8s自己插入的,如果没有打那个16进制的标签,就走这个

 

,否则就走这个

 

MASQUERADE是一个snat,只不过它是动态的基于当前使用的是哪个网络接口对外分发的报文,就把这个报文的源地址改成网络设备的地址。

为什么要做这个事情?

因为在这个ubuntu的pod中,curl这个apache的service的时候,这个service的目标地址通过刚才的介绍已经被修改了,但它的源地址依然是这个pod ip的地址,

 

实际上这个源地址是通过eth虚拟网络设备分配的,这个地址在k8s集群中是不可见的,在pod中可见,所以就需要把这个报文的源地址改成在集群中可见的地址,比如说出口网络设备的地址;

filter和service的流程是类似的,当然k8s的service还有loadbalancer、nodeport机制,k8s都是通过在iptables中动态的插入规则来实现的。