本文转载自微信公众号《运维开发故事》,作者无文案夏老师。转载本文请联系运维开发故事公众号。问题描述环境:ubuntu18.04,自建集群k8s1.18,容器运行时docker。现象:一个Node频繁NotReady,kubectl描述Node,出现“PLEGisnothealthy:plegwaslastseenactive3m46.752815514sago;thresholdis3m0s”的错误,频率5-10分钟出现一次。我们首先要了解什么是PLEG?PLEG的全称是PodLifecycleEventGenerator,即PodLifecycleEventGenerator。实际上,它只是Kubelet中的一个模块。它的主要职责是通过每个匹配的Pod级事件来调整容器的运行时状态,并将调整后的结果写入缓存,使Pod缓存保持最新。先说说PLEG的背景吧。在Kubernetes中,在每个节点上运行一个守护进程Kubelet来管理节点上的容器,调整容器的实际状态以匹配规范中定义的状态。具体来说,Kubelet需要及时响应两个地方的变化:Podspec中定义的状态容器运行时的状态对于Pod,Kubelet会从多个数据源观察Podspec的变化。对于容器,Kubelet会定期(例如10秒)轮询容器运行时以获取所有容器的最新状态。随着Pod和容器数量的增加,轮询具有不可忽略的开销,而这种开销又因Kubelet的并行运行而加剧(为每个Pod分配一个goruntine以获取容器的状态)。轮询带来的周期性大量并发请求会导致CPU使用率高峰值(即使pod定义和容器状态没有改变),降低性能。最终,容器运行时可能不堪重负,降低系统的可靠性,限制Kubelet的可扩展性。为了减少Pod管理开销,提高Kubelet的性能和可扩展性,引入PLEG来改进之前的工作方式:减少空闲期间不必要的工作(比如Pod定义和容器状态没有改变)。减少获取容器状态的并发请求数。所以我们看到这一切都离不开kubelet和pod的容器运行时。一方面,kub??elet扮演着集群控制器的角色。定时从APIserver获取pod及其他相关资源的信息,并根据这些信息控制节点上运行的pod的执行;另一方面,kub??elet用于监控节点Server的状态,获取节点信息,作为集群客户端,将状态同步到APIServer。在这个问题中,kubelet扮演了第二个角色。Kubelet会使用上图中的NodeStatus机制,定期检查集群节点的状态,并将节点的状态同步到APIServer。NodeStatus判断节点就绪状态的主要依据之一是PLEG。PLEG是PodLifecycleEventsGenerator的缩写。基本上,它的执行逻辑就是周期性的检查节点上Pod的运行状态。如果发现任何有趣的变化,PLEG会将这些变化打包为Events并发送给Kubelet的主要同步机制syncLoop来处理。但是当PLEG的Pod检查机制不能定时执行时,NodeStatus机制会认为这个节点的状态是错误的,然后将这个状态同步到APIServer。整体工作流程如下图所示,虚线部分为PLEG的工作内容。以nodenotready场景为例说明一下PLEG:Kubelet中的NodeStatus机制会定时检查集群节点的状态,并将节点状态同步到APIServer。NodeStatus判断节点就绪状态的主要依据之一是PLEG。PLEG会定期检查节点上Pod的运行状态,并将Pod的变化打包成一个Event发送给Kubelet的主要同步机制syncLoop进行处理。但是当PLEG的Pod检查机制不能定时执行时,NodeStatus机制会认为这个节点的状态不对,然后把这个状态同步到APIServer,我们就会看到notready。PLEG有两个关键的时间参数,一个是校验的执行间隔,一个是校验的超时时间。默认情况下,PLEG检查将以一秒为间隔。换句话说,每次检查过程执行完后,PLEG会等待一秒钟,然后再进行下一次检查;每次检查的超时时间为三分钟。如果一旦PLEG检查操作不能在三分钟内完成,那么这个状态将被NodeStatus机制作为集群节点NotReady凭证同步到APIServer。PLEGStart是启动一个协程,每隔relistPeriod(1s)调用一次relist,根据最新的PodStatus产生PodLiftCycleEvent。重新列出是PLEG的核心。它从容器运行时查询属于kubelet管理容器/沙箱的信息,与自己维护的pod缓存信息进行比对,生成对应的PodLifecycleEvent,然后输出到eventChannel,通过事件通道。消费,然后kubeletsyncPod触发pod同步过程,最终达到用户想要的状态。PLEGisnothealthy的原因这个错误很明确的告诉我们容器运行时异常,PLEG不健康。这里的容器运行时指的是dockerdaemon。Kubelet通过操纵docker守护进程来控制容器的生命周期。这里的PLEG是指Pod生命周期事件生成器。PLEG是kubelet用来检查运行时的健康检查机制。这可以通过kubelet使用轮询来完成。但是轮询有其高成本的缺陷,于是诞生了PLEG的应用。PLEG尝试以“中断”的形式实现容器运行时的健康检查,尽管实际上它是同时使用轮询和“中断”作为折衷方案。从Docker1.11开始,Docker容器的运行不再是简单的由DockerDaemon启动,而是通过集成containerd、runc等多个组件来启动。DockerDaemon守护进程模块虽然在不断重构,但基本功能和定位并没有太大变化。一直都是CS架构。守护进程负责与Docker客户端交互并管理Docker镜像和容器。在当前架构中,组件containerd负责集群节点上容器的生命周期管理,并为DockerDaemon提供gRPC接口。PLEG在每次迭代检查中调用runc的relist()函数是周期性地重新列出节点上的所有容器,并与之前的容器列表进行比较,以确定容器状态的变化。相当于dockerps获取所有容器,通过dockerinspect获取这些容器的详细信息。在有问题的节点上,通过dockerps命令不会有任何反应,说明上面的报错是准确的。Plegnothealthy出现的场景比较频繁,一般有以下几种可能:relistprocesscannotbecompletewithin3minutewithin完成relist时出现死锁,这个bug在Kubernetes1.14已经修复。网络排查过程说明1.我们在问题节点上执行top,发现一个名为scope的进程CPU占用率一直是100%。通过阅读资料了解到systemd.scope:作用域(scope)单元不是通过单元文件配置的,只能通过systemdD-Bus接口以编程方式创建。范围单元的名称都以“.scope”作为后缀。与服务单元不同,作用域单元用于管理一组外部创建的进程,它本身不fork任何进程。作用域单元的主要目的是分组管理系统服务的工作过程。2、在有问题的节点上继续执行后,通过dockerps命令不会有任何反应。它表明容器运行时也有问题。容器运行时和systemd有什么关系吗?3、我们参考了阿里的一篇文章,阿里巴巴的Kubernetes集群排查思路和方法。找到关系后,有兴趣的可以根据文末提供的链接详细了解。以下是文章的部分节选。什么是D总线?根据AlibabaKubernetes集群故障排查思路和方法[1],描述如下:在Linux上,dbus是一种进程间消息通信的机制。runC请求D-Bus容器运行时的runC命令,是对libcontainer的简单封装。该工具可用于管理单个容器,例如容器创建和容器删除。在上一节的最后,我们发现runC无法完成创建容器的任务。我们可以kill相应的进程,然后在命令行使用同样的命令启动容器,并使用strace跟踪整个进程。分析发现runC停在了向带有org.free字段的dbussocket写入数据的地方。解决问题最后可以断定是systemd的问题。我们使用systemctldaemon-reexec重启systemd,问题就消失了。所以更确定是systemd的问题。具体原因可以参考:https://www.infoq.cn/article/t_ZQeWjJLGWGT8BmmiU4这篇文章。根本解决问题的方法是:将systemd升级到v242-rc2,升级后需要重启操作系统。(https://github.com/lnykryn/systemd-rhel/pull/322)综上所述,PLEG不健康的问题其实是systemd造成的。最后,通过将systemd升级到v242-rc2,升级后需要重启操作系统。(https://github.com/lnykryn/systemd-rhel/pull/322)参考资料Kubelet:PodLifecycleEventGenerator(PLEG)Kubelet:RuntimePodCacherelist()inkubernetes/pkg/kubelet/pleg/generic.go过去关于CNI的错误——PLEGisnothealthy错误,节点标记为NotReadyhttps://www.infoq.cn/article/t_ZQeWjJLGWGT8BmmiU4https://cloud.tencent.com/developer/article/1550038
