kubernetes-canal 主机访问 service 异常问题分析
问题描述:
使用 canal 来做 Kubernetes CNI network provider, 在一次使用部署了新版本的 Kubernetes 平台环境上出现从本机访问应用的 service 异常,出现超时,查 iptables 规则发现在 nat 表的 POSTROUTING 链中多出几条规则, POSTROUTING 链中规则如下所示:
iptables -vnL POSTROUTING -t nat
Chain POSTROUTING (policy ACCEPT 33 packets, 2588 bytes)
pkts bytes target prot opt in out source destination
1673K 121M cali-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* cali:O3lYWMrLQYEMJtB5 */
1673K 121M KUBE-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
18 936 RETURN all -- * * !192.168.64.0/20 192.168.66.0/24
378K 26M RETURN all -- * * 192.168.64.0/20 192.168.64.0/20
294 15288 MASQUERADE all -- * * 192.168.64.0/20 !224.0.0.0/4
0 0 RETURN all -- * * !192.168.64.0/20 192.168.64.0/24
1 52 MASQUERADE all -- * * !192.168.64.0/20 192.168.64.0/20
其中 RETURN all -- * * !192.168.64.0/20 192.168.66.0/24
为多出来的规则。
192.168.64.0/20
为集群 POD 网段,192.168.64.0/24
为本节点 PODCidr, 192.168.66.0/24
为 service 对应的 endpoint POD IP 所在节点的 PODCidr。
从本机访问出去的流量不管目的地址是本节点还是跨节点都会匹配 OUTPUT 链,OUTPUT 链中有如下 hook 钩子,用来在 OUTPUT 链中实现 DNAT 功能。
/* Before packet filtering, change destination */
{
.hook = iptable_nat_ipv4_local_fn,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST,
},
从本机访问 service ,OUTPUT 链中 DNAT 将 serivce IP + port 转换为 POD IP + port,从本机请求的报文走 DNAT 有如下函数处理流程:
ip_local_out ----> iptable_nat_ipv4_local_fn -> nf_nat_ipv4_fn (match rule 并完成 DNAT )-> ip_route_me_harder(根据DNAT 后的目的地址重新查路由选择出接口) ---> dst_output
返回流量有如下匹配,即目的 IP 不在集群 POD 网段,即做 SNAT:
iptables -vnL cali-nat-outgoing -t nat
Chain cali-nat-outgoing (1 references)
pkts bytes target prot opt in out source destination
587 46720 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* cali:Wd76s91357Uv7N3v */ match-set cali4-masq-ipam-pools src ! match-set cali4-all-ipam-pools dst
有了上述前提条件描述之后,正常的请求流程应该是:
- 本机请求 service 之后 DNAT 为 POD IP(192.168.66.0), 重新查路由出接口为 flannel.x vxlan 接口,
- POSTROUTING 做 SNAT,修改源 IP 为flannel.x 的 IP, 从 vxlan 接口送出去,调用设备驱动发送函数 vxlan_xmit,
- vxlan 封装后调用 ip_local_output 再重新查路由选择出接口... ...
- 目的节点收到 vxlan 报文并解封装送至上的 POD,并根据目的 IP 为请求端 flannel.x 的 IP 返回,送至本节点vxlan 接口,vxlan封装送到请求节点,
- 请求节点根据连接跟踪做 SNAT, 将 POD IP + port 转换为 service IP + port ... ...
但是如果在步骤 2 中没有做 SNAT,在步骤 4 中直接根据上的 IP 将报文送至请求端,而此时的请求节点不做 SNAT,请求进程收到源 IP 与自己请求的目的 IP 不匹配的报文不处理,且自己预期的报文一直等待不至,直至请求超时。
解决问题
分析了问题产生原因之后,推测问题产生原因为节点规则清理不干净导致(原有的 kubernetes 节点未清理即加入到新 kubernetes 集群,本节点所在的 Kubernetes 有过得重装)
flannel 本身有做清理工作,在 defer 函数中删除创建的 iptables 规则,但是由于对于进程退出信号未捕获,导致未执行 defer 函数规则未做回收。
defer func() {
teardownIPTables(ipt, rules)
}()
作者针对 flannel 回收 iptables 规则的问题在 flannel github 社区提交了 PR,见[1]。
参考链接
[1] flannel recycle unused iptable rules pr. https://github.com/coreos/flannel/issues/986
[2] 洞悉linux下的Netfilter&iptables:网络地址转换原理之DNAT. http://blog.chinaunix.net/uid-23069658-id-3210931.html