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

 

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