日期: April 17, 2022

这里主要分析iptables模式的kube-proxy,所以涉及很多iptables规则


关于涉及的自定义iptables链

  • KUBE-SERVICES: 访问集群内服务的CLusterIP数据包入口,根据匹配到的目标ip+port将数据包分发到相应的KUBE-SVC-xxx链上。一个Service对应一条规则。由OUTPUT链调用
  • KUBE-NODEPORTS: 用来匹配nodeport端口号,并将规则转发到KUBE-SVC-xxx。一个NodePort类型的Service一条。在KUBE-SERVICES链的最后被调用
  • KUBE-SVC-xxx:相当于是负载均衡,将流量利用random模块均分到KUBE-SEP-xxx链上
  • KUBE-SEP-xxx:通过dnat规则将连接的目的地址和端口号做dnat,从Service的ClusterIP或者NodePort转换为后端的pod ip
  • KUBE-MARK-MASQ: 使用mark命令,对数据包设置标记0x4000/0x4000。在KUBE-POSTROUTING链上有MARK标记的数据包进行一次MASQUERADE,即SNAT,会用节点ip替换源ip地址

当在宿主机上访问pod的service的clusterIP时经历了什么

本机请求ClusterIP的数据包会经过iptables的链:OUTPUT POSTROUTING

1.iptables -nvL OUTPUT -t nat

查看output  nat 转发

发现转发至KUBE-SERVICES链

2.iptables -nvL KUBE-SERVICES -t nat

查看KUBE-SERVICES 转发

发现一个svc对应一个KUBE-SVC-XXX 链

可以根据clusterip  进行匹配

3.iptables -nvL KUBE-SVC-XXX   -t nat

发现可能多条链或者单条链(根据pod数量而定)

K8s  svc  就是由此实现负载均衡的,所以如果想改变clsterIP负载均衡的机制,也许可以在这条链上下功夫?🤔

4.iptables -nvL KUBE-SEP-XXX  -t nat

发现

KUBE-MARK-MASQ

DNAT

在dnat操作之前为对数据包执行打标签操作。KUBE-MARK-MASQ 自定义链为对数据包打标记的自定义规则

DNAT则将目标clusterip转为PodIP ,实现DNAT转换

至此DNAT转换完成


开始SNAT转换

5.iptables -nvL POSTROUTING -t nat

发现 KUBE-POSTROUTING 链

6.iptables -nvL KUBE-POSTROUTING -t nat

发现 MASQUERADE

MASQUERADE指令的操作实际上为SNAT操作

即从本机访问service clusterip的数据包,在output链上经过了dnat操作,在postrouting链上经过了snat操作后,最终会发往目标pod。pod在处理完请求后,回的数据包最终会经过nat的逆过程返回到本机

当外部访问nodeport时经历了什么

从外部访问本机的nodeport数据包会经过iptables的链:PREROUTING FORWARD POSTROUTING

nodeport都是被外部访问的情况,入口位于PREROUTING链上

1.iptables -nvL PREROUTING -t nat

发现KUBE-SERVICES

2.iptables -nvL KUBE-SERVICES -t nat

最后一条为KUBE-NODEPORTS自定义链,负责NodePort转发

3.iptables -nvL KUBE-NODEPORTS -t nat

发现一个nodeport端口对应两条链

KUBE-MARK-MASQ

KUBE-SVC-XXX

其中KUBE-MARK-MASQ链只有一条规则,即打上0x4000的标签。

4.iptables -nvL KUBE-SVC-XXX -t nat

发现KUBE-SEP-XXX

5.iptables -nvL KUBE-SEP-XXX -t nat

发现

KUBE-MARK-MASQ

DNAT

转发至pod对应的端口

至此完成DNAT


6.经过了PREROUTING链后,接下来会判断目的ip地址不是本机的ip地址(这里意思是你访问的kube-proxy节点上经过iptables解析出来的pod是否在这台机器),接下来会经过FORWARD链。在FORWARD链上,仅做了一件事情,就是将前面打了0x4000的数据包允许转发。

iptables -nvL  KUBE-FORWARD

可以看到规则

7.跟clusterip一样,会在POSTROUTING阶段匹配mark为0x4000/0x4000的数据包,并进行一次MASQUERADE转换,将ip包替换为宿主上的ip地址。

加入这里不做MASQUERADE,流量发到目的的pod后,pod回包时目的地址为发起端的源地址,而发起端的源地址很可能是在k8s集群外部的,此时pod发回的包是不能回到发起端的。NodePort跟ClusterIP的最大不同就是NodePort的发起端很可能是在集群外部的,从而这里必须做一层SNAT转换

在上述分析中,访问NodePort类型的Service会经过snat,从而服务端的pod不能获取到正确的客户端ip。可以设置Service的spec.externalTrafficPolicy为Local,(那么这时访问nodeport时,ip地址就应该是这个pod所在的节点的IP,这样pod就可以获取到源访问地址?我没试过还不确定)此时iptables规则只会将ip包转发给运行在这台宿主机上的pod,而不需要经过snat。pod回包时,直接回复源ip地址即可,此时源ip地址是可达的,因为源ip地址跟宿主机是可达的。如果所在的宿主机上没有pod,那么此时流量就不可以转发

Tips

关于svc的spec.externalTrafficPolicy策略

在k8s的Service对象(申明一条访问通道)中,有一个“externalTrafficPolicy”字段可以设置。有2个值可以设置:Cluster或者Local

1)Cluster表示:流量可以转发到其他节点上的Pod

2)Local表示:流量只发给本机的Pod

选择Cluster

注:默认模式,Kube-proxy不管容器实例在哪,公平转发

Kube-proxy转发时会替换掉报文的源IP。即:容器收的报文,源IP地址会被替换为上一个kube-proxy转发节点的

选择Local

这种情况下,只转发给本机的容器,绝不跨节点转发

**Kube-proxy转发时会保留源IP,**即:容器收到的报文,看到源IP地址还是用户的

注:这种模式下的Service类型只能为外部流量,即:LoadBalancer 或者 NodePort 两种,否则会报错