平滑下线核心是“停得可控”:容器收到SIGTERM后主动拒新请求、完成存量请求、优雅释放资源;需应用响应信号、反向代理摘流、编排层协同(如K8s preStop+readinessProbe)、全链路验证。

在高并发服务场景下,Docker 容器的 docker stop 默认行为(发送 SIGTERM 后等待 10 秒再强制 SIGKILL)往往不够“平滑”,容易导致请求被中断、连接被重置、下游超时等问题。真正的平滑下线,核心不是“停得慢”,而是“停得可控”——让容器在收到停止信号后,主动拒绝新请求、完成正在处理的请求,并优雅释放资源。
理解容器停止生命周期与应用响应逻辑
Docker 停止流程本质是信号传递链:
docker stop → 发送 SIGTERM → 应用捕获并执行清理 → (可选)等待 → 超时则发 SIGKILL
关键在于:应用是否真正监听并响应了 SIGTERM?是否在收到信号后立即关闭监听端口、拒绝新连接?是否等待活跃请求完成?
常见误区是只改 --time 参数延长等待时间,却不改应用行为——这无法解决请求中断问题。
- Java 应用需注册 ShutdownHook 或使用 Spring Boot Actuator 的
/actuator/shutdown(配合反向代理前置拦截) - Node.js 需监听
process.on('SIGTERM', ...),关闭 HTTP server 并等待server.close()回调 - Go 应用推荐用
http.Server.Shutdown()配合 context 控制超时 - Nginx / Envoy 等反向代理需配置健康检查探针(如
livenessProbe+readinessProbe),确保下线前流量已从服务发现中摘除
结合编排工具实现“先摘流、再停容器”闭环
单纯靠 docker stop 无法协调服务发现与容器生命周期。生产环境应依赖编排层统一调度:
- Kubernetes 中,
preStop生命周期钩子 +readinessProbe失败延迟 +terminationGracePeriodSeconds协同工作:先调用preStop(如 curl 本地 /health/ready 接口置为不可读),等 endpoints controller 摘除实例,再发 SIGTERM - Docker Swarm 使用
update-parallelism和update-delay控制滚动更新节奏,配合内置 DNS 轮询和健康检查自动剔除不健康任务 - 自建 Consul + Registrator 方案,需在容器退出前主动调用
consul agent service deregister,并确保 deregister 有足够缓冲(如 sleep 2s)让客户端刷新缓存
验证平滑性:不能只看容器状态,要看真实请求流
容器退出成功 ≠ 下线平滑。必须观测三类指标:
- 服务端日志:确认收到 SIGTERM 后是否打印“graceful shutdown started”,以及最后一条请求日志的时间戳是否早于容器终止时间
- 负载均衡器/网关日志:检查下线期间是否有 502/503 或连接拒绝(Connection refused),以及健康检查失败到最终摘除的耗时是否合理(建议 ≤ 5s)
- 客户端监控:抽样追踪请求链路(如 OpenTelemetry),确认无因服务端 abrupt close 导致的 Span 错误或超时突增
建议在预发环境模拟滚动发布,用 ab 或 hey 持续压测,同时触发 docker stop,观察错误率是否始终趋近于 0。
补充技巧:小步快跑,降低单次影响面
即使机制完备,单机承载过高流量仍会放大风险。推荐组合策略:
- 限制单容器 QPS 上限(通过 HPA 或服务端限流),避免“一机扛千并发”
- 滚动更新时设置最小可用副本数(如
minAvailable: 80%),确保集群始终有足够容量承接流量 - 对关键服务启用“灰度分批”:先停 1 个实例 → 观察 30 秒 → 再停下一个,而非全量并发停止
- 容器镜像内嵌轻量 healthcheck 脚本(如检测本地端口是否已关闭),供运维脚本调用判断是否真正就绪退出










