Kubernetes网络自学系列 | 前方高能:Kubernetes网络故障定位指南
Posted COCOgsta
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes网络自学系列 | 前方高能:Kubernetes网络故障定位指南相关的知识,希望对你有一定的参考价值。
素材来源:《Kubernetes网络权威指南》
一边学习一边整理内容,并与大家分享,侵权即删,谢谢支持!
附上汇总贴:Kubernetes网络自学系列 | 汇总_COCOgsta的博客-CSDN博客
3.9 前方高能:Kubernetes网络故障定位指南
网络可以说是Kubernetes部署和使用过程中最容易出问题的,通过前面的理论讲解我们已经掌握了Kubernetes网络的基本知识,本节将重点介绍Kubernetes网络故障定位的实战经验。
3.9.1 IP转发和桥接
Kubernetes网络利用Linux内核Netfilter模块设置低级别的集群IP负载均衡,除了iptables和IPVS(前面已经详细解析过,这里不再赘述),还需要用到两个关键的模块:IP转发(IP forward)和桥接。
1.IP转发
IP转发是一种内核态设置,允许将一个接口的流量转发到另一个接口,该配置是Linux内核将流量从容器路由到外部所必需的。有时,该项设置可能会被安全团队运行的定期安全扫描重置,或者没有配置为重启后生效。在这种情况下,就会出现网络访问失败的情况,例如,访问Pod服务连接超时:
Tcpdump可以显示发送了大量重复的SYN数据包,但没有收到ACK。
那么,该如何诊断呢?请看下面的诊断方法:
修复也很简单,如下所示:
2. 桥接
Kubernetes通过bridge-netfilter配置使iptables规则应用在Linux网桥上。该配置对Linux内核进行宿主机和容器之间数据包的地址转换是必需的。否则,Pod进行外部服务网络请求时会出现目标主机不可达或者连接拒绝等错误(host unreachable或connection refused)。
那么,如何诊断呢?请看下面的命令:
如何修复呢?请看下面的命令:
3.9.2 Pod CIDR冲突
Kubernetes有时会为容器和容器之间的通信建立一层特殊的overlay网络(取决于你使用什么样的网络插件)。使用隔离的Pod网络容器可以获得唯一的IP并且可以避免集群上的端口冲突,而当Pod子网和主机网络出现冲突时就会出现问题。Pod和Pod之间通信会因为路由问题被中断:
那么,该如何诊断呢?首先,查看Pod分配的IP地址:
然后,将主机IP范围与API Server中指定的子网进行比较,如果出现了同网段的IP,则很大概率会出现冲突。
解决方法就是仔细检查你的网络设置,确保你正在使用的网络、VLAN或VPC之间不会有重叠。如果有冲突,则可以在CNI插件或Kubelet的pod-cidr参数中指定IP地址范围,避免冲突。
3.9.3 hairpin
hairpin的含义用一句话表述就是“自己访问自己”。例如,Pod有时无法通过Service IP访问自己,这就有可能是hairpin的配置问题了。通常,当Kube-proxy以iptables或IPVS模式运行,并且Pod与桥接网络连接时,就会发生这种情况。Kubelet的启动参数提供了一个--hairpin-mode的标志,支持的值有hairpin-veth和promiscuous-bridge。检查Kubelet的日志也能看到以下日志行,例如:
用户需要检查Kubelet的--hairpin-mode是否被设置为一个合法的值。
--hairpin-mode被Kubelet设置成hairpin-veth并且生效后,底层其实是在修改宿主机操作系统/sys/devices/virtual/net目录下设备文件hairpin_mode的值,可以通过以下命令确认是否修改成功:
如果没有修改成功,则请确保Kubelet拥有宿主机/sys的读写权限。
如果有效的--hairpin-mode被设置成promiscuous-bridge,则请确保Kubelet拥有操作宿主机Linux网桥的权限,具体说就是要把网桥配置成混杂模式,例如:
3.9.4 查看Pod IP地址
严格意义上讲,查看Pod IP地址只能说是一个需求,而非“问题”,是定位Kubernetes网络问题的基本手段。因此,我们将花费一点篇幅介绍如何在Kubernetes中查看Pod的IP地址。通俗地说,查看Kubernetes Pod IP有两个办法,分别是从外面看和进到里面看。从外面看又分为Kubernetes API和docker命令。进到里面看就是进到容器内使用ip或ifconfig等命令查看。
1. 从外面看Pod IP地址之Kubernetes API
Kubernetes是知道集群中所有Pod的IP信息的,也提供相应的查询接口,因此kubectl get pod或者kubectl describe pod就能显示Pod的详细信息,自然也携带Pod的IP地址(在Pod的status.podIP字段),读者可以自行尝试,这里不再赘述。
如果运行在容器内的进程希望获取该容器的IP(同样是Pod的IP),环境变量是一个不错的选择,尤其是Kubernetes提供了相关的downward API。当在Pod的配置文件书写如下env字段时:
容器启动后MY_POD_IP这个环境变量的值来自Pod的status.podIP,容器内的应用只要读取该环境变量即可获取Pod的真实IP地址。
2. 从外面看Pod IP地址之docker命令
假设容器的ID是abc,一般情况下我们可以通过以下命令查询容器的IP地址:
然而对于Pod中的容器,这一招并不管用,你会发现输出是一个空字符串。究其原因,既不是Kubernetes的Bug也不是用户配置错误,而是Kubernetes使用的CNI与Docker奉行的CNM标准割裂导致的。Kubernetes曾旗帜鲜明地宣布永不使用Libnetwork(具体原因见5.2节),而Libnetwork正是CNM的标准实现,docker inspect命令查询容器IP就是调用的Libnetwork。
那么是不是就意味着不能使用docker或者简单的ip命令查看由Kubernetes管理的容器IP了呢?非也,具体请看下面的方法。
3. 进到容器里面看Pod IP地址
进到容器的docker命令有docker exec或docker attach,进到容器后再执行ip addr或ifconfig这类常规命令。同一个Pod内所有容器共享网络namespace,因此随便找一个有ip或者ifconfig 命令的容器就能很容易地查询到Pod IP地址。如果Pod内所有容器都不自带这些命令呢?从1.1节的内容中我们知道,在我们这个场景下,进入容器网络namespace的作用等价于进入容器,而且还能使用宿主机的ip或者ifconfig命令。
假设Pod的pause容器ID是abc,首先获得该容器在宿主机上映射的PID,如下所示:
如上所示,PID是32466。
然后调用nsenter命令(没有安装的需要自行安装),进入pause容器的网络namespace,如下所示:
正常情况下,上面的命令会输出pause容器的IP,也就是Pod的IP。
3.9.5 故障排查工具
下面是一些我们在排查上述问题时使用的非常有用的工具。
1.tcpdump
tcpdump是一个用来捕获网络流量的“利器”,不论是传统主机网络还是容器网络架构,它可以帮助我们定位一些常见的网络问题,下面是一个使用tcpdump进行流量捕获的简单例子。
2. 容器镜像内置常用网络工具
在一个镜像中内置一些网络工具包,对我们的排查工作会非常有帮助,比如在下面的简单服务中,我们添加一些常用的网络工具包:iproute2 net-tools ethtool,Dockerfile如下所示:
我们将上面Docker构建出来的镜像名字称为netbox,下面是一个使用netbox镜像部署Deployment的manifest文件:
另外,就算只用busybox镜像,其实也有很多“讲究”。要知道,busybox作为基础镜像,很多版本是不自带例如nslookup这类网络调试工具的,而nslookup在调试Kube-dns的域名功能时又非常有用。幸运的是,busybox 1.28版自带nslookup,需要的读者可以部署一个做域名解析测试之用。一个使用busybox 1.28版的Pod配置文件如下所示:
3.9.6 为什么不推荐使用SNAT
如果从外部主机无法直接访问容器,那么容器不可能和外部服务通信。如果一个容器请求外部的服务,由于容器IP是不可路由的,则远程服务器不知道应该把响应发到哪里。但事实上,只要每个主机对容器到外部的连接做一次SNAT或是Masquerade就能实现。
1. 问题描述
我们的Docker主机能够和数据中心的其他机器通信,它们有可路由的IP。当一个容器尝试访问一个外部服务时,运行容器的主机将网络包中的容器IP用它本身的IP替换,即Masquerade(SNAT的一种)。对于外部服务,看起来像是和主机建立了连接。当响应返回到主机的时候,它进行一个逆转换(把网络包中的主机IP替换成容器IP)。对于容器,这个操作完全是透明的,它不知道发生了这样的一个转换。Kubernetes NodePort的实现默认就开启了Kube-proxy的--masq-all=true选项。
例如,一个Docker主机10.0.0.1上运行着一个名为container-1的容器,它的IP为172.16.1.8。容器内部的进程初始化一个访问10.0.0.99:80的连接。它绑定本地容器端口32000。
(1)这个包离开容器到达Docker主机,源地址为172.16.1.8:32000。
(2)Docker主机将源地址由172.16.1.8:32000替换为10.0.0.1:32000,并把包转发给10.0.0.99:80。Linux用一个表格追踪转换过程,以便在包的响应中能够进行逆向转换。
(3)远程服务10.0.0.99:80处理这个请求并返回响应给主机。
(4)响应返回到主机的32000端口。Linux看到这是一个已经转换的连接的响应,便把目的地址从10.0.0.1:32000修改为172.16.1.8:32000,把包转发给容器。
这么做会带来一个问题,就是当并发访问NodePort时会导致丢包。除了NodePort,flannel的--ip-masq=true选项也会引起丢包。
SNAT导致Linux内核丢包的原因在于其conntrack的实现。SNAT代码在POSTROUTING链上被调用两次。首先通过修改源地址和/或端口修改包的结构,如果包在这个过程中没有丢失,则内核在conntrack表中记录这个转换。这意味着在SNAT端口分配和插入conntrack表之间有一个时延,如果有冲突的话可能最终导致插入失败及丢包。当在TCP连接上做SNAT的时候,NAT模块会做以下尝试:
(1)如果包的源地址是在目标NAT池中,且IP,端口,协议三元组是可用的,返回(包没有改变)。
(2)找到池中最少使用的IP,用它来替换包中的源IP。
(3)检查端口是否在允许的范围(默认1024-64512)内,并且检查带这个端口的三元组是否可用。如果可用则返回(源IP已经改变,端口未改变)。(注意:SNAT的端口范围不受内核参数net.ipv4.ip_local_port_range的影响。)
(4)端口不可用,内核通过调用nf_nat_l4proto_unique_tuple()请求TCP层找到一个未使用的端口来做SNAT。
当主机上只运行着一个容器时,NAT模块最可能在第三步返回。容器内部进程使用到的本地端口会被保留并用以对外的连接。当在Docker主机上运行多个容器时,很可能一个连接的源端口已经被另一个容器的连接使用。在那种情况下,通过nf_nat_l4proto_unique_tuple()调用来找到另一个可用的端口进行NAT操作。默认的端口分配做以下的事:
(1)从一个初始位置开始搜索并复制最近一次分配的端口。
(2)递增1。
(3)调用nf_nat_used_tuple()检查端口是否已被使用。如果已被使用,重复上一步。
(4)用刚分配的端口更新最近一次分配的端口并返回。
端口分配和把连接插入conntrack表之间有延时,因此当nf_nat_used_tuple()被并行调用时,对同一个端口的nf_nat_used_tuple()多次调用可能均回真——当端口分配以相同的初始值开始时,这种现象尤为明显。在我们的测试中,大多数端口分配冲突来自时间间隔在0~2us内初始化的连接。
netfilter也支持两种其他的算法来找到可用的端口:
· 使用部分随机来选择端口搜索的初始位置。当SNAT规则带有flag NF_NAT_RANGE_PROTO_RANDOM时,这种模式被使用;
· 完全随机来选择端口搜索的初始位置。带有flag NF_NAT_RANGE_PROTO_RANDOM_FULLY时使用。
NF_NAT_RANGE_PROTO_RANDOM降低了两个线程以同一个初始端口开始搜索的次数,但是仍然有很多的错误。只有使用NF_NAT_RANGE_PROTO_RANDOM_FULLY才能显著减少conntrack表插入错误的次数。在一台Docker上测试虚拟机,使用默认的masquerade规则,10到80个线程并发请求连接同一个主机有2%~4%的插入错误。当在内核强制使用完全随机时,丢包率降到了0。
2. 解决方法
需要在masquerade规则中设置flag NF_NAT_RANGE_PROTO_RANDOM_FULLY。从1.6.2版本开始的,iptables工具已经支持配置--ramdom-fully这个flag。通过使用打了补丁的flannel和Kube-proxy,能够显著降低conntrack表的插入错误,使整个集群中的丢包错误的数目从每几秒一次下降到每几个小时一次。
需要注意的是,iptabels的--ramdom-fully选项只能缓解集群SNAT带来的这个问题,而并不能根治。因此,我并不推荐在生产环境使用NodePort。
以上是关于Kubernetes网络自学系列 | 前方高能:Kubernetes网络故障定位指南的主要内容,如果未能解决你的问题,请参考以下文章
Kubernetes网络自学系列 | 打通CNI与Kubernetes:Kubernetes网络驱动
Kubernetes网络自学系列 | 终于等到你:Kubernetes网络