优雅停止与强制停止 Docker 容器

优雅停止与强制停止 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
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容