Docker Graceful Shutdown
2023-05-17 20:18:36 468
有几个前提
- 操作系统层面: 提供了 kill -9 (SIGKILL)和 kill -15(SIGTERM) 两种停机策略. SIGKILL 信号是一个不能被阻塞、处理或忽略的信号,它会立即终止目标进程. SIGTERM 信号是一个可以被阻塞、处理或忽略的信号,它也可以通知目标进程终止,但是它相对于 SIGKILL 信号来说更加温和,目标进程可以在接收到 SIGTERM 信号时进行一些清理操作,例如保存数据、关闭文件、释放资源等,然后再终止进程
- 语言层面: 在Java中, Runtime 类的 addShutdownHook 方法注册 shutdown hook. spring-boot已经实现了. 我们只要找个类实现java.io.Closeable接口的close方法, 再将其注册到容器中即可
- 在 Docker 中,执行 docker stop 命令时,它会向容器中的主进程 (pid=1)发送 SIGTERM 信号. 如果容器中的进程不响应 SIGTERM 信号,Docker 会等待一定的时间(默认为 10 秒),然后向容器中的所有进程发送 SIGKILL 信号,以强制结束容器中的进程. 如果我们需要修改 SIGTERM 信号等待的时间,可以在 docker run 命令中使用 --stop-timeout 参数来更改默认的停止超时时间(单位: s)
即
- 当使用kill, stop等命令时, 需要发送SIGTERM信号
- 你的进程要正常接收到信号
- 你的应用要正常处理信号
应用要正常处理信号
SpringBoot已经做了相关处理, 我们只要实现接口即可
实现org.springframework.context.SmartLifecycle接口, 实现getPhase/start/stop/isRunning方法, 通过getPhase方法定义优先级
@Override
public int getPhase() {
//在 WebServerGracefulShutdownLifecycle 那一组之后
return SmartLifecycle.DEFAULT_PHASE - 1;
}
进程要正常接收到信号
容器中只有pid=1的进程才能接收到信号, 所以要保证java的pid=1, dockerfile如下
...
ENV JAVA_OPTS=""
ENV APP_OPTS=""
# 如果用这个格式 sh -c start.sh 会导致pid不为1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar $APP_OPTS /root/user-web.jar"]
这样, 你的java在容器内的pid就是为1了
日志如下
08:59:04.373 [main ] INFO com.thy.backend.user.service.user.web.UserWebApplication : Starting UserWebApplication v1.0-SNAPSHOT using Java 17.0.2 with PID 1 (/root/user-web.jar started by root in /)
还可以使用https://github.com/krallin/tini/, 将java进程当成tini的子进程执行并执行信号转发
执行docker stop xxx
@Override
public void stop() {
// 输出
log.info("stop");
}