优雅地停止 Docker 容器

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

两年前我开始使用 Docker 容器。您可以在这里找到我的 dockerfile 示例:https: //github.com/komljen/dockerfile-examples

这是我的测试场地,但在我开始使用 Docker 进行企业级应用程序部署后,我发现我做错了很多事情。其中之一是我如何在 Docker 中启动应用程序。

几乎我所有的 dockerfiles 在最后都有一些 bash 脚本来在容器内做一些小的改变并最终启动应用程序。我通常将此脚本添加到 Dockerfile 中的 CMD 指令中。我认为这没有任何问题,只是 Docker 停止了它应该的工作。

问题是,当您运行 bash 脚本时,它将获得 PID 1,而您的应用程序是一个 PPID 为 1 的子进程。当您运行 docker stop 时,Bash 不会将 SIGTERM 信号转发给您的应用程序。相反,容器将在 10 秒超时后被终止,这是 docker stop 命令的默认设置。这个超时是可以调整的。

有一种简单的方法可以使用 bash 脚本中的“exec”命令来处理这个问题。它将在不创建新进程的情况下替换 shell,并且您的应用程序将获得 PID 1。让我们先测试这两种情况。

出于测试目的,我将使用这个简单的 redis dockerfile:


 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

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

这是 start.sh 脚本,它将更改一些推荐的 redis 容器内核设置(docker 必须在特权设置为 true 的情况下启动):


 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

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

现在让我们构建并运行这个容器:


 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

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

然后查看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

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

好的,所以这是一个问题。现在让我们尝试停止这个 docker 容器:


 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

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

10 秒后容器被杀死,没有正常停止。如果我们检查日志,我们可以看到这一点。最后一条消息是 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

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

为了让它工作,我们需要更改 start.sh 脚本中的最后一行并重建图像。我们在 /usr/bin/redis-server 命令之前添加 exec:


 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

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

再次构建、启动并运行 ps -ef 命令:


 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

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

如您所见,redis 现在以 PID 1 运行,并且 docker stop 可以正常工作。我们先试试看,再查看docker日志。您应该在 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

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

这就是我将 exec 与 postgres 和 tomcat 容器一起使用的方式,其中进程不以 root 用户运行:


 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

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

由于 sudo 或 su,这里的进程不会以 PID 1 运行,但是 Docker stop 在这两种情况下都能完美运行。这样做的原因是 sudo 和 su 命令会将 SIGTERM 信号传递给子进程。