Docker优雅的关闭SpringBoot - 第310篇

相关历史文章(阅读本文之前,您可能需要先看下之前的系列👇

国内最全的Spring Boot系列之三

布隆过滤器Bloom Filter竟然让我解决了一个大厂的问题 - 第305篇

100G的文件如何读取 - 第306篇

100G的文件如何读取续集 - 第307篇

Java语言的优雅停机 - 第308篇

SpringBoot 优雅停止服务的几种方法 - 第309篇

 

师傅:今天原本是要徒儿讲解下《字节码插桩》的,但是我们还是先回答下粉丝的问题“在Docker 或 kubernetes 容器内 是那种方式shutdown呢?”

悟纤:粉丝的问题要认真回答。古有得民心者得天下,今有得粉丝者得天下。

师傅:我吼,看出来,徒儿你的悟性这么高呐。

 

悟纤:那是,看我这名称‘悟纤’,其中悟字就道出了一切。

 

师傅:你这… 给你竿子就往上爬。

悟纤:不爬白不爬。

师傅:看来贱者无敌还是很对的,见过脸皮厚的,就没见过脸皮像你这么厚的。

 

悟纤:那师傅,那你还得感谢我呢,让你感受了不一样的人生。

师傅:…….

 

一、准备工作

1.1 Spring Boot项目准备

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

 

1.2 Dockerfile

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

# 基础镜像使用java

FROM java:8



# 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp

VOLUME /tmp



# 将jar包添加到容器中并更名为app.jar

COPY spring-boot-shutdown-demo-0.0.1-SNAPSHOT.jar app.jar



# 运行jar包

RUN bash -c "touch /app.jar"



# 指定于外界交互的端口

EXPOSE 8080



# 配置容器,使其可执行化

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:

# Add Tini

ENV TINI_VERSION v0.18.0

ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini

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"]

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章