前几天中午在吃火锅唱歌,突然手机上弹出一大堆告警信息,报某K8s集群的api-server无法连接。经过一番排查,发现集群中存在大量重复的Pod对象,导致etcd和apiserver占用大量内存,陷入不可用状态。为什么会这样?接下来,就和小编一起来看看吧。万恶之源众所周知,Kubernetes是一个容器编排系统,将集群中各种应用的状态维护到声明中要求的状态。为了更准确地将集群维护到维护者想象中的状态,Kubernetes中有大量的机制来控制集群。导致这次失败的原因是以下三个特性的错误使用。其中,Replicaset是一个控制器,用于维护工作负载中Pod的数量。当你在无状态工作负载中声明spec.replicas字段时,Kubernetes会继续创建Pod,直到可用Pod数量满足声明时才会停止调度。Taintandtolerance在创建Pod时,Kubernetes需要将Pod的容器部署到一个可用的节点上。这时调度器会选择一个可用的节点,将Pod部署到该节点上。如果我们希望某些节点不调度Pod,我们可以为这些节点设置污点。这样节点以后会拒绝新调度的Pod(NoSchedule),甚至打散已有的Pod(NoExecute)。只有配置了相应容忍度的Pod才能继续被调度并运行在受污染的节点上。节点选择,我们可以通过声明(nodeName)将一些Pod调度到指定的节点。这些Pod不会部署在具有其他名称的节点上。悲剧发生了现在,让我们想象一下,当一个带有副本集的工作负载设置节点选择器时,他选择的节点突然被NoExecute污染了。但是在工作负载中没有设置相应的容忍度。那么会发生什么?A.工作负载停止创建Pod,并提示所选节点被污染。B.Pod部署失败,提示无法部署后以无效状态终止部署。C.Pod部署失败。删除当前Pod,继续部署。D.Pod部署失败,立即创建一个新对象并继续尝试部署显然,答案是最不明显的D.污点的实现是在调度时判断当前节点是否被污点并满足要求。如果没有满足要求的节点,这个Pod的部署就会被阻塞。如果声明了nodeName,Pod的部署会跳过节点选择,直接部署到目标节点,然后Pod会因为没有对应的容忍度而被拒绝,其状态会被标记为Evicted。由于此失败不是容器运行失败,因此Kubernetes控制器将尝试继续创建Pod,以便可用的Pod数量与副本集中声明的数量相匹配。而且这个过程非常快,瞬间可以创建出无数个Pod,而且Pod的规模会随着时间的推移不断扩大。直到集群中的主节点崩溃。如何修复幸运的是,我们在集群完全崩溃之前发现了这个问题。但是,虽然集群还没有崩溃,但是apiserver的响应时间已经变得很长,任何一个get请求都可能需要等待5秒以上。然后我们通过kubectl删除了所有对应的Pod和workload,故障解决。我们可以使用以下命令删除所有被驱散的Podkubectl-nkube-systemgetPods|grepEvicted|awk'{print$1}'|xargskubectl-nkube-systemdeletePods如果集群真的彻底崩溃,那么有可能需要先停止Kubernetes相关组件的运行,手动删除etcd中对应的对象。然后重新启用k8s组件。总结虽然表面上看这种错误是人为错误,但实际上k8s会自动给一些节点加上污点。这样一来,类似的情况可能会再次发生。从源头上,我们可以尽可能使用节点选择器(nodeSelector)和节点亲和性(nodeAffinity)来调度Pod。这样部署的Pod在调度前会验证一次,如果没有可用的节点,调度就会停止。或者您可以使用PDB来防止创建过多的Pod。PDB可以控制应用在短时间内因非自愿干扰而关闭的Pod数量,从而避免无数Pod的创建和分散导致的集群崩溃。
