Docker优雅的关闭SpringBoot - 第310篇

一、准备工作

1.1 Spring Boot项目准备

       随便找一个项目进行打包,在target下就可以得到一个jar包,这里使用上一次的项目进行打包:spring-boot-shutdown-demo-0.0.1-SNAPSHOT.jar

 

1.2 Dockerfile

       要将我们的项目打包成docker image需要编写Dockerfile文件:

  1. # 基础镜像使用java
  2. FROM java:8
  3. # 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
  4. VOLUME /tmp
  5. # 将jar包添加到容器中并更名为app.jar
  6. COPY spring-boot-shutdown-demo-0.0.1-SNAPSHOT.jar app.jar
  7. # 运行jar包
  8. RUN bash -c "touch /app.jar"
  9. # 指定于外界交互的端口
  10. EXPOSE 8080
  11. # 配置容器,使其可执行化
  12. ENTRYPOINT ["java", "-jar", "/app.jar"]

 

       这里对于这个Dockerfile文件简单解释下,照顾下那些对于Docker不了解的小盆友:

(1)FROM java:8 是指Docker Hub上官方提供的java镜像,版本号是8也就是jdk1.8,有了这个基础镜像后,Dockerfile可以通过FROM指令直接获取它的状态——也就是在容器中java是已经安装的,接下来通过自定义的命令来运行Spring Boot应用。

(2)EXPOSE 容器暴露端口。

(3)ENTRYPOINT 应用启动命令 参数设定

 

1.3 上传文件

上传文件Dockerfile和spring-boot-shutdown-demo-0.0.1-SNAPSHOT.jar到服务器的同一目录下,我这里直接是Mac本地模拟的,直接放到同一个目录即可了。

 

1.4 构建docker镜像

       执行下面命令, 看好,最后面有个"."点!

docker build -t spring-boot-docker .

说明:

(1)build:构建镜像指令。

(2)-t 参数是指定此镜像的tag名。

 

1.5 查看构建的镜像

       使用如下命令:

docker images

可以看到有两个镜像java和spring-boot-docker:

1.6 启动运用

       使用命令:

docker run -d -p:8080:8080 spring-boot-docker

 

1.7 查看应用

       使用命令:

docker ps

1.8 查看日志

       使用命令:

docker logs -f -t --tail -100 7b2870e0dc0f

说明:7b2870e0dc0f是容器ID。

 

1.9 说明

       到这里准备工作大功告成,注意这里的日志是否输出我们在上一节可的那个打印信息。

 

二、Docker停止服务

2.1 使用暴露的地址进行关闭

       在上一节,我们编写了不少种使用地址的方式进行关闭,我们这里看看是否可以使用这样的方式进行关闭呐,随便访问一个关闭地址:

http://127.0.0.1:8080/shutdownBySpringApplication

结果:

  1. 控制台打印了信息:


o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

2020-06-03T09:36:38.733982409Z TerminalBean is destroyed

(2)使用docker ps查看容器是否还处于运行:已经关闭了。

 

2.2 使用docker stop

       当然我们使用docker之后,一般使用docker stop进行关闭容器:

docker stop 7b2870e0dc0f

观察日志的输出:此时控制台会执行preDestroy方法。

 

2.2.1 感觉一切都是这么顺利呐?

使用 docker stop 关闭容器时, 只有 init(pid 1)进程(这句话的意思就是初始化pid=1的进程)能收到中断信号, 如果容器的pid 1 进程是 sh 进程, 它不具备转发结束信号到它的子进程的能力, 所以我们真正的java程序得不到中断信号, 也就不能实现优雅关闭. 解决思路是: 让pid 1 进程具备转发终止信号, 或者将 java 程序配成 pid 1 进程.

需要说明的是, docker stop 默认是等待10秒钟, 这个时间有点太短了, 可以加 -t 参数, 比如 -t 30 等待30秒钟。

       但是我们上面操作了命名可以优雅退出呀,难道我们的pid=1的,我们查看下。

使用如下指令进入到容器:

docker exec -it 7b2870e0dc0f /bin/sh

然后使用ps进行查看下进程:

果然是这样子的,刚好我们的pid=1了。

2.2.3 方式一

       在docker镜像中强制 tini 作为 init(pid 1) 进程:

这种方式就是修改Dockerfile,配置tini:

  1. # Add Tini
  2. ENV TINI_VERSION v0.18.0
  3. ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
  4. RUN chmod +x /tini

 

2.2.4 方式二

       查看Spring Boot提供了一种方式:

https://spring.io/guides/topicals/spring-boot-docker

原文:

The exec form of the Dockerfile ENTRYPOINT is used so that there is no shell wrapping the java process. The advantage is that the java process will respond to KILL signals sent to the container. In practice that means, for instance, that if you docker run your image locally, you can stop it with CTRL-C. If the command line gets a bit long you can extract it out into a shell script and COPY it into the image before you run it. Example:

 

Dockerfile

FROM openjdk:8-jdk-alpine

VOLUME /tmp

COPY run.sh .

COPY target/*.jar app.jar

ENTRYPOINT ["run.sh"]

 

Remember to use exec java …​ to launch the java process (so it can handle the KILL signals):

 

run.sh

#!/bin/sh

exec java -jar /app.jar

 

用人话说下:

docker命令stop不会像springboot发送SIGTERM信号,导致只是关闭了容器。

通过运行exec命令,它将代替shell进程把SIGTERM传播到spring boot:

ENTRYPOINT [ "sh", "-c", "exec java -jar  /app.jar"]

购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287