日期: 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 两种,否则会报错