容器实战高手课
李程远
eBay 总监级工程师,云平台架构师
24647 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 31 讲
容器实战高手课
15
15
1.0x
00:00/00:00
登录|注册

03|理解进程(2):为什么我的容器里有这么多僵尸进程?

避免阻塞的回收僵尸进程方法
回收容器中的所有僵尸进程
父进程调用wait()或waitpid()回收僵尸进程
阻碍新进程的启动
占用进程号资源
pids.max 文件控制最大进程数目
pids Cgroup限制最大进程数目
进程数目限制的重要性
进程状态:运行态和睡眠态
进程状态转化图
task_struct 结构表示进程
容器的init进程创建了子进程B,B又创建了自己的子进程C。如果C运行完之后,退出成了僵尸进程,B进程还在运行,而容器的init进程还在不断地调用waitpid(),那C这个僵尸进程可以被回收吗?
僵尸进程需要父进程调用wait()或waitpid()系统调用来清理
僵尸进程如果不清理,会消耗系统中的进程数资源
每一个Linux进程在退出的时候都会进入一个僵尸状态
waitpid()系统调用
容器中的init进程的责任
解决思路
僵尸进程的危害
限制容器中进程数目
Linux的进程状态
产生原因:父进程不负责回收子进程
容器中出现大量僵尸进程
主题:容器中的僵尸进程问题
作者:程远
标题:为什么我的容器里有这么多僵尸进程?
思考题
重点总结
解决问题
知识详解
问题再现
参考文章
为什么我的容器里有这么多僵尸进程?

该思维导图由 AI 生成,仅供参考

你好,我是程远。今天我们来聊一聊容器里僵尸进程这个问题。
说起僵尸进程,相信你并不陌生。很多面试官经常会问到这个知识点,用来考察候选人的操作系统背景。通过这个问题,可以了解候选人对 Linux 进程管理和信号处理这些基础知识的理解程度,他的基本功扎不扎实。
所以,今天我们就一起来看看容器里为什么会产生僵尸进程,然后去分析如何怎么解决。
通过这一讲,你就会对僵尸进程的产生原理有一个清晰的认识,也会更深入地理解容器 init 进程的特性。

问题再现

我们平时用容器的时候,有的同学会发现,自己的容器运行久了之后,运行 ps 命令会看到一些进程,进程名后面加了 <defunct> 标识。那么你自然会有这样的疑问,这些是什么进程呢?
你可以自己做个容器镜像来模拟一下,我们先下载这个例子,运行 make image 之后,再启动容器。
在容器里我们可以看到,1 号进程 fork 出 1000 个子进程。当这些子进程运行结束后,它们的进程名字后面都加了标识。
从它们的 Z stat(进程状态)中我们可以知道,这些都是僵尸进程(Zombie Process)。运行 top 命令,我们也可以看到输出的内容显示有 1000 zombie 进程。
# docker run --name zombie-proc -d registry/zombie-proc:v1
02dec161a9e8b18922bd3599b922dbd087a2ad60c9b34afccde7c91a463bde8a
# docker exec -it zombie-proc bash
# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4324 1436 ? Ss 01:23 0:00 /app-test 1000
root 6 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 7 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 8 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 9 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 10 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 999 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 1000 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 1001 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 1002 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 1003 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 1004 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 1005 0.0 0.0 0 0 ? Z 01:23 0:00 [app-test] <defunct>
root 1023 0.0 0.0 12020 3392 pts/0 Ss 01:39 0:00 bash
# top
top - 02:18:57 up 31 days, 15:17, 0 users, load average: 0.00, 0.01, 0.00
Tasks: 1003 total, 1 running, 2 sleeping, 0 stopped, 1000 zombie
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了容器中的僵尸进程问题,通过实际操作展示了在容器中出现僵尸进程的情况,并详细介绍了Linux进程状态的转化和限制容器中进程数目的方法。作者提出了通过pids Cgroup来限制容器中的最大进程数目,避免因创建过多进程而影响其他容器和宿主机的情况发生。文章还分析了僵尸进程的产生原因,并介绍了父进程调用wait()或者waitpid()系统调用来避免僵尸进程产生的方法。总结了僵尸进程的危害和清理方法,强调了容器中init进程必须具备清理僵尸进程的功能。文章通过代码示例和实际操作,帮助读者深入理解和解决容器中僵尸进程问题。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《容器实战高手课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(42)

  • 最新
  • 精选
  • 莫名
    C 应该不会被回收,waitpid 仅等待直接 children 的状态变化。 为什么先进入僵尸状态而不是直接消失?觉得是留给父进程一次机会,查看子进程的 PID、终止状态(退出码、终止原因,比如是信号终止还是正常退出等)、资源使用信息。如果子进程直接消失,那么父进程没有机会掌握子进程的具体终止情况。一般情况下,程序逻辑可能会依据子进程的终止情况做出进一步处理:比如 Nginx Master 进程获知 Worker 进程异常退出,则重新拉起来一个 Worker 进程。

    作者回复: 谢谢 @莫名! 很好的解释!

    2020-11-20
    5
    86
  • JianXu
    问题一:在Kubernetes 的情况下,是不是该节点上所有的容器都是kubelet 的子进程?不然kubelet 怎么来清理这些容器产生的僵尸进程呢? 问题二:在Docker 的场景下,容器第一个进程是用户自己写的进程,而该进程是不能保证在回收子进程资源上的质量的,所以才有Tinit 等工具,那为什么docker runtime 不默认把这样的回收功能做了呢? 问题三:Linux 为什么不设计成可以kill -9 杀死僵尸进程呢?现在把希望都寄托在父亲进程的代码质量上,而要init 回收,就得把init 到 僵尸进程之间的血缘进程全部杀死。为什么要做这样的设计呢?

    作者回复: > 问题一 在kuberenetes下,kubelet还是调用 containerd/runc去启动容器的,每个容器的父进程是containerd-shim, 最终shim可以回收僵尸进程。 > 问题二 docker倒是也做了这件事。 用docker启动容器的时候 加--init参数,起来的容器就强制使用tini作为init进程了。 > 问题三 Linux进程要响应SIGKILL并且执行signal handler,只有在被进程调度到的时候才可以做。对于zombie进程,它已经是不可被调度的进程了。

    2020-11-28
    4
    43
  • 上邪忘川
    总结一下这节课相关的东西 1.,父进程在创建完子进程之后就不管了,而每一个 Linux 进程在退出的时候都会进入一个僵尸状态,这时这些进入僵尸状态的进程就因为无法回收变成僵尸进程。 2.僵尸进程是无法直接被kill掉的,需要父进程调用wait()或watipid()回收。 3.清理僵尸进程的两个思路 (1)kill掉僵尸进程的父进程,此时僵尸进程会归附到init(1)进程下,而init进程一般都有正常的wait()或watipid()回收机制。 (2)利用dumb-init/tini之类的小型init服务来解决僵尸进程

    作者回复: @上邪忘川, 谢谢你的总结

    2020-11-22
    23
  • 水蒸蛋
    老师您的意思是僵尸线程默认都不会自动关闭的,全靠父进程回收,如果产生大量僵尸进程说明父进程相关回收策略有问题是吗

    作者回复: 对的

    2020-11-22
    8
  • Helios
    僵尸进程也是进程,就是资源没有被回收,父进程还活着就不会被init回收。 补充一点 子进程推出的时候会给父进程发送个信号,如果父进程不处理这个信号就会变味僵尸进程。现在一般只会出现在c这种需要手动垃圾回收得语言了。 老师是踩过坑呢,感觉这个坑不好踩,一是因为高级语言会处理信号,就像上一节说的。还有就是啥业务场景能搞三万多进程

    作者回复: @Helios, 对于容器或者说pod, 我们加了pids cgroup的限制,pids.max 对于每个容器一般就是以千为单位了,这个值还是很容易达到上限的。 我们在线上看到的大量Z进程,实际的情况要复杂一些,一个进程有多个线程,主进程处于Z状态,而还有一个线程处于D状态,但是从表象查看进程状态的时候,看到都是<defunct>进程了(Z)。由于有了D的线程在里面,这时候waitpid(), 任何信号对这些进程都无效了。 这一讲,我是把Z进程的概念单独说了一下,对于D进程,它会引起其他的一些现象,我会在后面讲到。

    2020-11-20
    3
    7
  • Delia
    我是一个Docker新手,请教一下老师,经常看到一些容器僵尸,状态栏显示:Exited (2) 10 days ago,Exited (1) 10 days ago,Exited (100) 10 days ago等等,这些容器为啥不能被回收呢?目前只能docker rm清理掉。

    作者回复: docker 自己没有自动清理的功能。如果是kubernetes/kubelet是会做清理。

    2020-11-25
    3
    5
  • 朱雯
    最后我作为一个运维工程师,我还是不知道怎么处理僵死进程,第一我可能不能直接杀死他们的父进程,因为可能有用,第二,我无法kill掉他们,第三我无法修改代码,代码本身对我是黑盒子。

    作者回复: 我觉得刚才 @上邪忘川的回复挺好的 3.清理僵尸进程的两个思路 (1)kill掉僵尸进程的父进程,此时僵尸进程会归附到init(1)进程下,而init进程一般都有正常的wait()或watipid()回收机制。 (2)利用dumb-init/tini之类的小型init服务来解决僵尸进程

    2020-11-22
    8
    5
  • nuczzz
    k8s+kata集群会有这个问题吗?感觉kata做了内核隔离,容器里的僵尸进程应该影响不到宿主机了

    作者回复: kata是基于VM的,它里面的僵尸进程不会影响到宿主机。

    2021-01-12
    4
  • 1900
    老师你好 关于设置容器的Cgroup 中 pids.max配置 ,是跟业务进程一起运行在容器中然后修改当前的容器配置,是否还有其他优雅的方式呢 ?

    作者回复: 对容器设置pids Cgroup限制,一般需要容器云平台来做,在启动容器的时候就自动的设置好,类似cpu/memory Cgroup的设置。 像Kubernetes的类似这么做, https://github.com/kubernetes/kubernetes/commit/ecd6361ff0e8421332a50e55fcba17b823d5d338

    2020-11-20
    4
  • 争光 Alan
    老师,我最近发现ulimit也会限制进程数量,这两个有什么区别吗?在容器内具体是哪个在生效?

    作者回复: ulimit对于shell session中的总进程数量做限制,pids cgroup对于控制块中的cgroup.procs/tasks 中的进程数目做限制。都会起效,看哪个先达到限制值。

    2021-03-17
    3
    2
收起评论
显示
设置
留言
42
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部