K8S – 手把手分析service生成的iptables规则

最近遇到一点k8s的网络问题,顺便仔细看了一下k8s的iptables规则,在此记录一下相关思路。

查看service

我司使用的是ucloud云,使用的是ucloud提供的LoadBalancer Service。

下面是service的详细信息:

该Service背后对应了2个POD,也就是Endpoints所示。

我们首先需要搞清楚LoadBalancer Service是如何工作的,才能进一步分析iptables规则:

首先,该service仍旧会分配一个K8S CIDR网段的Cluster iP,这里就是IP:172.17.185.22。

其次,该service仍旧会分配一个nodeport,也就是在所有node上开放一个唯一端口对K8S集群外服务,这里端口就是NodePort:39746。

最后,会在k8s集群外部署一个proxy服务,其反向代理到K8S集群各个node上的nodeport端口,这个proxy的服务地址是:LoadBalancer Ingress 17.42.162.216。

集群内调用service

集群内POD调用service,通常是使用Cluster IP,也就是172.17.185.22。

但因为该Service是LoadBalancer类型,所以其实也可以调用集群外LB(IP:10.42.162.216)反向代理到nodeport,同样可以访问到集群内的Service。

所以,我们在观察k8s service iptables规则的时候,可以分析2种访问路径:

  • 集群内发起调用,通过cluster ip访问到service。
  • 集群外发起调用,通过nodeport访问到service。

现在我们登录到任意node,因为k8s会把完整的iptables规则下发到每一个node。

cluster ip工作原理

调用方是某个node上面的某个POD,它向cluster ip 172.17.185.22发起调用,所以走的是OUTPUT链。

iptables要做的事情,就是识别dst(目标IP)是172.17.185.22的包,把dst改写成某一个Endpoints的IP地址(这个service背后对应2个endpoints)。

我们从output链开始看:

直接交给KUBE-SERVICES链了,继续看:

我对结果进行了截取,只保留了属于该Service的规则,你会发现K8S生成的规则都有清晰的注释,还是很好阅读的。

上述3条规则是顺序执行的:

  • 第1条规则匹配发往Cluster IP 172.17.185.22的流量,跳转到了KUBE-MARK-MASQ链进一步处理,其作用就是打了一个MARK,稍后展开说明。
  • 第2条规则匹配发往Cluster IP 172.17.185.22的流量,跳转到了KUBE-SVC-G3OM5DSD2HHDMN6U链进一步处理,稍后展开说明。
  • 第3条规则匹配发往集群外LB IP的10.42.162.216的流量,跳转到了

    KUBE-FW-G3OM5DSD2HHDMN6U链进一步处理,稍后展开说明。

打MARK这一步还不是很重要,所以我们先说第2条规则,显然第2条规则对发往Cluster IP的流量要做dst IP改写到具体的一个Endpoints上面,我们看一下:

一共2条规则,每条规则的应用概率是0.5,其实就是负载均衡流量到这2条链的意思,而这2条链分别对应各自endpoints的DNAT规则:

这样就实现了在调用方完成负载均衡逻辑,也就是Cluster IP的工作原理了。

流量经过本机路由表决定从eth0 LAN出去(ucloud是underlay扁平网络):

在流量离开本机之前会经过POSTROUTING链:

其实直接就跳转到了KUBE-POSTROUTING,然后匹配打过0x4000 MARK的流量,将其做SNAT转换,而这个MARK其实就是之前没说的KUBE-MARK-MASQ做的事情:

也就是说,当流量离开本机时,src IP会被修改为node的IP,而不是发出流量的POD IP了。

最后还有一个KUBE-FW-G3OM5DSD2HHDMN6U链没有讲,从本机发往LB IP的流量要做啥事情呢?

其实也是让流量直接发往具体某个Endpoints,就别真的发往LB了,这样才能获得最佳的延迟:

其中第2条规则就是复用了之前我们看到的负载均衡链。

nodeport工作原理

nodeport就是在每个node上开放同一个端口,只要集群外访问node上的该端口就可以访问到对应的service。

因此,iptables此时的目标是dst IP是node自己,并且dst port是nodeport端口的流量,将其直接改写到service的某个endpoints。

其实该匹配规则也是写在PREROUTING链上的,具体在KUBE-SERVICES链上,在之前的结果里面我没有贴出来而已:

处理nodeport的规则在末尾,只要dst IP是node本机IP的话(–dst-type LOCAL),就跳转到KUBE-NODEPORTS做进一步判定:

我只保留了与该service相关的2条规则:

  • 第1条匹配dst port如果是39746,那么就打mark。
  • 第2条匹配dst port如果是39746,那么就跳到负载均衡链做DNAT改写。
  • 否则就不做任何处理,流量继续交给后续链条处理。

路由表

因为使用ucloud的CNI插件,所以网络是一个underlay模式,Node和POD的IP段是直通的。

当被调用的node收到流量的时候,经过PREROUTING链什么也不做,经过路由表后可以判定转发给哪个虚拟网卡,从而将流量FORWARD到具体的docker容器内。

最后

关于K8S Service的分析就到这里,我觉得比较重要的一点认识是:

K8S集群内调用LB IP并不会真的发给LB,仍旧是直接走了集群内的负载均衡。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~