在使用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其所收养的所有僵尸进程。
为避免产生僵尸进程,实际应用中一般采取的方式是:
- 将父进程中对SIGCHLD信号的处理函数设置SIG_IGN
- fork两次并杀死一级自进程,令二级子进程成为孤儿进程而被
init
所“收养”、清理
解决办法
Tini是能想到的最简单的init
。
Tini一般在容器中运行,用于生成子进程,等待它推出,reap僵尸进程,并执行信号转发。
在github上是这么介绍的:
1 | Why Tini? |
docker打一个tomcat镜像
措施
- 基于以上要求我们构建出一下镜像
- 首先使用apline作为基础镜像足够小只有5M
- 由于alpine自带支持中文的字符集,这里我们只需要将LANG设置为C.UTF-8即可完美的支持中文。
- 国内软件源首选阿里云啦,顺道配置一下阿里云的镜像源,加速我们的镜像构建速度。
- 配置UTC+8时区需要安装tzdata,安装完成之后配置一下即可。
- 使用tini 包装java进程。
dockerfile
tini的dockerfile
1 | FROM alpine:3.8 |
tomcat的dockerfile
1 | FROM sy:tini |
1 | docker build -t sy-tomcat:tini -f Dockerfile . |
这样打完的镜像不仅小,也解决了僵尸进程,包括jmap等命令用不了的问题