1. 问题背景
在 Docker 环境中运行 Spring Boot 应用时,为了确保应用能够优雅关机(Graceful Shutdown),需要正确配置多个组件。错误的配置可能导致应用无法正常接收关闭信号,从而无法执行优雅关机流程。
今天才发现服务器上配置有问题没支持, 本地是没问题的。。。最后发现是配置有问题, 这里总结一下。
日志中出现INFO o.s.b.w.e.tomcat.GracefulShutdown - Commencing graceful shutdown. Waiting for active requests to complete
表示成功(我使用的是 tomcat 容器, 其他的日志内容可能不太一样)。
2. Docker 关机流程
Docker 容器的关闭过程如下:
- Docker 向容器的 1 号进程(PID 1)发送 SIGTERM 信号
- 等待
stop_grace_period
时间(默认 10 秒) - 如果进程还未退出,发送 SIGKILL 信号强制终止
3. 常见配置错误
3.1 Dockerfile ENTRYPOINT 错误写法
1 | # ❌ 错误写法(shell 形式) |
我遇到的问题就是写了第一种导致 PID 不是 1,信号没有传递。
shell 形式的问题:
- 命令会通过
/bin/sh -c
执行 - Java 进程不是 PID 1
- 信号会被 shell 进程接收,而不是直接传递给 Java 应用
- 可能导致优雅关机失效
4. 正确配置方法
4.1 Spring Boot 配置
1 | # application.properties/yaml |
4.2 Docker Compose 配置
1 | services: |
4.3 Dockerfile 配置
基础版本:
1 | ENTRYPOINT ["java", "-jar", "app.jar"] |
使用 tini 的增强版本:
1 | RUN apt-get update && apt-get install -y tini |
4.4 JVM 配置优化
1 | ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:+ExitOnOutOfMemoryError", "-jar", "app.jar"] |
我用的是 java21 里 UseContainerSupport
写不写都可以,这个参数很老了,除非你用老版本的 java8,那可能还是需要开启的。
5. 调试方法
5.1 查看容器日志
1 | docker-compose logs your-service |
Spring Boot 会把 graceful shutdown 的内容打出来
5.2 Spring Boot 日志配置
1 |
|
5.3 Docker Compose 命令
1 | # 使用详细日志模式关闭 |
6. 最佳实践建议
- 总是使用 exec 形式的 ENTRYPOINT/CMD
- 配置足够的 stop_grace_period 时间
- 确保 stop_grace_period 大于 Spring Boot 的 shutdown timeout
- 考虑使用 tini 等初始化系统
- 添加适当的日志来监控关机过程
- 在开发环境充分测试关机流程
7. 关键时间设置建议
- Spring Boot shutdown timeout: 20s
- Docker stop_grace_period: 30s
- Docker Compose down timeout: 30s
第二个和第三个是同一个东西,第三个用-t 会覆盖配置文件中的 stop_grace_period,临时设置用
这样的配置可以确保:
- 应用有足够时间处理现有请求
- Docker 不会过早发送 SIGKILL 信号
- 避免资源泄露和数据丢失
通过以上配置和最佳实践,可以确保 Spring Boot 应用在 Docker 环境中能够正确执行优雅关机流程。