作者简介
姚灿武,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 svc
NAME                        TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGE
default-nginx-lb-db9bdca5   LoadBalancer   10.43.113.238                  80:32586/TCP   4h32mharvester-host:/home/rancher # kubectl get endpointslices
NAME                       ADDRESSTYPE   PORTS   ENDPOINTS       AGE
default-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.238
curl: (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.178
tcp      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=1
conntrack 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_writable
0xffff9e90c12a8d00    [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_writable
l4proto_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_slow
br_nf_pre_routing [br_netfilter]
br_handle_frame [bridge]
__netif_receive_skb_core
__netif_receive_skb_one_core
process_backlog
__napi_poll
net_rx_action
__softirqentry_text_start
run_ksoftirqd
smpboot_thread_fn
kthread
ret_from_fork# stack of the function inet_proto_csum_replace4
0xffff9e90c12a8d00    [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_replace4
l4proto_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_slow
br_nf_pre_routing [br_netfilter]
br_handle_frame [bridge]
__netif_receive_skb_core
__netif_receive_skb_one_core
process_backlog
__napi_poll
net_rx_action
__softirqentry_text_start
run_ksoftirqd
smpboot_thread_fn
kthread
ret_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 string
The 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

记一个 Harvester SNAT 案例相关推荐

  1. 记一个自认为写得有点复杂的sql语句

    记一个自认为写得有点复杂的sql语句,含义是跨3张表的select: select table_name,column_name,data_type,data_length,data_scale fr ...

  2. 记一个简单的保护if 的sh脚本

    记一个简单的保护if 的sh脚本 真是坑爹,就下面的sh,竟然也写了很久! if [ `pwd` != '/usr/xx/bin/tomcat' ] thenecho "rstall is ...

  3. 一个多线程死锁案例,如何避免及解决死锁问题

    转载自 一个多线程死锁案例,如何避免及解决死锁问题 多线程死锁在java程序员笔试的时候时有遇见,死锁概念在之前的文章有介绍,大家应该也都明白它的概念,不清楚的去翻看历史文章吧. 下面是一个多线程死锁 ...

  4. zemax光学设计超级学习手册_穿越十年的一个ZEMAX光学设计案例

    目前超过两千人的光学与光学设计方面的微信公众号,欢迎您! 穿越十年的一个ZEMAX光学设计案例 作者:窗台小绿萝 CAD,这个词已经深入到学习.工作很多年,翻译过来就是Computer Aided D ...

  5. 一个Python爬虫案例让你看清Python2和3之间的区别

    随着Python越来越受欢迎了,也越来越多的人加入到这个大家庭当中,有很多的初学者都会有一个疑问,我学习Python是学习2版本的呢还是3版本的呢?Python2和Python3又有什么区别呢? 我想 ...

  6. 记一个 DataBindings遇到的坑,当ComboBox同时绑定DataSource与DataBindings的时候,绑定的元素一定要同名...

    记一个 DataBindings遇到的坑,当ComboBox同时绑定DataSource与DataBindings的时候,绑定的元素一定要同名 原文:记一个 DataBindings遇到的坑,当Com ...

  7. 一个自动化测试的案例之记事狗微博篇

    一个自动化测试的案例之记事狗微博篇 前言: 针对于我这个学计算机的孩子,总是希望能够增强动手能力,在机子小小感冒时,能够自己帮它治愈,但现实情况是我总是把它搞成重病患者,貌似每次的动手能力,都让我的机 ...

  8. 记一个简单Android图书阅读器的制作过程

    记一个简单图书阅读器的制作过程 微澜 2018/9/27 qq:9611153 从有个想法,到到一个可用程序,断断续续几个月,花上不少的功夫,即便是简单的程序一个人写下来也是很难的.越写越是发现,想要 ...

  9. 数据挖掘时功能和一个聚类分析应用案例

    数据挖掘时功能和一个聚类分析应用案例 数据挖掘的常用方法和数据挖掘的重要功能(出自MBA智库百科).当然,横看成岭侧成峰,这些常用方法和重要功能也许并不完全正确或完整.除此以外,笔者尝试学习了SMAR ...

  10. 数据挖掘的常用方法、功能和一个聚类分析应用案例

    在今天的博文中,笔者整理了数据挖掘的常用方法和数据挖掘的重要功能(出自MBA智库百科).当然,横看成岭侧成峰,这些常用方法和重要功能也许并不完全正确或完整.除此以外,笔者尝试学习了SMARTBI公司中 ...

最新文章

  1. Unity游戏开发大师班
  2. 自学python推荐书籍2019-2019最全Python入门学习路线,绝
  3. 二维码类库--phpqrcode使用简介
  4. [转]ReiserFS与ext3的比较
  5. oracle导库需要删除原始库,导入dmp文件时,需要删除原有ORACLE数据库实例
  6. jsp九大内置对象和四种属性范围介绍
  7. SVM(三)—Kernels(核函数)
  8. 正则表达式(三)操作符的运算优先级、全部符号的解释
  9. 数据清洗挑战Day1 | 手把手教你处理数据集中的缺失值
  10. 华为手机序列号前三位_华为Nova2s手机序列号前六位是TPG4C1是什么意思
  11. Python根据拼音对中文排序
  12. qos 流控功能_QOS流量控制管理详解!
  13. 山西大同大学教务处学生端--送给学弟,学妹的礼物,可在PC端,手机端操作
  14. php版本大屏幕,PHP现场抽奖大屏幕互动系统
  15. 眼底视网膜血管增强方法(四)Frangi滤波
  16. 帆软部署服务器linux,部署集成
  17. kali linux 获取ip_Kali Linux常用服务配置教程获取IP地址
  18. 32个企业软件门类名称和释义
  19. 提升计算机英语的阅读,计算机专业英语教程阅读
  20. 企业运用人工智能的案例_百度企业网盘渠道伙伴招募如火如荼

热门文章

  1. window11 网络突然就用不了,系统更新网络就用不了了,DNS服务器可能不可用
  2. 哞力无法挡 516争抢最后一群斐讯“天天牛”
  3. python开发桌面便签_python制作一个桌面便签软件
  4. Java 生成微信群头像 九宫格头像
  5. 酷狗小程开发,项目创建(Vue)
  6. Spark学习笔记1(初始spark
  7. 397. 整数替换【我亦无他唯手熟尔】
  8. leetcode——第714题——可以买卖多次股票(每次有手续费)
  9. “三权分立”模型的概述
  10. webpack项目运用(一)打包压缩css文件