写点什么

记一个 Harvester SNAT 案例

作者:Rancher
  • 2022 年 5 月 24 日
  • 本文字数:3834 字

    阅读完需:约 13 分钟

记一个 Harvester SNAT 案例

作者简介

姚灿武,SUSE Rancher 研发工程师,拥有 6 年云计算领域经验,热衷开源技术,在云原生相关技术领域拥有丰富的开发和实践经验。


Harvester 通过 Multus 扩展了标准的 Kubernetes CNI 网络,可以让虚拟机拥有基于 Bridge Vlan 技术分配的虚拟网卡。本文源于一次问题排查实践,以解决复杂网络情况下产生的通信问题。


本文使用的 Harvester 版本为 v1.0.0

问题描述

Harvester 利用 Kubernetes service 为虚拟机中的服务提供负载均衡。在这个方案中,负载均衡后端地址是<虚拟机的 IP 地址:端口>,被记录在与 Kubernetes service 对应的 endpointslice 中。示意图表示如下:



下面是本文所使用的例子对应的 service 与 endpointslice。


harvester-host:/home/rancher # kubectl get svcNAME                        TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGEdefault-nginx-lb-db9bdca5   LoadBalancer   10.43.113.238                  80:32586/TCP   4h32m
harvester-host:/home/rancher # kubectl get endpointslicesNAME ADDRESSTYPE PORTS ENDPOINTS AGEdefault-nginx-lb-db9bdca5 IPv4 80 172.16.178.178 4h33m
复制代码


但是,我们发现,当 VM 使用 Harvester VLAN 网络,并且发起请求的客户端(如 curl)与 VM 同在一个 Harvester 主机时,通过负载均衡(本例中是访问 service clusterIP)的请求失败了。结果如下:


harvester-host:/home/rancher # curl 10.43.113.238curl: (7) Failed to connect to 10.53.202.161 port 80: Connection timed out
复制代码


整个网络拓扑表示如下:


分析过程

明确流量路径

一般来说,对于网络问题,我们首先需要明确流量的转发路径。在 Kubernetes 中,当请求 service clusterIP 时,Kube-proxy 会将请求目的地址转为后端服务的地址。在我们的案例中,后端地址是 172.16.178.178:80。因为目的地址172.16.178.178 是在 VLAN 178 里,请求和响应都需要经过外部网关。因此,我们可以在网络拓扑中标记上流量转发路径。


抓包

不通过负载均衡,在各个 VLAN 网络中直接访问后端服务是通的,我们可以首先排除是外部交换机以及网关引发的问题。为了定位到是在哪个转发环节发生的问题,我们需要对 Harvester 主机流量路径上的各个网络接口进行抓包。我们将抓包结果整理后简化表示如下:



从抓包结果可以看出,VM 网卡正常接收到了 TCP SYN 网络包,并且响应发送了 SYN/ACK 报文。但是,当 SYN/ACK 报文被网桥从 veth2db2ad9c 转发到 eth1后,目的端口发生了改变。因为该目的端口与 SYN 报文的源端口不匹配,SYN/ACK 报文被丢弃导致 TCP 三次握手失败。由 conntrack 表中的记录可以看出,修改后的目的端口是负载均衡之前原方向请求的源端口。


harvester-host:/home/rancher # conntrack -L | grep 172.16.178.178tcp      6 56 SYN_RECV src=172.16.0.57 dst=10.43.113.238 sport=38944 dport=80 src=172.16.178.178 dst=172.16.0.57 sport=80 dport=10598 mark=0 use=1conntrack v1.4.5 (conntrack-tools): 262 flow entries have been shown.
复制代码


因此,我们合理猜测在网桥转发 SYN/ACK 报文的过程中发生了一次网络地址转换(NAT)。


根据 Kubernetes 官方文档描述,Kubernetes 默认开启了net.bridge.bridge-nf-call-iptables设置。


if the plugin connects containers to a Linux bridge, the plugin must set the net/bridge/bridge-nf-call-iptables sysctl to 1 to ensure that the iptables proxy functions correctly.
复制代码


该设置可以控制当报文经过网桥时,原来作用于三层网络的 iptables 规则是否在此二层转发过程中生效。在 Kubernetes 中,默认生效。也就是说,Kube-proxy 所设置的 iptables 规则包括一些 NAT 规则都会在网桥转发过程中起作用。


当我们设置 net.bridge.bridge-nf-call-iptables为 0 时,我们发现请求成功了。所以,毫无疑问,kube-proxy 的 iptables 规则导致了这个问题。但是,到目前为止,我们仍然无法定位具体是哪一条规则,或者说我们还没有定位 netfilter 中哪个环节导致了该问题。我们需要深入研究分析 netfilter 才能找到问题背后的根本原因。

深入分析 netfilter NAT

在网络分析工具 pwru 的帮助下,通过观察目的端口的变化以及打印出内核函数调用栈,我们可以确定 NAT 发生在 pre-routing 链上。


0xffff9e90d1c55200        [<empty>]      skb_ensure_writable   16542012456363 netns=4026531992 mark=0x0 ifindex=10 proto=8 mtu=1500 len=60 172.16.178.178:80->172.16.0.57:10598(tcp)0xffff9e90d1c55200        [<empty>] inet_proto_csum_replace4   16542012502740 netns=4026531992 mark=0x0 ifindex=10 proto=8 mtu=1500 len=60 172.16.178.178:80->172.16.0.57:38944(tcp)
复制代码


# stack of the function skb_ensure_writable0xffff9e90c12a8d00    [ksoftirqd/5]      skb_ensure_writable   16558140061379 netns=4026531992 mark=0x0 ifindex=10 proto=8 mtu=1500 len=60 172.16.178.178:80->172.16.0.57:10598(tcp)skb_ensure_writablel4proto_manip_pkt [nf_nat]nf_nat_ipv4_manip_pkt [nf_nat]nf_nat_manip_pkt [nf_nat]nf_nat_ipv4_pre_routing [nf_nat]nf_hook_slowbr_nf_pre_routing [br_netfilter]br_handle_frame [bridge]__netif_receive_skb_core__netif_receive_skb_one_coreprocess_backlog__napi_pollnet_rx_action__softirqentry_text_startrun_ksoftirqdsmpboot_thread_fnkthreadret_from_fork
# stack of the function inet_proto_csum_replace40xffff9e90c12a8d00 [ksoftirqd/5] inet_proto_csum_replace4 16558140095491 netns=4026531992 mark=0x0 ifindex=10 proto=8 mtu=1500 len=60 172.16.178.178:80->172.16.0.57:38944(tcp)inet_proto_csum_replace4l4proto_manip_pkt [nf_nat]nf_nat_ipv4_manip_pkt [nf_nat]nf_nat_manip_pkt [nf_nat]nf_nat_ipv4_pre_routing [nf_nat]nf_hook_slowbr_nf_pre_routing [br_netfilter]br_handle_frame [bridge]__netif_receive_skb_core__netif_receive_skb_one_coreprocess_backlog__napi_pollnet_rx_action__softirqentry_text_startrun_ksoftirqdsmpboot_thread_fnkthreadret_from_fork
复制代码


通过 Linux 内核中的 netfilter 源码以及 netfilter 在三层协议上的结构图,我们可以看到,NF_IP_PRI_NAT_DST参数表明,pre-routing 链上的 NAT 钩子只会改变报文的目的地址,即在 pre-routing 链上只会发生 DNAT 或者 de-SNAT。


static const struct nf_hook_ops nf_nat_ipv4_ops[] = { {  .hook  = ipt_do_table,  .pf  = NFPROTO_IPV4,  .hooknum = NF_INET_PRE_ROUTING,  .priority = NF_IP_PRI_NAT_DST, }, ...}
复制代码



在我们的案例中,SYN/ACK 是回包,所以应当是发生了 de-SNAT。Kube-proxy 在 post-routing 链上添加了 SNAT iptables 规则。请求 service 的报文经过时会在 output 链上打上标记,打上标记的报文在 post-chain 上会发生 SNAT。回包时,报文会在 pre-routing 链上发生 de-SNAT。


-A KUBE-SVC-2CMXP7HKUVJN7L6M ! -s 10.42.0.0/16 -d 10.43.220.155/32 -p tcp -m comment --comment "default/nginx cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
复制代码


-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
复制代码


在 Kubernetes 中,通常客户端请求与后端服务实例在同一个主机节点上,请求不会经过 KUBE-MART-MASQ 链,也就不会发生 SNAT 和 de-SNAT。但是不知道为什么在这个案例中发生了。通过查阅 Kubernetes 官方文档,确定 kube-proxy 的配置项 cluster-cidr 是罪魁祸首。


--cluster-cidr stringThe CIDR range of pods in the cluster. When configured, traffic sent to a Service cluster IP from outside this range will be masqueraded and traffic sent from pods to an external LoadBalancer IP will be directed to the respective cluster IP instead
复制代码


cluster-cidr 设置为空,请求成功。

解决方案

从上面的分析中可以知道,解决问题的关键是避免发生不必要的 SNAT。有两个可选的解决方案。


  1. 因为 Harvester 所使用的 canal CNI 不依赖 bridge netfilter,我们可以直接关闭 net.bridge.bridge-nf-call-iptables

  2. 将 kube-proxy cluster-cidr 配置为空。

参考文献

  • [kube-proxy]

https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/


  • [network-plugin-requirements]

https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/#network-plugin-requirements


  • [Net.bridge.bridge-nf-call and sysctl.conf]

https://wiki.libvirt.org/page/Net.bridge.bridge-nf-call_and_sysctl.conf


  • [IPTABLES 的连接跟踪与 NAT 分析]

https://segmentfault.com/a/1190000041259845


  • [Deep Dive kube-proxy with iptables mode]

https://serenafeng.github.io/2020/03/26/kube-proxy-in-iptables-mode/


  • [Service cluster ip How to disable snat]

https://github.com/projectcalico/calico/issues/2999

发布于: 刚刚阅读数: 7
用户头像

Rancher

关注

牛牛本牛ヾ(◍°∇°◍)ノ゙ 2019.09.17 加入

SUSE Rancher是一个开源的企业级Kubernetes管理平台,实现了Kubernetes集群在混合云+本地数据中心的集中部署与管理。目前Rancher在全球拥有超过三亿的核心镜像下载量。

评论

发布
暂无评论
记一个 Harvester SNAT 案例_Kubernetes_Rancher_InfoQ写作社区