热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

记一个HarvesterSNAT案例

记,一个,harvest

作者简介
姚灿武,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 4h32m harvester-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 [] 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 [] 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


推荐阅读
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • express工程中的json调用方法
    本文介绍了在express工程中如何调用json数据,包括建立app.js文件、创建数据接口以及获取全部数据和typeid为1的数据的方法。 ... [详细]
  • 本文介绍了如何在Jquery中通过元素的样式值获取元素,并将其赋值给一个变量。提供了5种解决方案供参考。 ... [详细]
  • 本文介绍了DataTables插件的官方网站以及其基本特点和使用方法,包括分页处理、数据过滤、数据排序、数据类型检测、列宽度自动适应、CSS定制样式、隐藏列等功能。同时还介绍了其易用性、可扩展性和灵活性,以及国际化和动态创建表格的功能。此外,还提供了参数初始化和延迟加载的示例代码。 ... [详细]
  • 抽空写了一个ICON图标的转换程序
    抽空写了一个ICON图标的转换程序,支持png\jpe\bmp格式到ico的转换。具体的程序就在下面,如果看的人多,过两天再把思路写一下。 ... [详细]
  • 现在比较流行使用静态网站生成器来搭建网站,博客产品着陆页微信转发页面等。但每次都需要对服务器进行配置,也是一个重复但繁琐的工作。使用DockerWeb,只需5分钟就能搭建一个基于D ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
author-avatar
NE丰胸茶urghx
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有