优雅地停止 Docker 容器
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2800+ 小伙伴加入学习 ,欢迎点击围观
两年前我开始使用 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 信号传递给子进程。