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

有了上述前提条件描述之后,正常的请求流程应该是:

  1. 本机请求 service 之后 DNAT 为 POD IP(192.168.66.0), 重新查路由出接口为 flannel.x vxlan 接口,
  2. POSTROUTING 做 SNAT,修改源 IP 为flannel.x 的 IP, 从 vxlan 接口送出去,调用设备驱动发送函数 vxlan_xmit,
  3. vxlan 封装后调用 ip_local_output 再重新查路由选择出接口... ...
  4. 目的节点收到 vxlan 报文并解封装送至上的 POD,并根据目的 IP 为请求端 flannel.x 的 IP 返回,送至本节点vxlan 接口,vxlan封装送到请求节点,
  5. 请求节点根据连接跟踪做 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

posted on 2018-04-26 14:33  mainred  阅读(500)  评论(0编辑  收藏  举报