docker容器优雅停机

docker容器优雅停机

我们部署在docker中的springboot程序在docker停止的时候并没有执行shutdownHook的操作,正常在本地idea停止springboot服务会看到一系列的shutdownHook操作日志

日志如下:

2021-05-13 11:12:29.253 INFO  218 [,] [SpringContextShutdownHook] o.s.s.c.ThreadPoolTaskScheduler Shutting down ExecutorService 'catalogWatchTaskScheduler'
2021-05-13 11:12:29.261 INFO  96 [,] [Thread-694] c.x.j.core.server.EmbedServer >>>>>>>>>>> xxl-job remoting server stop.
2021-05-13 11:12:29.272 INFO  87 [,] [xxl-job, executor ExecutorRegistryThread] c.x.j.c.t.ExecutorRegistryThread >>>>>>>>>>> xxl-job registry-remove success, registryParam:RegistryParam{registryGroup='EXECUTOR', registryKey='shop', registryValue='http://192.168.1.88:9999/'}, registryResult:ReturnT [code=200, msg=null, content=null]
2021-05-13 11:12:29.272 INFO  105 [,] [xxl-job, executor ExecutorRegistryThread] c.x.j.c.t.ExecutorRegistryThread >>>>>>>>>>> xxl-job, executor registry thread destory.
2021-05-13 11:12:29.272 INFO  125 [,] [SpringContextShutdownHook] c.x.j.core.server.EmbedServer >>>>>>>>>>> xxl-job remoting server destroy success.
2021-05-13 11:12:29.273 INFO  99 [,] [xxl-job, executor JobLogFileCleanThread] c.x.j.c.t.JobLogFileCleanThread >>>>>>>>>>> xxl-job, executor JobLogFileCleanThread thread destory.
2021-05-13 11:12:29.273 INFO  97 [,] [xxl-job, executor TriggerCallbackThread] c.x.j.c.t.TriggerCallbackThread >>>>>>>>>>> xxl-job, executor callback thread destory.
2021-05-13 11:12:29.273 INFO  127 [,] [Thread-693] c.x.j.c.t.TriggerCallbackThread >>>>>>>>>>> xxl-job, executor retry callback thread destory.
2021-05-13 11:12:29.361 INFO  94 [,] [SpringContextShutdownHook] o.s.c.c.s.ConsulServiceRegistry Deregistering service with consul: shop-192-168-1-88-8883
2021-05-13 11:12:29.379 INFO  218 [,] [SpringContextShutdownHook] o.s.s.c.ThreadPoolTaskExecutor Shutting down ExecutorService
2021-05-13 11:12:29.379 INFO  350 [,] [SpringContextShutdownHook] c.z.hikari.HikariDataSource HikariCP - Shutdown initiated...
2021-05-13 11:12:29.387 INFO  352 [,] [SpringContextShutdownHook] c.z.hikari.HikariDataSource HikariCP - Shutdown completed.

启动一个docker容器来验证问题

编写一个Dockerfile

FROM openjdk:8-jre-alpine
COPY ./build/libs/app.jar /app/app.jar
COPY ./docker/bootapp.sh /app/bootapp.sh
WORKDIR /app
EXPOSE 8080
ENTRYPOINT ["./bootapp.sh"]

创建启动脚本bootapp.sh

#!/bin/sh
echo 'Do something'
java -jar app.jar

docker stop 做了什么

docker官网对docker stop的描述:

The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL. The first signal can be changed with the STOPSIGNAL instruction in the container’s Dockerfile, or the --stop-signal option to docker run.

docker stop命令执行的时候,会先向容器中PID为1的进程发送系统信号SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认的10秒,会继续发送SIGKILL的系统信号强行kill掉进程。在容器中的应用程序,可以选择忽略和不处理SIGTERM信号,不过一旦达到超时时间,程序就会被系统强行kill掉,因为SIGKILL信号是直接发往系统内核的,应用程序没有机会去处理它。在使用docker stop命令的时候,我们唯一能控制的是超时时间,比如设置为20秒超时:

当我们在 shell 中给进程发送 SIGTERMSIGINT 信号的时候,这些进程往往都能正确的处理。但是在 docker 中却不灵了。这是因为在 docker 中,只会将 SIGTERM 等所有的 signal 信号发送给 PID 为 1 的进程,当我们 docker 中运行的进程的PID不是 1 时,就不会收到这样的信号。

如何优雅的关闭容器

通过前面的内容,我们已经了解了容器没有被优雅关闭的原因和可能导致的问题,接下来,我们来看看如何解决。

使目标进程成为 PID 1

既然 docker 只会将 sigal 发送给 PID 1 的进程,那就让我们的进程成为 PID 1 的进程就好了。

docker 的 exec 与 shell 模式

DockerfileENTRYPOINT 有两种写法,即 execshell

# exec form
ENTRYPOINT ["command", "param"]
# shell form
ENTRYPOINT command param

两者的区别在于:

  • exec 形式的命令会使用 PID 1 的进程
  • shell 形式的命令会被执行为 /bin/sh -c ,PID为1的进程是“/bin/sh”,command不会执行在 PID 1 上,也就不会收到 signal

所以,我们应该选择 exec 模式,让我们的程序成为 PID 1 进程。

ENTRYPOINT ["java", "-jar", "app.jar"]

exec 命令

exec 形式的 ENTRYPOINT 只能解决 无需任何准备工作就启动进程 的场景,而不能解决一些需要准备工作的复杂场景。

在这样的场景中,我们的 ENTRYPOINT 往往需要执行一个 shell 脚本:

ENTRYPOINT ["./bootapp.sh"]

然后在这个脚本中执行我们的准备工作,完成后再启动真正的进程。
比如上面的例子,做完准备后,启动 java 进程。
这时候,我们的 java 进程就无法成为 PID 1 进程。

image-20210513112239661

我们可以看到,java 进程的 PID 是 45,也就无法优雅退出了。

为了解决这个问题,我们可以使用 exec 命令来解决。这个命令的作用就是使用新的进程替代原有的进程,并保持 PID 不变
这就意味着我们可以在执行 java 命令的时候使用它,从而替换掉 PID 1 的 shell 脚本:

#!/bin/sh
echo "Do something"
exec java -jar app.jar

shell的内建命令exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行。

我们再来看一下容器中的进程:

image-20210513112212109

使用 exec 命令之后,我们无论是使用 ctrl+c 还是 docker stop 都能让进程接收到信号,执行相应的操作后退出:

image-20210513112401883

日志表明,java进程收到了 docker stop 发送的 SIGTERM 信号,并且正确的触发了相关操作,最后退出程序。

你可能感兴趣的:(docker,docker,docker优雅停机,docker,SIGTERM)