Kubernetes Service详解(概念、原理、流量分析、代码)

作者: ​​liukuan73​​ 


如有再转,请事先征得原创作者允许,自觉在文章醒目位置标注原创作者及出处,并保留结尾二维码等相关信息,以示尊重!!

1.相关概念解读

1.1 Service

Kubernetes中一个应用服务会有一个或多个实例(Pod),每个实例(Pod)的IP地址由网络插件动态随机分配(Pod重启后IP地址会改变)。为屏蔽这些后端实例的动态变化和对多实例的负载均衡,引入了Service这个资源对象,如下所示:

apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
selector:
app: nginx

根据创建Service的​​type​​类型不同,可分成4种模式:

  • ​ClusterIP​​: 默认方式。根据是否生成ClusterIP又可分为普通Service和Headless Service两类:
  • ​普通Service​​:通过为Kubernetes的Service分配一个集群内部可访问的固定虚拟IP(Cluster IP),实现集群内的访问。为最常见的方式。
  • ​Headless Service​​:该服务不会分配Cluster IP,也不通过kube-proxy做反向代理和负载均衡。而是通过DNS提供稳定的网络ID来访问,DNS会将headless service的后端直接解析为podIP列表。主要供StatefulSet使用。
  • ​NodePort​​:除了使用Cluster IP之外,还通过将service的port映射到集群内每个节点的相同一个端口,实现通过nodeIP:nodePort从集群外访问服务。
  • ​LoadBalancer​​:和nodePort类似,不过除了使用一个Cluster IP和nodePort之外,还会向所使用的公有云申请一个负载均衡器(负载均衡器后端映射到各节点的nodePort),实现从集群外通过LB访问服务。
  • ​ExternalName​​:是 Service 的特例。此模式主要面向运行在集群外部的服务,通过它可以将外部服务映射进k8s集群,且具备k8s内服务的一些特征(如具备namespace等属性),来为集群内部提供服务。此模式要求kube-dns的版本为1.7或以上。这种模式和前三种模式(除headless service)最大的不同是重定向依赖的是dns层次,而不是通过kube-proxy。

比如,在service定义中指定externalName的值"​​my.database.example.com​​":


此时k8s集群内的DNS服务会给集群内的服务名 ​​<service-name>.<namespace>.svc.cluster.local​​ 创建一个CNAME记录,其值为指定的"​​my.database.example.com​​"。

当查询k8s集群内的服务​​my-service.prod.svc.cluster.local​​时,集群的 DNS 服务将返回映射的CNAME记录"​​foo.bar.example.com​​"。

备注:

  1. 前3种模式,定义服务的时候通过​​selector​​​指定服务对应的pods,根据pods的地址创建出​​endpoints​​​作为服务后端;​​Endpoints Controller​​​会watch Service以及pod的变化,维护对应的Endpoint信息。kube-proxy根据Service和Endpoint来维护本地的路由规则。当Endpoint发生变化,即Service以及关联的pod发生变化,kube-proxy都会在每个节点上更新iptables,实现一层负载均衡。

    而​​​ExternalName​​​模式则不指定​​selector​​​,相应的也就没有​​port​​​和​​endpoints​​。
  1. ExternalName和ClusterIP中的Headles Service同属于Headless Service的两种情况。Headless Service主要是指不分配Service IP,且不通过kube-proxy做反向代理和负载均衡的服务。

针对以上各发布方式,会涉及一些相应的Port和IP的概念。

1.2 Port

Service中主要涉及三种Port: * `port` 这里的port表示service暴露在clusterIP上的端口,clusterIP:Port 是提供给集群内部访问kubernetes服务的入口。

  • ​targetPort​​ containerPort,targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。
  • ​nodePort​​ nodeIP:nodePort 是提供给从集群外部访问kubernetes服务的入口。

总的来说,port和nodePort都是service的端口,前者暴露给从集群内访问服务,后者暴露给从集群外访问服务。从这两个端口到来的数据都需要经过反向代理​​kube-proxy​​流入后端具体pod的targetPort,从而进入到pod上的容器内。

1.3 IP

使用Service服务还会涉及到几种IP:

  • ​ClusterIP​​ Pod IP 地址是实际存在于某个网卡(可以是虚拟设备)上的,但clusterIP就不一样了,没有网络设备承载这个地址。它是一个虚拟地址,由kube-proxy使用iptables规则重新定向到其本地端口,再均衡到后端Pod。当kube-proxy发现一个新的service后,它会在本地节点打开一个任意端口,创建相应的iptables规则,重定向服务的clusterIP和port到这个新建的端口,开始接受到达这个服务的连接。
  • ​Pod IP​​​ Pod的IP,每个Pod启动时,会自动创建一个镜像为​​gcr.io/google_containers/pause​​的容器,Pod内部其他容器的网络模式使用​​container​​模式,并指定为pause容器的ID,即:​​network_mode: "container:pause容器ID"​​,使得Pod内所有容器共享pause容器的网络,与外部的通信经由此容器代理,pause容器的IP也可以称为Pod IP。
  • ​节点IP​​​ Node-IP,service对象在Cluster IP range池中分配到的IP只能在内部访问,如果服务作为一个应用程序内部的层次,还是很合适的。如果这个service作为前端服务,准备为集群外的客户提供业务,我们就需要给这个服务提供公共IP了。指定service的​​spec.type=NodePort​​,这个类型的service,系统会给它在集群的各个代理节点上分配一个节点级别的端口,能访问到代理节点的客户端都能访问这个端口,从而访问到服务。

2.kube-proxy简介

当service有了port和nodePort之后,就可以对内/外提供服务。那么其具体是通过什么原理来实现的呢?奥妙就在kube-proxy在本地node上创建的iptables规则。

每个Node上都运行着一个kube-proxy进程,kube-proxy是service的具体实现载体,所以,说到service,就不得不提到kube-proxy。

kube-proxy是kubernetes中设置转发规则的组件。kube-proxy通过查询和监听API server中service和endpoint的变化,为每个service都建立了一个服务代理对象,并自动同步。服务代理对象是proxy程序内部的一种数据结构,它包括一个用于监听此服务请求的​​SocketServer​​​,SocketServer的端口是随机选择的一个本地空闲端口。如果存在多个pod实例,kube-proxy同时也会负责负载均衡。而具体的负载均衡策略取决于Round Robin负载均衡算法及service的session会话保持这两个特性。会话保持策略使用的是​​ClientIP​​(将同一个ClientIP的请求转发同一个Endpoint上)。kube-proxy 可以直接运行在物理机上,也可以以 static-pod 或者 daemonset 的方式运行。

kube-proxy 当前支持以下3种实现模式:

  • ​userspace​​:最早的负载均衡方案,它在用户空间监听一个端口,Service的请求先从用户空间进入内核iptables转发到这个端口,然后再回到用户空间,由kube-proxy完成后端endpoints的选择和代理,这样流量会有从用户空间进出内核的过程,效率低,有明显的性能瓶颈。
  • ​iptables​​:目前默认的方案,完全以内核 iptables 的 nat 方式实现 service 负载均衡。该方式在大规模情况下存在一些性能问题:首先,iptables 没有增量更新功能,更新一条规则需要整体 flush,更新时间长,这段时间之内流量会有不同程度的影响;另外,iptables 规则串行匹配,没有预料到 Kubernetes 这种在一个机器上会有很多规则的情况,流量需要经过所有规则的匹配之后再进行转发,对时间和内存都是极大的消耗,尤其在大规模情况下对性能的影响十分明显。
  • ​ipvs​​:为解决 iptables 模式的性能问题,v1.11 新增了 ipvs 模式(v1.8 开始支持测试版,并在 v1.11 GA),采用增量式更新,不会强制进行全量更新,可以保证 service 更新期间连接保持不断开;也不会进行串行的匹配,会通过一定的规则进行哈希 map 映射,很快地映射到对应的规则,不会出现大规模情况下性能线性下降的状况。
  • 后文主要对目前使用较多的iptables模式进行分析。

3.iptables模式下kube-proxy转发规则分析

传统iptables的数据包转发流程如下所示:

3.1 PREROUTING阶段

3.1.1 流量跟踪

由图可知,流量到达防火墙后进入路由表前会先进入PREROUTING链,所以首先对PREROUTING阶段进行分析。

以k8s集群中的一个heapster服务为例:


如图所示,这个heapster服务的模式是NodePort。

1. 首先来看nat表的PREROUTING链

PREROUTIN链只存在于nat表和mangle表中,又由于从代码可知kube-proxy主要操作nat表和filter表,不涉及mangle表,因此,首先看nat表的PREROUTING链:



可见经过cali-PREROUTING后,流量全部进入到了KUBE-SERVICES链。

2. 再来看KUBE-SERVICES链

KUBE-SERVICES链如下:

  • 目的地址是clusterIP的有两条链,对应两种流量:
  • 源地址不是PodIP,目的地址是clusterIP的流量,先经过​​KUBE-MARK-MASQ​​​链,再转发到​​KUBE-SVC-BJM46V3U5RZHCFRZ​
  • 源地址是PodIP,目的地址是clusterIP的流量(集群内部流量)直接转发到​​KUBE-SVC-BJM46V3U5RZHCFRZ​​链
  • 另外,在KUBE-SERVICES链最后还有一条链,对应访问NodePort的流量:
KUBE-NODEPORTS  all  --  anywhere             anywhere             /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
  • 1

3. 接下来跟踪一下以上3种链的流量

<1> 首先跟踪KUBE-MARK-MASQ链:

![-w700](https://ws1.sinaimg.cn/large/006tNbRwgy1fuymn78pdlj30j802djrr.jpg)

​KUBE-MARK-MASQ​​​链给途径的流量打了个​​0x4000​​标记。

<2> 再来看KUBE-SVC-BJM46V3U5RZHCFRZ链:

途径​​KUBE-SVC-BJM46V3U5RZHCFRZ​​​链的流量会以各50%的概率(statistic mode random probability 0.50000000000)转发到两个endpoint后端链​​KUBE-SEP-P7XP4GXFNM4TCRK6​​​和​​KUBE-SEP-HY3BTV7JVLTVQYRP​​中,概率是通过probability后的1.0/float64(n-i)计算出来的,譬如有两个的场景,那么将会是一个0.5和1也就是第一个是50%概率第二个是100%概率,如果是三个的话类似,33%、50%、100%。

<3> 最后看KUBE-NODEPORTS链:

也是先通过​​KUBE-MARK-MASQ​​​链打了个标记​​0x4000​​​,然后转到​​KUBE-SVC-BJM46V3U5RZHCFRZ​​链。

可见经过第三步后,第二步中的三种流量都跳转到了KUBE-SVC-BJM46V3U5RZHCFRZ,其中从集群外部来的流量以及通过nodePort访问的流量添加上了​0x4000​标记。

4. 再来取其中一个endpoint链​​KUBE-SEP-P7XP4GXFNM4TCRK6​​的流量进行跟踪

有两条链,对应两种流量:

  • 源地址是Pod自身IP的流量首先转发到​​KUBE-MARK-MASQ​​​链,然后会打上​​0x4000​​标记,然后再做DNAT。
  • 源地址不是Pod自身IP的流量直接做​​DNAT​​,将目的ip和port转换为对应pod的ip和port。

####**小结** 从上面4步可以看出,经过PREROUTING的分为4种流量:

原始源地址

原始目标地址

是否打​​0x4000​​标记(做SNAT)

是否做DNAT

非PodIP

clusterIP



服务自身PodIP

clusterIP

是(当转发到该pod自身时)


PodIP

clusterIP



*

NodePort



3.1.2 抓包验证

下面通过抓包实验来验证以上的三种流量。

1. 验证第一种流量

**实验环境**

在主机上通过clusterIP访问heapster服务,各IP如下:

  • 访问方的主机地址为:​​10.142.232.150​​;
  • heapster的clusterIP为:​​10.233.60.145​
  • 有两个pod实例,分别位于10.142.232.150和10.142.232.151机器上,两个pod的IP分别是:
  • ​192.168.38.213​
  • ​192.168.68.78​

实验过程

从10.142.232.150这个主机上访问heapster的 ​​clusterIP:port​​ (10.233.60.145:80),在heapster的两个pod实例所在机器分别抓包:

  • 在10.142.232.150上,由于其中一个实例192.168.38.213就在本机,会直接走本机上calico创建的网卡,查询路由表:

可知到走的是cali008939a0a2f网卡,用tcpdump抓这块网卡的数据包。

  • 在10.142.232.151上,抓取目的地址是本机heapster podIP的数据包。

实验结果

访问流量:

10.142.232.150抓包结果:

10.142.232.151抓包结果:

可以看到:

  • 150机器上可以收到源地址为10.142.232.150,目的地址为192.168.38.213(跑在该节点上的heapster pod实例ip)的数据包;
  • 151机器上可以收到源地址为10.142.232.152,目的地址为192.168.68.66(跑在该节点上的heapster pod实例ip)的数据包。

实验表明,当从集群中主机上通过clusterIP访问服务时,都会对数据包做SNAT(转换为该节点ip)和DNAT(转换为podIP),与表中第一种流量一致。

2. 验证第二种流量

实验环境

使用测试应用prometheus,从10.142.232.150主机的prometheus pod(192.168.38.214)中通过clusterIP(10.233.6.92)访问prometheus服务,各IP如下:

  • 使用的prometheus的podIP为:​​192.168.38.214​​;
  • prometheus的clusterIP为:​​10.233.6.92​
  • 有两个prometheus的pod实例,分别位于10.142.232.150和10.142.232.151机器上,两个pod的IP分别是:
  • ​192.168.38.214​
  • ​192.168.68.78​

实验过程

从10.142.232.151主机的prometheus pod(192.168.38.214)中通过clusterIP(10.233.6.92)访问自身服务,在prometheus的两个pod实例所在机器分别用tcpdump抓包:

  • 在10.142.232.150主机抓源地址是本机地址(10.142.232.150),目的地址是本机prometheus podIP(192.168.38.214)的数据包。
  • 在10.142.232.151主机抓源地址是访问方podIP(192.168.38.214),目的地址是本机prometheus podIP(192.168.68.78)的数据包

实验结果

访问流量:

10.142.232.150抓包结果:

10.142.232.151抓包结果:

可以看到:

  • 150机器上可以收到源地址为10.142.232.150,目的地址为192.168.38.214(跑在该节点上的prometheus pod实例ip)的数据包;
  • 151机器上可以收到源地址为192.168.38.214,目的地址为192.168.68.78(跑在该节点上的heapster pod实例ip)的数据包。

实验表明,当从服务自身pod中访问服务的clusterIP时:

  • 当kube-proxy将流量转发到该pod自身的endpoint时,会做SNAT(转换为pod所在主机地址)和DNAT(转换为podIP)
  • 当kube-proxy将流量转发到同一服务的其他endpoint时,仅会做DNAT(转换为podIP)

实验结果与表中第三种流量一致。

3. 验证第三种流量

实验环境

从10.142.232.152主机的network-detect pod中通过clusterIP访问heapster服务,各IP如下:

  • network-detect的podIP为:​​192.168.114.194​​;
  • heapster的clusterIP为:​​10.233.60.145​
  • 有两个pod实例,分别位于10.142.232.150和10.142.232.151机器上,两个pod的IP分别是:
  • ​192.168.38.213​
  • ​192.168.68.78​

实验过程

从192.168.114.194这个pod中访问heapster的clusterIP地址10.233.60.145,在heapster的两个pod实例所在机器分别用tcpdump抓源地址是192.168.114.194的数据包。

实验结果

访问流量:

10.142.232.150抓包结果:

10.142.232.151抓包结果:

可以看到:

  • 150机器上可以收到源地址为192.168.114.194,目的地址为192.168.38.213(跑在该节点上的heapster pod实例ip)的数据包;
  • 151机器上可以收到源地址为192.168.114.194,目的地址为192.168.68.66(跑在该节点上的heapster pod实例ip)的数据包。

实验表明,当源地址为podIP,且不是要访问的服务本身的pod时,仅会对数据包做DNAT(转换为podIP),与表中第三种流量一致。

####4. 验证第四种流量
实验环境

通过NodeIP访问heapster服务,各IP如下:

  • network-detect的podIP为:​​192.168.114.194​​;
  • heapster的clusterIP为:​​10.233.60.145​
  • 有两个pod实例,分别位于10.142.232.150和10.142.232.151机器上,两个pod的IP分别是:
  • ​192.168.38.213​
  • ​192.168.68.78​

实验过程

实验1:
从192.168.114.194这个pod中访问heapster的 ​​​nodeIP:nodePort​​ (10.142.232.152:30082),在heapster的两个pod实例所在机器分别用tcpdump抓目的地址是本机heapster podIP的数据包。

实验2:
从某一节点访问heapster的 ​​​nodeIP:nodePort​​ 10.142.232.152:30082,在heapster的两个pod实例所在机器分别用tcpdump抓源目的地址是本机的heapster podIP的数据包。

实验结果

  1. 实验1结果:
    访问流量:

10.142.232.150抓包结果:

10.142.232.151抓包结果:

  1. 实验2结果:
    访问流量:

10.142.232.150抓包结果:

10.142.232.151抓包结果:

可以看到实验1和实验2结果一致:

  • 150机器上可以收到源地址为10.142.232.152,目的地址为192.168.38.213(跑在该节点上的heapster pod实例ip)的数据包;
  • 151机器上可以收到源地址为10.142.232.152,目的地址为192.168.68.66(跑在该节点上的heapster pod实例ip)的数据包。

实验表明,无论源地址是什么,只要目的地址是通过nodePort访问,都会对数据包做SNAT(转换为该节点ip)和DNAT(转换为podIP),与表中第四种流量一致。

3.2 路由阶段

heapster的两个podIP分别是192.168.68.66和192.168.114.202,根据路由表,分别发送到10.142.232.151和10.142.232.152:

3.3 FORWARD阶段

以其中一条发送到10.142.232.151的流量为例,进行流量跟踪。假如当前流量所在机器是10.142.232.150,由于要发送的151不是本机,所以流量会走filter的FORWARD链:

FORWARD链不对流量做处理,所以流量随后会继续去到POSTROUTING链

3.4 POSTROUTING阶段

在POSTROUTING阶段,对之前打了0x4000标记的流量做了MASQUERADE,将源地址替换为主机网卡地址。

4. kube-proxy核心代码分析

Kubernetes使用iptables来为service做路由和负载均衡,其核心逻辑代码在​​kubernetes/pkg/proxy/iptables/proxier.go​​​中的​​syncProxyRules​​函数内。

1. iptables链定义

这里定义了kube-proxy创建的自定义iptables链的名称。

2. 导流到KUBE-SERVICES链


上述代码做了如下两件事情:

  • 分别在filter和nat表中创建名为​​KUBE-SERVICES​​的自定义链。
  • 调用iptables对filter表的output链、nat表的output链和prerouting链创建了如下三条规则:
  • 将经过filter表的output链的数据包重定向到自定义链​​KUBE-SERVICES​​中
iptables -I OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES


  • 将经过nat表的prerouting链的数据包重定向到自定义链​​KUBE-SERVICES​​中
iptables -t nat -I PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES


  • 将经过nat表的output链的数据包重定向到自定义链​​KUBE-SERVICES​​中
iptables -t nat -I OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

3. 导流到KUBE-POSTROUTING链


上述代码做了如下两件事情:

  • 在表nat表中创建了名为​​KUBE-POSTROUTING​​的自定义链
  • 调用iptables对nat表的POSTROUTING链创建了如下规则:
  • 将经过nat表的postrouting链的数据包重定向到自定义链​​KUBE-POSTROUTING​​中(要做SNAT)
iptables -t nat -I POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING

4. 调用iptables-save获取filter和nat中已有链

上述代码做的事情是:

  • kubernetes调用​​iptables-save​​​命令解析当前node中iptables的filter表和nat表中已经存在的chain,kubernetes会将这些chain存在两个map中(​​existingFilterChains​​​和​​existingNATChains​​​),然后再创建四个protobuf中的buffer(分别是​​filterChains​​​、​​filterRules​​​、​​natChains​​​和​​natRules​​),后续kubernetes会往这四个buffer中写入大量iptables规则,最后再调用iptables-restore写回到当前node的iptables中。

5. 打标记


上述代码做了如下事情:

  • 如果当前node的iptables的filter表和nat表中已经存在名为​​KUBE-SERVICES​​、​​KUBE-NODEPORTS​​、​​KUBE-POSTROUTING​​和​​KUBE-MARK-MASQ​​的自定义链,那就原封不动将它们按照原来的形式(: [:])写入到filterChains和natChains中;如果没有,则以“: [0:0]”的格式写入上述4个chain(即将​​:KUBE-SERVICES – [0:0]​​、​​:KUBE-NODEPORTS – [0:0]​​、​​:KUBE-POSTROUTING – [0:0]​​和​​:KUBE-MARK-MASQ – [0:0]​​写入到filterChains和natChains中,这相当于在filter表和nat表中创建了上述4个自定义链);
  • 对nat表的自定义链​​KUBE-POSTROUTING​​写入如下规则,重定向到自定义链KUBE-MASQUERADE:
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark –mark 0x4000/0x4000 -j MASQUERADE


  • 对nat表的自定义链​​KUBE-MARK-MASQ​​写入如下规则:
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000


这里2和3做的事情的实际含义是kubernetes会让***所有kubernetes集群内部产生的数据包***流经nat表的自定义链​​KUBE-MARK-MASQ​​​,然后在这里kubernetes会对这些数据包打一个标记(0x4000/0x4000),接着在nat的自定义链​​KUBE-POSTROUTING​​中根据上述标记匹配所有的kubernetes集群内部的数据包,匹配的目的是kubernetes会对这些包做***SNAT***操作。

**6. 创建KUBE-SVC-XXX链** 这一步主要做了如下事情:

  • 遍历所有服务,对每一个服务,在nat表中创建名为​​KUBE-SVC-XXXXXXXXXXXXXXXX​​​的自定义链(这里的XXXXXXXXXXXXXXXX是一个16位字符串,kubernetes使用SHA256 算法对“服务名+协议名”生成哈希值,然后通过base32对该哈希值编码,最后取编码值的前16位,kubernetes通过这种方式保证每个服务对应的“KUBE-SVC-XXXXXXXXXXXXXXXX”都不一样)。然后对每个服务,根据服务是否有cluster ip、是否有external ip、是否启用了外部负载均衡服务在nat表的自定义链​​KUBE-SERVICES​​中加入类似如下这样的规则:
  • 所有流经自定义链KUBE-SERVICES的来自于服务“kongxl/test2:8778-tcp”的数据包都会跳转到自定义链KUBE-SVC-XAKTM6QUKQ53BZHS中
-A KUBE-SERVICES -d 172.30.32.92/32 -p tcp -m comment --comment "kongxl/test2:8778-tcp cluster IP" -m tcp --dport 8778 -j KUBE-SVC-XAKTM6QUKQ53BZHS

  • 在遍历每一个服务的过程中,还会检查该服务是否启用了nodeports,如果启用了且该服务有对应的endpoints,则会在nat表的自定义链​​KUBE-NODEPORTS​​中加入如下两条规则:
  • 所有流经自定义链​​KUBE-NODEPORTS​​​的来自于服务“ym/echo-app-nodeport”的数据包都会跳转到自定义链​​KUBE-MARK-MASQ​​中,即kubernetes会对来自上述服务的这些数据包打一个标记(0x4000/0x4000)
-A KUBE-NODEPORTS -p tcp -m comment --comment "ym/echo-app-nodeport:" -m tcp --dport 30001 -j KUBE-MARK-MASQ


  • 所有流经自定义链​​KUBE-NODEPORTS​​​的来自于服务“ym/echo-app-nodeport”的数据包都会跳转到自定义链​​KUBE-SVC-LQ6G5YLNLUHHZYH5​​中
-A KUBE-NODEPORTS -p tcp -m comment --comment "ym/echo-app-nodeport:" -m tcp --dport 30001 -j KUBE-SVC-LQ6G5YLNLUHHZYH5


  • 在遍历每一个服务的过程中,还会检查该服务是否启用了nodeports,如果启用了但该服务没有对应的endpoints,则会在filter表的自定义链​​KUBE-SERVICES​​中加入如下规则:
  • 如果service没有配置endpoints,那么kubernetes这里会REJECT所有数据包,这意味着没有endpoints的service是无法被访问到的
-A KUBE-SERVICES -d 172.30.32.92/32 -p tcp -m comment --comment "kongxl/test2:8080-tcp has no endpoints" -m tcp --dport 8080 -j REJECT


  • 在遍历每一个服务的过程中,对每一个服务,如果这个服务有对应的endpoints,那么在nat表中创建名为​​KUBE-SEP-XXXXXXXXXXXXXXXX​​的自定义链。然后对每个endpoint,如果该服务配置了​​session affinity​​,则在nat表的该service对应的自定义链​​KUBE-SVC-XXXXXXXXXXXXXXXX​​中加入类似如下这样的规则:
  • 所有流经自定义链KUBE-SVC-ECTPRXTXBM34L34Q的来自于服务​​default/docker-registry:5000-tcp​​​的数据包都会跳转到自定义链​​KUBE-SEP-LPCU5ERTNL2YBWXG​​​中,且会在一段时间内保持​​session affinity​​​,保持时间为180秒(这里kubernetes用​​-m recent –rcheck –seconds 180 –reap​​实现了会话保持)
-A KUBE-SVC-ECTPRXTXBM34L34Q -m comment --comment "default/docker-registry:5000-tcp" -m recent --rcheck --seconds 180 --reap --name KUBE-SEP-LPCU5ERTNL2YBWXG --mask 255.255.255.255 --rsource -j KUBE-SEP-LPCU5ERTNL2YBWXG


  • 在遍历每一个服务的过程中,对每一个服务,如果这个服务有对应的endpoints,且没有配置​​session affinity​​,则在nat表的该service对应的自定义链​​KUBE-SVC-XXXXXXXXXXXXXXXX​​中加入类似如下这样的规则(如果该服务对应的endpoints大于等于2,则还会加入负载均衡规则):
  • 所有流经自定义链KUBE-SVC-VX5XTMYNLWGXYEL4的来自于服务“ym/echo-app”的数据//包既可能会跳转到自定义链​​KUBE-SEP-27OZWHQEIJ47W5ZW​​,也可能会跳转到自定义链​​KUBE-SEP-AA6LE4U3XA6T2EZB​​,这里kubernetes用​​-m statistic --mode random //--probability 0.50000000000​​实现了对该服务访问的负载均衡
-A KUBE-SVC-VX5XTMYNLWGXYEL4 -m comment --comment "ym/echo-app:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-27OZWHQEIJ47W5ZW
-A KUBE-SVC-VX5XTMYNLWGXYEL4 -m comment --comment "ym/echo-app:" -j KUBE-SEP-AA6LE4U3XA6T2EZB
  • 最后,在遍历每一个服务的过程中,对每一个服务的endpoints,在nat表的该endpoint对应的自定义链​​KUBE-SEP-XXXXXXXXXXXXXXXX​​中加入如下规则,实现到该服务最终目的地的***DNAT***:
  • 服务“ym/echo-app”有两个endpoints,之前kubernetes已经对该服务做了负载均衡,所以这里一共会产生4条跳转规则
-A KUBE-SEP-27OZWHQEIJ47W5ZW -s 10.1.0.8/32 -m comment –comment "ym/echo-app:" -j KUBE-MARK-MASQ
-A KUBE-SEP-27OZWHQEIJ47W5ZW -p tcp -m comment –comment "ym/echo-app:" -m tcp -j DNAT –to-destination 10.1.0.8:8080
-A KUBE-SEP-AA6LE4U3XA6T2EZB -s 10.1.1.4/32 -m comment –comment "ym/echo-app:" -j KUBE-MARK-MASQ
-A KUBE-SEP-AA6LE4U3XA6T2EZB -p tcp -m comment –comment "ym/echo-app:" -m tcp -j DNAT –to-destination 10.1.1.4:8080

7. 删除失效链


上述代码做的事情是:

  • 删掉当前节点中已经不存在的服务所对应的​​KUBE-SVC-XXXXXXXXXXXXXXXX​​链和​​KUBE-SEP- XXXXXXXXXXXXXXXX​​链;
  • 向nat表的自定义链​​KUBE-SERVICES​​中写入如下这样规则:
  • 将目的地址是本地的数据包跳转到自定义链KUBE-NODEPORTS中
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: th"

8. 写回iptables


上述代码做的事情是:合并已经被写入了大量规则的四个protobuf中的buffer(分别是filterChains、filterRules、natChains和natRules),然后调用​​iptables-restore​​写回到当前node的iptables中。


更多精彩内容,请订阅本人微信公众号:K8SPractice

Kubernetes Service详解(概念、原理、流量分析、代码)_IP