前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >docker的reap问题

docker的reap问题

作者头像
大蟒传奇
发布2018-07-31 11:44:47
1.2K0
发布2018-07-31 11:44:47
举报
文章被收录于专栏:派森公园

在使用docker容器的时候,应该了解“PID1僵尸进程reap”问题。如果使用的时候不加注意,可能会导致出现一些意想不到的问题。

问题

僵尸进程

僵尸进程是指完成执行(通过exit系统调用,或运行时发生致命错误或收到终止信号所致),但在操作系统进程表中仍然有一个表项,处于“终止状态”的进程。这发生于子进程需要保留表项以允许其父进程读取子进程的exit status:一旦退出态通过wait系统调用读取,僵尸进程条目就从进程表中删除,这个过程被称为reap。正常情况下,进程直接被其父进程wait并由系统回收,进程长时间保持僵尸状态一般是错误的并导致资源泄漏。

英语中的zombie process源自丧尸--不死之人,隐喻进程已死大但没有被reap。与正常进程不同,kill命令对僵尸进程无效。孤儿进程不同于僵尸进程,其父进程已经死掉,但孤儿进程仍能正常执行,并不会变为僵尸进程,因为init进程会收养并wait其退出。

子进程死后,系统会发送SIGCHLD信号给父进程,父进程对其默认处理是忽略。如果想响应这个消息,父进程通常在SIGCHLD信号处理程序中,使用wait系统调用来响应子进程的终止。

僵尸进程被reap后,其进程号与在进程表中的表项都可以被系统重用。但如果父进程没有调用wait,僵尸进程将保留进程表中的表项,导致资源泄漏。

reap僵尸进程的方式是通过kill命令手工向其父进程发送SIGCHLD信号,如果其父进程仍然拒绝reap僵尸进程,则终止父进程,使得init进程收养僵尸进程。init进程周期执行wait系统调用reap其所收养的所有僵尸进程。

为避免产生僵尸进程,实际应用中一般采取的方式是:

  1. 将父进程中对SIGCHLD信号的处理函数设置SIG_IGN
  2. fork两次并杀死一级自进程,令二级子进程成为孤儿进程而被init所“收养”、清理

与docker的关系

现在有很多人使用docker,只在容器里面运行一个进程。大多数情况下,这个进程并不会有init进程的行为,也就是说,这个进程并不会reap收养的进程,而是期望init进程来做这件事,这种做法是合理的。

来看一个具体的例子。假设容器中跑一个Web服务器,这个服务器运行bash编写的CGI脚本,脚本中调用了grep。Web服务器发现脚本执行超时,杀掉了它,但是grep进程没有受到影响并继续运行。当grep进程执行完后,变成了僵尸进程,被PID为1的进程收养(Web服务器进程)。Web服务器不知道grep进程,所以并没有reap它,这时grep僵尸进程就留在了系统里。

在其他的情况下,这个问题可能也存在。大家经常将第三方的应用程序跑在docker容器里,比如PostgreSQL,和上面一样,这个进程也是容器内的唯一进程。在这种情况下,真的能确定在容器中运行这些第三方应用不会产生僵尸进程吗?所以,在一般情况下,应该运行适当的init系统来防止出现类似的问题。

胖容器问题

现有Upstart,Systemd,SysV init等方案可用,不过把这些一股脑地放在容器里,会不会显得太重呢?其实,虽然需要这些功能,“完全init系统”却不是必要的。

这里讨论的init系统是一个简单的程序,负责fork出应用程序,并且reap收养的进程。

解决办法

bash

是否已经有现成,流行的软件可以做到这一点呢?还真有,这就是bash。bash会正确地reap收养的子进程。bash可以执行任何程序。将Dockerfile中的

代码语言:javascript
复制
CMD ["/path-to-your-app"]

改成

代码语言:javascript
复制
CMD ["/bin/bash", "-c", "set -e && /path-to-your-app"]

即可。

不过,这个办法有一个关键问题:不能正确处理信号。对bash发送一个SIGTERM信号,bash会终止,但是并不会发送SIGTERM给其子进程。

当bash程序终止时,内核会停止整个容器和其中的进程。一些进程会接收到SIGKILL信号,不正确地终止。SIGKILL无法被捕获,所以进程不能干净地终止。假如应用程序正在写文件;如果应用程序在写入过程中被不正确地终止,则文件可能会损坏。这就像拔服务器电源一样。

docker init

docker提供了一个解决的办法,在运行容器的时候添加init标志

代码语言:javascript
复制
docker run --init your_image_here

这会让docker内部的微型init系统封装应用程序,这个init系统会保证将信号传递给其子进程并确保获取所有孤儿进程。

如果想重新映射程序退出码呢?比如Java接收SIGTERM信号退出时,退出码是143,而不是0。

docker init无法处理此类情况。

Tini

Tini是能想到的最简单的init

Tini一般在容器中运行,用于生成子进程,等待它推出,reap僵尸进程,并执行信号转发。

在最新的版本中,能将退出码143重新映射为0。使用的命令行如下

代码语言:javascript
复制
ENTRYPOINT ["/tini", "-v", "-e", "143", "--", "/runner/init"]
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 派森公园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题
    • 僵尸进程
      • 与docker的关系
        • 胖容器问题
        • 解决办法
          • bash
            • docker init
              • Tini
              相关产品与服务
              容器镜像服务
              容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档