容器僵尸进程造成集群节点NotReady
问题
集群工作节点频繁NotReady
NAME STATUS ROLE AGE VERSION 10.9.x.x NotReady <none> 120d v1.13.9
查看工作节点/var/log/messages日志,发现PLEG not healthy导致Not become not ready。
Jul 12 03:24:22 hostnamex kubelet:I0712 03:24:22.233328 2653 kubelet.go:1846] skipping pod synchronization - [PLEG is not healthy: pleg was last seen active 3m7.15341635s ago; threshold is 3m0s] Jul 12 03:24:23 hostnamex kubelet:I0712 03:24:23.731971 2563 setters.go:520] Node vecame not ready:{Type:Ready Status:False LastHeartbeatTime:2020-07-12 -3:24:23.731942491 +0800 CST m=+6020732.425522098 LastTransitionTime:2020-07-12 03:24:23.731942491 +0800 CST m=+6020732.425522098 Reason:KubeletNotReady Message:PLEG is not healthy: pleg was last seen active 3m8.652082872s ago; threshold is 3m0s}
排查临近时间点的日志,发现PLEG一直无法获取某一个POD 的状态。
Jul 12 03:21:14 hostnamex kubelet: E0712 03:21:14.079575 2563 generic.go:277] PLEG: pod xxx-xxx-xxx-xxx/xxx-xxx-xxx failed reinspection: rpc error: code = DeadlineExceeded desc = context deadline exceeded Jul 12 03:23:15 hostnamex kubelet: E0712 03:23:15.100271 2563 generic.go:247] PLEG: Ignoring events for pod xxx-xxx-xxx-xxx/xxx-xxx-xxx: rpc error: code = DeadlineExceeded desc = context deadline exceeded
执行'kubectl get pod xxx-xxx-xxx-xxx -n xxx-xxx-xxx',发现这个POD不存在。
执行'docker ps | grep xxx-xxx-xxx-xxx',发现容器还在,但是执行'docker inspect <container id>'时命令卡死无响应。
查看/var/lib/docker/containers/<container id>/config.v2.json'文件中的Pid字段,获取容器PID。
'ps -ef | grep <PID>' 发现有僵尸进程,并且这些僵尸进程均为sh或su进程。
进入用户正常的POD中查看进程,发现用户在同一个容器中运行了多个不同的应用,例如shell,uwsgi,nginx。而容器的原则是应该尽量保持容器功能的单一,把不同功能的应用拆分成多个容器。
并且,用户容器中有多个sh和su进程,其父进程ID为0,并且启动时间比容器启动时间晚了几天。怀疑是用户进入容器执行的命令。
经测试,如果用户通过kubectl exec进入容器,会生成父进程ID为0的进程。如果用户正常退出的话,这些进程会自动结束,但如果会话因为超时退出,这些进程则无法自动结束,会遗留在容器中。
为什么会话会超时呢?kubectl exec其实是通过kube-api -> kubelet -> docker daemon -> docker 容器执行命令的,kubelet本身有超时限制(--streaming-connection-idle-timeout),默认是4小时。另外,有些平台自行开发了在网页登录容器的功能,这些平台本身也会有超时限制。
解决方案
因为用户的容器是通过shell进程启动的,所以容器中的PID 1进程为shell进程,如果shell进程不能正常回收容器中因为kubectl exec遗留下的进程,则可能会造成僵尸进程。
于是建议用户通过dumb-init启动应用。关于dumb-init的介绍及下载可以参考以下链接:
https://github.com/Yelp/dumb-init
https://github.com/Yelp/dumb-init/releases/
下载dumb-init二进制文件,在编译镜像时拷贝到镜像中,并且在Dockerfile文件中添加以下内容:
# 设置可执行权限 RUN chmod +x /path/to/dumb-init ENTRYPOINT ["/path/to/dumb-init", "--"] CMD ["应用的启动命令"]