优雅停止与强制停止 Docker 容器
核心概念
Docker 容器停止有两种方式:优雅停止和强制终止。两者的核心区别在于是否给容器时间处理好收尾工作。
graph TB
subgraph 停止容器
STOP[docker stop]
KILL[docker kill]
end
subgraph docker stop 流程
STOP --> SIGTERM[发送 SIGTERM<br/>信号 15]
SIGTERM --> WAIT[等待 10 秒<br/>默认超时]
WAIT --> CHECK{容器优雅退出?}
CHECK -->|是| EXIT_OK[进程退出 ✅ 干净]
CHECK -->|否 超时| SIGKILL[自动发送 SIGKILL<br/>信号 9]
SIGKILL --> FORCE_EXIT[强制终止]
end
subgraph docker kill 流程
KILL --> SKILL[立即发送 SIGKILL<br/>信号 9]
SKILL --> IMMEDIATE_EXIT[立即终止<br/>无法处理收尾]
end
docker stop:优雅停止
# 优雅停止容器
docker stop myapp
# 停止流程:
# 1. 向容器主进程(PID 1)发送 SIGTERM
# 2. 等待容器优雅退出(默认最多 10 秒)
# 3. 如果 10 秒后还没退出,发送 SIGKILL
# 4. 容器退出
# 指定等待超时时间
docker stop --time=30 myapp
# 或简写
docker stop -t 30 myapp
# 给容器 30 秒完成收尾
从容应对 SIGTERM
应用需要正确处理 SIGTERM 信号,才能实现优雅停止:
Node.js 应用:
// app.js
const server = require('http').createServer((req, res) => {
res.end('Hello World');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
// 优雅关闭处理
function gracefulShutdown(signal) {
console.log(`Received ${signal}. Starting graceful shutdown...`);
server.close(() => {
console.log('HTTP server closed.');
// 关闭数据库连接
// 完成未处理的请求
// 清理临时文件
process.exit(0);
});
// 超过 30 秒强制退出
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 30000);
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
Python 应用:
# app.py
import signal, sys, time
def graceful_shutdown(signum, frame):
print(f"Received signal {signum}. Shutting down gracefully...")
# 关闭数据库连接
# 完成未处理的请求
sys.exit(0)
signal.signal(signal.SIGTERM, graceful_shutdown)
signal.signal(signal.SIGINT, graceful_shutdown)
# 应用主体
while True:
print("Working...")
time.sleep(1)
Go 应用:
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
server := &http.Server{Addr: ":8080"}
// 优雅关闭
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<-sigChan
fmt.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.Shutdown(ctx)
}()
server.ListenAndServe()
}
docker kill:强制终止
# 强制终止(立即发送 SIGKILL)
docker kill myapp
# 发送自定义信号(不一定是 SIGKILL)
docker kill --signal=SIGTERM myapp
# 这实际上等同于 docker stop(但不会自动超时后补 SIGKILL)
docker kill --signal=SIGHUP nginx
# 发送 SIGHUP,nginx 会重载配置而不退出
kill 的使用场景
# 1. 容器卡死,stop 在超时后也会 kill
docker kill stuck-container
# 2. 批量强制停止
docker kill $(docker ps -q)
# 3. 发送自定义信号
docker kill --signal=USR1 myapp # 某些应用用 USR1 做日志轮转
比较:stop vs kill
| 行为 | docker stop | docker kill |
|---|---|---|
| 默认信号 | SIGTERM (15) | SIGKILL (9) |
| 超时机制 | 10 秒超时后发 SIGKILL | 立即终止 |
| 进程响应 | 可以捕获并处理 | 无法捕获 |
| 优雅退出 | ✅ 可能 | ❌ 不可能 |
| 数据完整性 | ✅ 有保障 | ❌ 可能丢失 |
| 速度 | 慢(等待超时) | 极快 |
停止方式的决策树
graph TB
START[需要停止容器] --> Q1{容器有状态?}
Q1 -->|有(数据库/缓存)| STOP[docker stop<br/>给时间flush数据和断开连接]
Q1 -->|无(无状态应用)| Q2{应用卡死?}
Q2 -->|是| KILL[docker kill<br/>立即终止]
Q2 -->|否| STOP
STOP --> CHECK{做了 SIGTERM 处理?}
CHECK -->|是| GRACEFUL[优雅停止 ✅]
CHECK -->|否 / 超时| FORCE[自动转为强制终止]
优雅停止的最佳实践
1. 在 Dockerfile 中正确使用 CMD
# ❌ shell 形式——不会转发信号给应用
CMD node app.js
# 实际执行: /bin/sh -c "node app.js"
# SIGTERM 发给 shell,不是 node
# ✅ exec 形式——信号直接给应用
CMD ["node", "app.js"]
# ✅ 使用 tini 作为 init 进程,正确处理信号
FROM node:18-alpine
# 安装 tini(tiny init)
RUN apk add --no-cache tini
COPY . .
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "app.js"]
2. 测试优雅停止
# 测试容器是否正确处理 SIGTERM
# 1. 启动容器
docker run -d --name myapp myapp:test
# 2. 发送 SIGTERM(模拟 stop)
docker kill --signal=SIGTERM myapp
# 3. 查看日志确认优雅退出
docker logs myapp
# 应看到: "Received SIGTERM. Graceful shutdown..."
3. 设置合理的 stop 超时
# 对需要长时间收尾的应用,增加超时时间
docker stop -t 60 myapp
# 给 60 秒完成收尾
# 在 docker-compose 中配置
# docker-compose.yml
services:
myapp:
image: myapp:latest
stop_grace_period: 60s
stop_signal: SIGTERM
不同信号的含义
# 常用信号
SIGHUP (1) # 重载配置
SIGINT (2) # Ctrl+C 发送
SIGTERM (15) # docker stop 默认信号 优雅停止
SIGKILL (9) # docker kill 默认信号 强制终止
SIGUSR1 (10) # 用户自定义(如日志轮转)
SIGUSR2 (12) # 用户自定义
# 在容器中查看支持的信号
docker exec myapp kill -l
面试追问
问:docker stop 等待 10 秒怎么来的?超出会发生什么?
答:10 秒是 Docker 默认的停止超时。如果容器在 10 秒内没有响应 SIGTERM 退出,Docker 会自动发送 SIGKILL。可以通过 -t 参数调整。
问:什么是”僵尸进程”问题?为什么建议用 tini?
答:在没有 init 系统的容器中,孤儿进程不会被正确回收,可能变成僵尸进程。tini 作为 init 进程(PID 1),可以正确回收子进程并转发信号。
总结
# 停止容器的黄金规则
# 1. 默认用 docker stop
# 2. 给应用足够的时间处理 SIGTERM
# 3. 应用响应 SIGTERM 实现优雅关闭
# 4. 只有卡死才用 docker kill
# 5. 用 tini 或 exec 格式 CMD 确保信号正确传递
一句话总结:docker stop 是劝退(给时间收拾行李),docker kill 是拖走(锁喉立即执行)——对正经应用用 stop,对耍流氓的应用用 kill。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容