基于Iptables方式的Kubernetes Service流量分析

前言

Kubernetes Service是Kubernetes中提供的一种访问Pod的方式,是一组Pod的服务抽象,本文从网络流量的角度,分析了基于Iptables方式的Kubernetes Service的实现原理。

Kubernetes Service简介

在Kubernetes中,一般Pod的IP地址由网络插件动态分配,IP地址会随着Pod的重启而重新分配,因此为了保证对Pod的正常访问,引入了Kubernetes Service概念。Service是一组Pod的服务抽象,对Service进行的访问请求会被分发到后端的Pod上,相当于一个简易的负载均衡。

一般来说,Kubernetes Service有三种访问方式:

ClusterIP: 分配一个集群内的虚拟IP,默认类型。这种方式提供的ClusterIP只能在集群内访问。

NodePort:在每个Node上提供一个相同静态端口,作为服务的端口映射。可以使用NodeIP:NodePort的方式从集群外进行服务的访问。

LoadBalancer:使用外部的负载均衡器来提供服务的访问功能。

另外,还有一种ExternalName类型,通过将服务映射到某个域名来提供访问,这种方式需要使用1.7版本及以上的kube-dns组件,一般使用较少。

Kubernetes Service由Kube-Proxy实现,Kube-Proxy运行在每个节点上,有三种运行模式:

userspace:在用户空间通过kube-proxy实现一个负载均衡,进行流量转发。这种方式虽然稳定,但效率不高。

iptables:完全采用iptables来实现负载均衡,使用kube-proxy维护iptables中的规则。效率比userspace高,也是现在最常用的方式。

ipvs:使用IPVS来提供负载均衡的功能,kube-proxy维护ipvs rules。这种模式不仅性能高效,并且能提供更多的负载均衡算法以供选择,但现阶段只是beta版本。

基于Iptables方式的Kubernetes Service流量分析

根据上面的介绍,下面以最常见的Iptables模式为例,分析Kubernetes Service的流量。

主要分析两种常见的访问方式:ClusterIP方式和NodePort方式。

ClusterIP

使用Kubernetes 1.8作为测试环境,同时使用calico作为网络插件。分别在两个Node上新建两个Pod,Node1的IP为10.142.21.215;Node2的IP为10.142.21.216。Node1上创建Pod1,IP为10.222.119.113;Node2上创建Pod2,IP为10.222.75.80,并且为其创建ClusterIP类型的Service,Cluster IP为10.233.48.22,端口为3306。

Node1上的Pod通过ClusterIP访问Node2上的Pod,其流量的转发过程如下:

1

Pod1发出访问请求的数据包,此时数据包的源地址为Pod1的IP,目标地址为ClusterIP

src 10.222.119.113 dst 10.233.48.22 dst port 3306

2

数据包到宿主机的命名空间后,会经过iptables规则,里面包含了由kube-proxy进行维护一系列规则。然后会命中部分规则,进行DNAT,使用后端Pod2的IP作为目标地址,变成src 10.222.119.113 dst 10.222.75.80。命中的iptable规则如下:

KUBE-SERVICES链引用了一系列以KUBE-SVC开头的链,每一条KUBE-SVC-XXX对应着一个Service对象,在KUBE-SERVICES中,通过访问的ClusterIP(目标地址)与端口来决定进入哪条KUBE-SVC-XXX链。

KUBE-SVC-XXX链中包含了一系列的以KUBE-SEP开头的链,而每一条KUBE-SEP-XXX对应着后端的一个Pod(Service Endpoint),当存在多条KUBE-SEP-XXX链时,会等概率的随机命中,最终在命中的KUBE-SEP-XXX链中进行DNAT,将ClusterIP转换为对应的PodIP。

3

数据包经过DNAT后,变成了普通的Pod到Pod之间的直接访问,因此下面就是Calico的转发过程了。数据包路由到另一个主机,然后经过协议栈,转发到服务的后端Pod2上,后端Pod2收到的数据包为

src 10.222.119.113 dst 10.222.75.80

4

Pod2处理请求,返回响应

src 10.222.75.80 dst 10.222.119.113

5

返回的包经过Calico的路由,到达目标主机,以src 10.222.75.80 dst 10.222.119.113的形式进入iptables。

6

由于iptable会对DNAT操作进行记录,此时会对数据包进行un-DNAT,将数据包还原成src 10.233.48.22 dst 10.222.119.113,然后根据Calico的路由信息,转发到Pod1的 Interface上,完成通信。

可以看到ClusterIP方式比较简单,仅仅是对数据包做了一次DNAT,然后再返回时再进行一次un-DNAT。

NodePort

仍然使用上面的环境,但会为Node2上的Pod2创建NodePort类型的Service,nodeport为32034。另外,引入Node3,地址为10.142.21.140,通过Node3IP:NodePort的方式访问服务,这样分析起来会更加透彻。

1

Pod1通过Node3IP:NodePort的方式访问Service

src 10.222.119.113 dst 10.142.21.140 dst port 32034

2

数据包进入宿主机后,在这个地方会根据Calico的IPPool的nat-outgoing参数不同有些许不同,当nat-outgoing为true时,会命中下面的calico Iptables规则,进行MASQUERADE,其中cali4-masq-ipam-pools与cali4-all-ipam-pools,是集群里Calico ippool的范围。数据包会被转换为src 10.142.21.215 dst 10.142.21.140 dst port 32034。

当nat-outgoing为false时,数据包保持原样,从Node1上发送出去。

这里的MASQUERADE并不会对后序的Kubernetes Service产生什么影响,为排除网络插件的因素,下面以nat-outgoing为false的情况进行分析,另一种情况分析类似。

3

Node3(10.142.21.140)收收到数据包

src 10.222.119.113 dst 10.142.21.140 dst port 32034

4

经过Node3(10.142.21.140)上的Kubernetes Service的iptables规则,数据包会进行MASQUERADE与DNAT,变为src 10.142.21.140 dst 10.222.75.80 dst port 3306,规则如下:

上面说到,KUBE-SERVICE链包含了所有的KUBE-SVC-XXX链,而在KUBE-SERVICE链最后,其实还有引用了一条KUBE-NODEPORTS链,用来处理NodePort类型的数据包。而KUBE-NODEPORT链中包含了NodePort类型的kubernetes Service对应的KUBE-SVC-XXX链,以dport(也就是NodePort)作为命中条件。

KUBE-MARK-MASQ链作用是对数据包标记,在KUBE-POSTROUTING链中再进行MASQUERADE,将源地址改为宿主机地址。

从KUBE-NODEPORTS链进入KUBE-SVC-M3G5B4GFQP6YTETH之后,就是和上面ClusterIP一样的DNAT流程,将目标地址换成服务后端的Pod IP。

5

通过Calico网络,服务后端Pod收到请求包,形式如下

src 10.142.21.140 dst 10.222.75.80

6

服务后端Pod处理请求,返回的数据包形式为

src 10.222.75.80 dst 10.142.21.140

7

通过Calico网络,数据包发送到10.142.21.140主机(Node3),形式为

src 10.222.75.80 dst 10.142.21.140

8

10.142.21.140主机(Node3)上的iptables对数据包进行un-DNAT和un-SNAT,变成src 10.142.21.140 dst 10.222.119.113

9

接下来就是通过Calico网络,将数据包发送给客户端Pod,完成通信。

访问自身服务

如果按照上面的流程,Pod在访问自身服务的时候会出现问题:Pod发出数据包,形式为PodIP到ClusterIP,经过宿主机Iptable,转变成PodIP到PodIP,由于是访问自身服务,则可能出现两个PodIP相同的情况,也就是自己访问自己。那么Pod在收到这种数据包后,就容易导致响应数据包无法传递出Pod,也就无法进行un-DNAT的情况,这样Pod收不到ClusterIP到PodIP的响应包,就无法正常通信。

因此在KUBE-SEP-XXX链中也引用了KUBE-MARK-MASQ链,比如上面的KUBE-SEP-F32HTB6TOZJNMJ7Y链,会有如下规则的KUBE-MARK-MASQ链。其中10.222.75.80是KUBE-SEP-F32HTB6TOZJNMJ7Y链所代表的Pod的IP,也就是说只有当源地址是Pod本身的时候才会命中,即访问自身服务的情况。命中后,除了进行DNAT外,还会对发出的访问报文进行MASQUERADE,这样就能解决上面的问题了。

END

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180626B1ERXX00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券