记一次Kubernetes/Docker网络排障
Posted Hollis
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次Kubernetes/Docker网络排障相关的知识,希望对你有一定的参考价值。
原文地址:https://coolshell.cn/articles/18654.html
原文作者:左耳朵耗子
昨天周五晚上,临下班的时候,用户给我们报了一个比较怪异的Kubernetes集群下的网络不能正常访问的问题,让我们帮助查看一下,我们从下午5点半左右一直跟进到晚上十点左右,在远程不能访问用户机器只能远程遥控用户的情况找到了的问题。这个问题比较有意思,我个人觉得其中的调查用到的的命令以及排障的一些方法可以分享一下,所以写下了这篇文章。
用户直接在微信里说,他们发现在Kuberbnetes下的某个pod被重启了几百次甚至上千次,于是开启调查这个pod,发现上面的服务时而能够访问,时而不能访问,也就是有一定概率不能访问,不知道是什么原因。而且并不是所有的pod出问题,而只是特定的一两个pod出了网络访问的问题。用户说这个pod运行着Java程序,为了排除是Java的问题,用户用docker exec -it
命令直接到容器内启了一个 Python的 SimpleHttpServer来测试发现也是一样的问题。
我们大概知道用户的集群是这样的版本,Kuberbnetes 是1.7,网络用的是flannel的gw模式,Docker版本未知,操作系统CentOS 7.4,直接在物理机上跑docker,物理的配置很高,512GB内存,若干CPU核,上面运行着几百个Docker容器。
问题初查
首先,我们排除了flannel的问题,因为整个集群的网络通信都正常,只有特定的某一两个pod有问题。而用telnet ip port
的命令手工测试网络连接时有很大的概率出现connection refused
错误,大约 1/4的概率,而3/4的情况下是可以正常连接的。
当时,我们让用户抓个包看看,然后,用户抓到了有问题的TCP连接是收到了SYN
后,立即返回了RST, ACK
我问一下用户这两个IP所在的位置,我们知道了,10.233.14.129
是docker0
,10.233.14.145
是容器内的IP。所以,这基本上可以排除了所有和kubernets或是flannel的问题,这就是本地的Docker上的网络的问题。
对于这样的情况,在telnet
上会显示connection refused
的错误信息,对于我个人的经验,这种SYN
完直接返回RST, ACK
的情况只会有三种情况:
TCP链接不能建立,绝大多数情况都是服务端没有相关的端口号
TCP链接建错误,有可能是因为修改了TCP参数
有防火墙iptables的设置
因为当时还在开车,在等红灯的时候,我感觉到有点像 NAT 的网络中服务端开启了tcp_tw_recycle
和tcp_tw_reuse
的症况(详细参看《TCP的那些事(上)》),所以,让用户查看了一上TCP参数,发现用户一个TCP的参数都没有改,全是默认的,于是我们排除了TCP参数的问题。
然后,我也不觉得容器内还会设置上iptables,所以,我怀疑容器内的端口号没有侦听上,但是马上又好了,这可能会是应用的问题。于是我让用户那边看一下,应用的日志,并用kublet describe
看一下运行的情况,并把宿主机的 iptables 看一下。
然而,我们发现并没有任何的问题。这时,我们失去了所有的调查线索,感觉不能继续下去了……
重新梳理
$ arping -D -I docker0 -c 2 10.233.14.145
$ echo$?
柳暗花明
现在我们知道,IP冲突的可能性是非常大的,但是我们找不出来是和谁的IP冲突了。而且,我们知道只要把这台机器重启一下,问题一定就解决掉了,但是我们觉得这并不是解决问题的方式,因为重启机器可以暂时的解决掉到这个问题,而如果我们不知道这个问题怎么发生的,那么未来这个问题还会再来。而重启线上机器这个成本太高了。
在这个事上,我们费了点时间。
首先,我们到
/var/run/netns
目录下查看系统的network namespace,发现没有。然后,我们到 /var/run/docker/netns 目录下查看Docker的namespace,发现有好些。
$ls/var/run/docker/netns|xargs-I {} nsenter --net=/var/run/docker/netns/{} ip addr
10.233.14.145
我们查到了,docker的namespace下还有这个IP。我们发现了比较诡异的事情。
10.233.14.137
,这个IP没有在docker的network namespace下查到。
于是我上网查了一下,发现了一个docker的bug – 在docker remove/stop 一个容器的时候,没有清除相应的network namespace,这个问题被报告到了 Issue#31597 然后被fix在了 PR#31996,并Merge到了 Docker的 17.05版中。而用户的版本是 17.09,应该包含了这个fix。
要查看所有network namespace,只有最后一条路了,那就是到/proc/
目录下,把所有的pid下的/proc/<pid>/ns
目录给穷举出来。好在这里有一个比较方便的命令可以干这个事 –lsns
于是我写下了如下的命令:
$ lsns -t net |awk‘{print $4}' |xargs-t -I {} nsenter -t {} -n ip addr |grep-C 4"10.233.14.137"
lsns -t net
列出所有开了network namespace的进程,其每4列是进程PID解释一下。
把所有开过network namespace的进程PID拿出来,转给
xargs
命令由
xargs
命令把这些PID 依次传给nsenter
命令,xargs -t
的意思是会把相关的执行命令打出来,这样我知道是那个PID。xargs -I {}
是声明一个占位符来替换相关的PID
我们继续乘胜追击,穷追猛打,用pstree
命令把整个进程树打出来。发现上述的三个进程的父进程都在多个同样叫docker-contiane
的进程下!
这明显还是docker的,但是在docker ps
中却找不道相应的容器,什么鬼!我快崩溃了……
继续看进程树,发现,这些docker-contiane
的进程的父进程不在dockerd
下面,而是在systemd
这个超级父进程PID 1下,我靠!进而发现了一堆这样的野进程(这种野进程或是僵尸进程对系统是有害的,至少也是会让系统进入亚健康的状态,因为他们还在占着资源)。
docker-contiane
应该是dockerd
的子进程,被挂到了pid 1
只有一个原因,那就是父进程“飞”掉了,只能找 pid 1 当养父。这说明,这台机器上出现了比较严重的dockerd
进程退出的问题,而且是非常规的,因为systemd
之所以要成为 pid 1,其就是要监管所有进程的子子孙孙,居然也没有管理好,说明是个非常规的。(注,关于 systemd,请参看《Linux PID 1 和 Systemd 》,关于父子进程的事,请参看《Unix高级环境编程》一书)
接下来就要看看systemd
为dockerd
记录的日志了……
通过这个调查,可以总结一下,
1) 对于问题调查,需要比较扎实的基础知识,知道问题的成因和范围。
2)如果走不下去了,要生新梳理,回头看一下过的一些蛛丝马迹,但认真推敲每一个细节。
3) 各种诊断工具要比较熟悉,这会让你事半功倍。
4)系统维护和做清洁比较类似,你需要经党看看系统中是否有一些僵尸进程或是一些垃圾东西,这些东西要及时清理掉。
最后,多说一下,很多人都说,Docker适合放在物理机内运行,这并不完全对,因为他们只考虑到了性能成本,没有考虑到运维成本,在这样512GB中启动几百个容器的玩法,其实并不好,因为这本质上是个大单体,因为你一理要重启某些关键进程或是机器,你的影响面是巨大的。
2018年最后一个月,Hollis的知识星球限时折扣中。欢迎您的加入。
直面Java第175期:什么是Java8 中的LocalDate和localTime?
成神之路第015期:深入学习Java中的枚举。
- MORE | 更多精彩文章 -
如果你喜欢本文。
请长按二维码,关注Hollis
以上是关于记一次Kubernetes/Docker网络排障的主要内容,如果未能解决你的问题,请参考以下文章