背景
之前有同事和我提过,在 Kubernetes 中,删除一个“应用”,有些应用删除比较慢。我们的“应用”,可以理解为一个 Deployment,删除应用,就是删除 Deployment,然后等待 Pod 全部退出。
当时其实没有太在意这个事情,因为 Kubernetes 的删除,从最细的粒度来看,就是删除 Pod,而删除 Pod,其实本质上就是停止容器,停止容器,本身其实会执行的是 docker stop 的过程,超时后,执行 docker kill 逻辑。
docker stop 是 docker daemon 进程,向 docker 容器进程,发送 kill -USR2 信号,而 docker kill,其实是一个 Kill -9 信号。换句话说,先让容器自行退出,一定时间内没有成功,再强制杀死。
当时觉得,有些容器停止慢,应该是容器业务比较繁重造成的,进程自行退出花费时间比较多而已。但今天看偶尔看 ENTRYPOINT 的的东西的时候,发现,也许这个问题,并没有那么简单。
ENTRYPOINT 的用法说明
我们知道,在构建镜像的时候,可以指定程序入口。可以设置 ENTRYPOINT 和 CMD。这2个可以配合使用,也可以完全独立使用。我们本次,不关心CMD,只讨论 ENTRYPOINT 的不同写法,对容器的影响。
Dockerfile 中 ENTRYPOINT 的2个用法
1 | // 用法1 |
第一种写法,你可以自行定义某个二进制程序以及参数,作为容器的1号进程的相关启动内容。
这种写法,也就是数组写法。
第二种写法,会将所设定的程序,限定在 /bin/sh -c 下执行。
换句话说,这种方式,容器内会有2个进程,一个是 /bin/sh 进程,一个是真正的你的二进制程序的进程,它的进程ID,不是1。
注意1:Docker 官方,推荐第一种写法。
注意2:第二种写法,限定你的进程在 /bin/sh 下,是很多文章提到的,但这个说法,其实并不准确,后边我会测试说明。现在,我们先默认这句话是正确的。
需要特别说明的是:
如果你的进程不是1号进程,/bin/sh 是1号进程的话,会存在一个问题:/bin/sh 进程,不会处理 Linux 信号。这就导致,用第二种 ENTRYPOINT 的写法,就可能出现 docker stop 无法正常停止容器(当时等待 docker stop 超时后,docker daemon还是会发送 kill 信号的,这个可以保证容器停止并退出)。
好了,我们要用第二种写法,测试 docker stop 无法正常停止容器这个过程。
在开始之前,我们明确几个事情
- 我们要测试 Dockerfile 中 ENTRYPOINT 写法不同,对容器中进程的影响
- 我们的可执行程序,就用 top
- 我们要看这个影响,是否会间接影响到 docker stop
- 最后,我们看一下,如果基础镜像不同,是否测试结果也会不同,我们先用 ubuntu:latest 做基础镜像测试。最后再用 centos:7.5.1804 作为基础镜像测试。
开始测试
使用 ubuntu 作为基础镜像做测试
Dockerfile 内容如下:
1 | FROM ubuntu:latest |
执行下面命令,创建测试镜像,并运行为容器:
1 | docker build -t test-centos2 -f Dockerfile . |
可以直接看到如下结果:
1 | top - 11:40:05 up 1 day, 19:34, 0 users, load average: 0.08, 0.05, 0.01 |
这个结果说明:
- 容器内确实起了2个进程
- 1号进程就是 /bin/sh
- 我们的可执行程序,不是1号进程,而是 /bin/sh 进程的子进程,进程ID为6
那么,我们停止此容器。
停止之前,先说一下,docker stop 会给容器发送SIG信号,让进程自行退出,如果进程不处理此信号,docker stop 会超时,然后 发送 kill 信号。默认超时时间是 10s ,我们执行如下命令:
1 | time docker stop -t 30 e58dc2887ef8 |
这个输出表明,docker stop 真的等了 30s 后才执行成功,也就是,/bin/sh 确实没有处理 SIG 信号。最后被 kill 掉了。
初期结论和说明
我们要尽量避免上面的现象,就要保证容器的1号进程,是应用的真正可执行程序,不能是 /bin/sh 进程,否则,Kubernetes 删除 pod,就会等待一段时间才能执行成功。另外,我们还是应该尽量使用官方推荐的 ENTRYPOINT 写法(数组写法)
使用centos作为基础镜像测试
但是,问题到此并没有结束。我们之前的 Dockerfile 是使用 ubuntu 作为基础镜像的。我们尝试,换为 centos 作为基础镜像试一下
1 | FROM centos:7.5.1804 |
然后,我们生成新镜像,并运行为容器:
1 | $ docker run -it test-centos3 |
我们能看到,只有1个进程,且进程ID为1。我们直接进入到容器内部看一下:
1 | [root@e8fa215e3ad6 /]# ps -ef |
从上,我们确实只能看到 top 进程为1号进程,并没有 /bin/sh 进程。而这个 Dockerfile 和之前的 Dockerfile,唯一的区别就是基础镜像不同。
结论
- /bin/sh 进程是无法处理 Linux 信号的。
- 不论使用哪种镜像做应用镜像的基础镜像,都要注意构建完应用镜像后测试一下,最好不要让 /bin/sh 成为 1 号进程。
- 尽量在 Dockerfile 中,为 ENTRYPOINT、CMD、RUN 使用数组方式写法。