背景
某现场19年部署一套k8s集群,docker版本1.12 ,k8s版本1.8.6,现网k8s资源池规模,生产环境58台物理机,灰环境60台虚机(后来才知道用的一套k8s资源池,通过标签区分),生产环境实例数2000左右,灰度环境实数900左右
现象
某现场在夜晚做业务升级的时候,批量更新业务包(由于微服务架构,而拆分并不完全,批量更新了十个中心的代码)同时启动副本为1的实例,再通过批量扩容的方式拉起2000左右的实例,出现现场大面积的k8s-node节点not Ready,以至于业务无法全部启动成功。
故障定位流程
由于之前现场出现过此问题,并只是伴有几个node的notReady问题,现场并没有第一时间联系我们,7点左右联系到我,我们第一时间拉取专家团队进行故障分析定位,因为早上8点需要营业,所以我在熟悉现场环境的情况下,并隐约知道这个问题与启动或扩容多个实例个数有关,再简单看了异常的节点的kubelet 日志和docker日志,快速备份节点日志后。建议现场优先恢复生产,建议先将业务应用的实例数先减少至1,保障所有业务都运行,再通过人工的方式,逐一对应用增加副本数。
一开始应该之前就曾有过这个情况(现场版本比较老,docker 1.12 k8s 1.8.6),所以将问题引向bug,但无法说明此次出现大面积的瘫痪。node节点的event:
拿到kubelet 日志 ,系统日志和docker日志以后,发现日志有以下问题
按照网上修复建议,以为是systemd的版本问题导致,但检查发现,现场的cgroup的版本还是用的cgroupfs 。
然后开始又开始翻找日志,同时在现场的测试过程中发现,容器在不使用端口映射的方式的情况下扩容并未引起节点的失联。现场采用的并不是NodePort的方式,而是hostPort方式并改动源代码能够随机在宿主机启动映射端口,也就是说每启动一个都会通过docker的方式生产iptables规则。一开始的方向觉得可能代码问题,但运行了接近3年,才出现属实说不过去。
真实原因定位:
在docker 日志中查到,有进程相互锁住,但是没查出具体的什么原因:
essage.txt:May 13 10:27:47 paas-10-239-40-157 dockerd: is currently holding the xtables lock; waiting (47s) for it to exit...\nAnother app is currently holding the xtables lock; waiting (49s) for it to exit...\nAnother app is currently holding the xtables lock; waiting (51s) for it to exit...\nAnother app is currently holding the xtables lock; waiting (53s) for it to exit...\nAnother app is currently holding the xtables lock; waiting (55s) for it to exit...\nAnother app is currently holding the xtables lock; waiting (57s) for it to exit...\n"
然后我在远程连线现场服务器时,查看top的时候发现有个进程cpu进程100%
查看服务的父进程发现是kube-proxy,检查kube-proxy的日志发现大量的进程互锁。
基本能判断由于改写了源码,容器在启动时需要映射端口由docker去修改iptables规则,
kube-proxy同时尝试修改规则,导致互锁。因为互锁时间长,docker出现了过载或没有响应时间过长,导致kublet去联系docker时联系不上。从而node节点报ContainerGcFailed的问题
就此根源问题基本排查出来了。
但针对现场还有个问题也需要排查,就是该问题应该在一开始就存在,而且之前都是几台出现,这次出现突然大面积的问题。docker发起端口映射时,采用的方式是docker -P的方式随机指定端口,它需要遍历iptables规则树获取可用端口,再写入,在iptables的规则多的情况下,这个时间耗时越来越长。大批量启动pod的时候就出现docker和kube-proxy的服务争抢iptables。当时检查iptables的规则条目有3万多
总结
导致的原因是,kube-proxy的刷新策略时间是2小时,在业务进程全停全启动时,原来的iptables规则未失效,docker遍历时间长。同时,现场3月份做了一套灰度环境,但为了节省资源,采用的是同一套k8s资源池,灰度环境的服务也启动了700多个pod,导致iptables达到瓶颈。所以再同时启动多pod的情况下,docker占用的时间延长,导致kubelet通信不到,才节点出现NotReady。这也解释了服务启动完成后就不会再出现这类情况。
建议
1.将灰度环境单独一套k8s资源池处理,解决当前iptables过大的的问题
2.应用均匀分布,在中心之间无相互影响的情况下,建议不打标签,使批量启动pod的时候不会只落在某几台上,导致docker寻址时间加长,从而kublet联系超时,而NotReady
3.拆分资源池,减少资源池内的pod运行数量。
4.取消采用该种端口映射的方式,可采用NodePort的方式,但需要修改和拆分应用配置。
5.不采用端口映射,通过ingress寻址,但会影响微服务的优雅停机和负载策略。
后记
从现场看,已经开始实行新版本的建设,目前其实简单的方式,就是拆灰度环境为单独k8s集群,然后现网增加node节点,既能够保障当前的业务承载量也能够减少该故障的发生。
对了,为啥要用端口映射,其实就是因为容器上云的相互之间的调用不全部在容器内,也有在外面的。然后采用NodePort的方式,一个应用一台宿主机起一个pod,10台宿主机只能是10个。如果还要多, 那只能复制多个应用出来,比如应用01、应用02,其实也不影响使用,影响系统的分类和拓扑。 也就是我第四条提到的方法