如何优雅的停止容器

1. docker容器和PID 1

当在容器中执行一个bash脚本时,它就是个1号进程,应用进程就是这个1号进程的
子进程,这是bash的问题,因为它不会将终止信号SIGTERM发送到容器中的应用进程。
相反,在收到终止信号后,docker将会在10s后将容器kill掉。我们可以调整这个
时间,这个时间的存在主要是为了尽最大可能的让应用能够优雅的停止。

为了解决上述问题,一个比较简单的做法就是在bash脚本中使用exec命令。
exec将会取代shell并且不会创建新的进程,并且应用也将会是1号进程

2. 示例

假如我们有一个Dockerfile文件,它将会在容器中的运行redis服务。内容如下:

FROM ubuntu:trusty
ENV DEBIAN_FRONTEND noninteractive

RUN \
  apt-get update && \
  apt-get -y install \
          software-properties-common && \
  add-apt-repository -y ppa:chris-lea/redis-server && \
  apt-get update && \
  apt-get -y install \
          redis-server && \
  rm -rf /var/lib/apt/lists/*

COPY start.sh start.sh
RUN chmod +x start.sh

EXPOSE 6379

RUN rm /usr/sbin/policy-rc.d
CMD ["/start.sh"]

start.sh中的内容如下:

#!/usr/bin/env bash

# Disable THP Support in kernel
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# TCP backlog setting (defaults to 128)
sysctl -w net.core.somaxconn=16384
#---------------------------------------------------------------
/usr/bin/redis-server

下面我们将会生成镜像并运行容器:

docker build -t my/redis .
docker run -d --privileged --name test my/redis

检查运行的容器进程:

docker exec test ps -ef
  UID        PID  PPID  C STIME TTY          TIME CMD
  root         1     0  0 13:20 ?        00:00:00 bash /start.sh
  root         6     1  0 13:20 ?        00:00:00 /usr/bin/redis-server *:6379

正如我们看到的,这个redis服务的进程号为PID6。这种情况下,当我们执行docker stop test
时我们将无法优雅的停止容器。10s后容器将会被kill掉。执行docker logs test
时,最新的消息为Ready to accept connection,这意味着redis并没有收到终止
信号。

最简单的实现优雅停止redis容器的方法就是将start.sh的最后一行
/usr/bin/redis-server改为exec /usr/bin/redis-server

重新构建镜像并启动容器,然后再检查进程:

docker exec test ps -ef
  UID        PID  PPID  C STIME TTY          TIME CMD
  root         1     0  1 13:24 ?        00:00:00 /usr/bin/redis-server *:6379

正如我们看到的,现在redis进程已经是1号进程了,并且执行docker stop时也能
正常优雅的stop掉了redis服务。再次检查日志发现,redis log: Received SIGTERM scheduling shutdown...

3. 其它

在上面的例子中,redis进程是以root权限运行的,但这并不是一个很好的实践。
下面这几个例子中,我是以非root权限运行Postgres和Tomcat容器,

exec sudo -E -u tomcat7 ${CATALINA_HOME}/bin/catalina.sh run
exec su postgres -c "${POSTGRES_BIN} -D ${PGDATA} -c config_file=${CONF}"

在这些例子中,应用进程并不是PID 1的进程,但是docker stop 却可以优雅的
停止他们,因为我使用了sudo和su命令,这两个命令都会将SIGTERM信号发送给
子进程,这一点与Bash不一样。

posted @ 2020-08-03 16:18  大海星  阅读(690)  评论(0编辑  收藏  举报