由于应用镜像是由spring boot制作的,在关注docker 容器停止之前,先看下Java应用是如何处理程序停止的。

java shutdownhook

在java程序停止前,我们可能会需要一些清理工作,如关闭数据库连接池,执行一些反注册等。Runtime的addShutdownHook方法给我们提供了这样一个机制,通过这个方法,我们可以告诉JVM,在收到停止信号时,执行一些我们自定义的逻辑

/**

* Registers a new virtual-machine shutdown hook.

*

*

The Java virtual machine shuts down in response to two kinds

* of events:

*

*

*

*

The program exits normally, when the last non-daemon

* thread exits or when the {@link #exit exit} (equivalently,

* {@link System#exit(int) System.exit}) method is invoked, or

*

*

The virtual machine is terminated in response to a

* user interrupt, such as typing ^C, or a system-wide event,

* such as user logoff or system shutdown.

*

*

*

*

A shutdown hook is simply an initialized but unstarted

* thread. When the virtual machine begins its shutdown sequence it will

* start all registered shutdown hooks in some unspecified order and let

* them run concurrently. When all the hooks have finished it will then

* run all uninvoked finalizers if finalization-on-exit has been enabled.

* Finally, the virtual machine will halt. Note that daemon threads will

* continue to run during the shutdown sequence, as will non-daemon threads

* if shutdown was initiated by invoking the {@link #exit exit}

* method.

*

*

In rare circumstances the virtual machine may abort, that is,

* stop running without shutting down cleanly. This occurs when the

* virtual machine is terminated externally, for example with the

* SIGKILL signal on Unix or the TerminateProcess call on

* Microsoft Windows. The virtual machine may also abort if a native

* method goes awry by, for example, corrupting internal data structures or

* attempting to access nonexistent memory. If the virtual machine aborts

* then no guarantee can be made about whether or not any shutdown hooks

* will be run.

*

* @see #removeShutdownHook

* @see #halt(int)

* @see #exit(int)

* @since 1.3

*/

public void addShutdownHook(Thread hook)

此方法在程序正常终止或者jvm收到中断interrupt、停止信号terminate时被触发

一个程序可以注册多个shutdown hook,当JVM开始停止时,这些shutdown hooks会同时执行,相互之间没有次序

序号

执行命令

结果

说明

1

kill -9

不能触发

发送的是SIGKILL

2

kill

触发

默认的是kill -15 发送SIGERM

3

ctrl+c

触发

发送的是SIGINT

4

正常结束

触发

5

oom

触发

docker 容器内部

将java程序做成docker镜像,以容器形式执行时,我们不能直接给容器内部的java进程发送信号,此时只能通过docker命令来操作正在运行的容器。根据docker stop命令的描述,

The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL.

docker只会给容器内的主进程发送信号,所以为了使java进程能收到停止信号,触发shutdown hooks,java 进程在容器内只能作为主进程(1号进程)运行。

可以通过以下方式让Java进程作为主进程在容器中运行。

在Dockerfile中通过 CMD作为容器启动的默认命令,如:

FROM openjdk:8u212-jdk-alpine

ADD ***.jar /home

WORKDIR /home

CMD java -jar ***.jar

在Dockerfile中用exec格式的ENTRYPOINT作为容器启动的默认命令,在ENTRYPOINT对应的脚本内部,用exec启动java程序,如:

Dockerfile:

FROM openjdk:8u212-jdk-alpine

ADD spring-boot-shutdownhook-1.0-SNAPSHOT.jar /home

COPY docker-entrypoint.sh /home/docker-entrypoint.sh

WORKDIR /home

RUN chmod +x docker-entrypoint.sh

ENTRYPOINT ["./docker-entrypoint.sh"]

docker-entrypoint.sh:

#!/bin/sh

#do something

exec java -jar spring-boot-shutdownhook-1.0-SNAPSHOT.jar

linux exec 命令的意思是在当前进程内执行,并且exec命令后面的 指令就不在执行了

ENTRYPOINT command param1 param2 和 ENTRYPOINT ["/bin/sh", "param1"] 都是shell模式,pid 为1的进程都是shell,不能使Java进程收到停止信号

序号

执行命令

结果

说明

1.

docker rm -f

不能触发

直接发送SIGKILL

2.

docker stop

触发

3.

docker stack rm

触发

4.

docker service rm

触发

5.

docker service scale

触发

缩减实例个数的情况下

6.

docker service update

触发

造成实例停止的更新

If one of the Pod's containers has defined a preStop hook, the kubelet runs that hook inside of the container. If the preStop hook is still running after the grace period expires, the kubelet requests a small, one-off grace period extension of 2 seconds.

Note: If the preStop hook needs longer to complete than the default grace period allows, you must modify terminationGracePeriodSeconds to suit this.

The kubelet triggers the container runtime to send a TERMsignal to process 1 inside each container.

Note: The containers in the Pod receive the TERM signal at different times and in an arbitrary order. If the order of shutdowns matters, consider using a preStop hook to synchronize.

只有容器中的1号进程能收到SIGTERM信号,所以为了在k8s环境下,使Java进程执行shutdown hooks,需保证在容器中的Java进程是主进程

在k8s环境下,还可以通过preStop这个hook来在主进程收到TERM之前做一些事情,如果我们的Java进程在容器中不是主进程,在k8s环境下,我们可以通过如下的preStop来触发Java进程的shutdown hook

***

containers:

- image: myimage:test

lifecycle:

preStop:

exec:

command: ["/bin/sh","-c","ps|grep java|grep -v grep| awk '{ print $1 }' | xargs -I{} kill {}]

***

在preStop这个hook中,通过kill ${java 进程PID (kill 默认发送 15 SIGTERM 信号)

补充

当Java进程在容器中是1号进程时,虽然能收到SIGTERM信号,自动执行shutdown hooks,但是,利用 jmap、jstack等工具对1号进程(Java进程)进行分析时,会出现如下错误

chengaofeng@chengaofeng target % docker exec -it b6a45781b81f sh

/home # ps

PID USER TIME COMMAND

1 root 0:22 java -jar spring-boot-shutdownhook-1.0-SNAPSHOT.jar

37 root 0:00 sh

42 root 0:00 ps

/home # jmap -dump:format=b,file=dump.bin 1

1: Unable to get pid of LinuxThreads manager thread

如果想让Java进程既不是1号进程,也要能收到信号,可以利用tini来实现 ,通过让tini运行在1号进程,Java作为tini的子进程来实现

Dockerfile:

FROM openjdk:8u212-jdk-alpine

ADD spring-boot-shutdownhook-1.0-SNAPSHOT.jar /home

COPY docker-entrypoint.sh /home/docker-entrypoint.sh

WORKDIR /home

RUN chmod +x docker-entrypoint.sh

RUN apk add --no-cache tini

ENTRYPOINT ["/sbin/tini", "--","./docker-entrypoint.sh"]

docker-entrypoint.sh:

#!/bin/sh

echo "hello"

exec java -jar spring-boot-shutdownhook-1.0-SNAPSHOT.jar

启动后进入容器

chengaofeng@chengaofeng target % docker exec -it 7b04dd056973 sh

/home # ps

PID USER TIME COMMAND

1 root 0:00 /sbin/tini -- ./docker-entrypoint.sh

7 root 0:21 java -jar spring-boot-shutdownhook-1.0-SNAPSHOT.jar

37 root 0:00 sh

43 root 0:00 ps

/home # jstack 7

2020-10-21 03:31:36

Full thread dump OpenJDK 64-Bit Server VM (25.212-b04 mixed mode):

"Attach Listener" #30 daemon prio=9 os_prio=0 tid=0x000056519338b800 nid=0x38 waiting on condition [0x0000000000000000]

java.lang.Thread.State: RUNNABLE

执行docker stop 命令停止容器对应的日志

2020-10-21 03:34:16.845 INFO 7 --- [ Thread-0] o.example.shutdownhook.ShutdownHookApp : app shutdown hook executed

对应的代码

@SpringBootApplication

@Slf4j

public class ShutdownHookApp {

public static void main(String[] args) {

Runtime.getRuntime().addShutdownHook(new Thread(()->{

log.info("app shutdown hook executed");

}));

SpringApplication.run(ShutdownHookApp.class, args);

}

}

需要让Java进程时tini的直接子进程

总结

使容器内Java进程能收到停止信号有以下三种方式

通过 CMD java -jar 直接运行

以exec格式启动 ENTRYPOINT,在 ENTRYPOINT对应的脚本中,以 exec java -jar 形式启动java进程

以 exec 形式启动ENTRYPOINT,command用tini,在 ENTRYPOINT对应的脚本中,以 exec java -jar 形式启动java进程

其中前两种都是让Java进程作为一号进程运行,第三种以tini作为一号进程,Java作为tini的子进程

docker停止信号java_docker容器优雅停止相关推荐

  1. docker拉镜像、创建容器、停止容器、移除容器、构建镜像

    导语:最近发现好多面试者不会docker,虽然不是开发必备,但是偶尔会弄个demo,或者解析个什么自己搭建项目也很方便,以我微薄的docker经验在这里留下记录并供自己以后巩固 以下内容可学会dock ...

  2. docker删除所有容器和镜像

    docker删除所有镜像: docker rmi -f $(docker images -qa) docker删除所有容器: 停止容器 docker stop $(sudo docker ps -a ...

  3. 【Docker系列】容器快速上手

    Docker CLI 命令行介绍 Docker Version Windows (Intel芯片) Server 的 OS/Arch: linux/amd64 是因为Windows内置的hyper-V ...

  4. 如何优雅地删除Docker镜像和容器(超详细)

    一.前言   大家是怎么删除Docker中的镜像和容器的呢,有没有考虑过如何优雅地删除呢?本教程详细指导如何在优雅地删除Docker容器和镜像.如需了解如何在Centos7系统里面安装Docker,可 ...

  5. docker容器优雅停机

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

  6. docker常规操作——启动、停止、重启容器实例

    本系列目录请看这里 https://blog.csdn.net/michel4liu/article/details/80819510 前几篇我们已经掌握了docker容器实例的运行,接下来我们就来了 ...

  7. 【docker系列】容器自启动与守护进程停止后容器保活

    本文为大家介绍容器自启动以及docker 守护进程挂掉或者docker升级的情况下,如何保证容器服务的正常运行.主要包含三个部分内容 文章目录 一.守护进程开机自启 二.容器自启动 重启策略说明 三. ...

  8. Docker 退出容器不停止容器运行

    Docker 退出容器不停止容器运行 通常我们使用Ctrl+C退出正在执行的操作 在docker容器中使用Ctrl+D即可退出容器,但是这样会让容器停止运行. 如果想退出容器但又不想让容器停止,使用C ...

  9. Docker---(9)Docker中容器无法停止无法删除

    注:博主系统是ubuntu. 问题:mysql容器如法停止,无法删除,也无法连接.docker stop,docker kill等命令都无效. 处理办法: 1.停止所有的容器 docker stop ...

最新文章

  1. bash的常见命令及文章查看命令
  2. CODEVS 3288 积木大赛
  3. 【鸿蒙 HarmonyOS】创建 Java 语言 HarmonyOS 手机应用 ( 首次进入 DevEco Studio 配置环境 | 创建 Java 手机工程 | 鸿蒙工程代码目录简介 )
  4. C++用递归方式实现在对不更改随机数组的情况下查找最大值
  5. asp.net拦截器
  6. SSD浅层网络_目标检测SSD
  7. 微软全球副总裁给你发了一张Connect 2016专属邀请卡:信仰再充值!Connect 2016技术大会在线直播!
  8. python类成员变量_Python 类变量和成员变量
  9. java异或_JAVA面试必备之HashMap必会点
  10. python如何生成随机数_python如何生成随机数
  11. Heritrix 3.1.0 源码解析(二十八)
  12. 机器人码垛搬运编程程序_一条指令搞定机器人搬运程序
  13. java网上书店模板_网上书店静态网站模板
  14. 手机usb计算机连接不能选择,USB调试 是灰色按钮,无法点击,现在手机无法与电脑连接。...
  15. 支教笔记 我在泸定的那十天
  16. 曼哈顿距离最小生成树(树状数组)
  17. 运维简历怎么写项目描述_职场小白怎么写简历?一份好简历=成功一半
  18. 3G上网:按时长计费是运营商的“最佳选择”
  19. 多种分类以及模型评估
  20. H5项目如何打包成APP

热门文章

  1. 阿里云人脸识别公测使用说明
  2. 借助 Cloud Toolkit 快速创建 Dubbo 工程
  3. 技术选型:Sentinel vs Hystrix
  4. 不用跑项目,组件效果所见即所得,绝了!
  5. 刷爆了!这份被程序员疯传的Python神作牛在哪?
  6. android笔试添加自定义服务,Android之Listview(item为单选题)自定义adapter,像考试时前面的10几道单选题的实现...
  7. Centos7 安装Go环境
  8. 查询某一支接口,指定时间段的数据 按分钟排序
  9. form表单提交,后台实体类接收转义问题
  10. Spring Boot2 整合 Shiro ,两种方式全总结!