Docker容器停止原理与安全批量终止实践
1. 为什么“一键停所有容器”不是偷懒,而是专业习惯的起点
在本地开发环境里,我每天至少要启动、调试、重启五六个容器:一个 PostgreSQL、两个 Node.js API 服务、一个 Redis 缓存、一个 Nginx 网关,外加一个临时跑数据迁移的 Python 工具容器。最开始我也是一条命令一条命令地敲 docker stop api-v2 、 docker stop db 、 docker stop cache ……直到某天下午三点,我正赶着提交 PR,手一滑把 docker stop api-v2 敲成了 docker stop api-v1 ——而 api-v1 正在跑一个关键的集成测试,结果测试断连、日志中断、CI 流水线直接报红。排查了 47 分钟才发现是自己误停了容器。那天之后,我删掉了所有单容器 stop 命令的历史记录,转而只用一套可复现、可验证、带安全缓冲的批量停止机制。
这不是为了图快,而是因为 Docker 的容器生命周期管理,本质上是一场与信号、时序、状态和资源释放的精密协作。你敲下 docker stop 的那一刻,不是在发一个“关机指令”,而是在向操作系统发起一次 受控的进程协商请求 :请主进程优雅退出、请数据库刷写事务日志、请 HTTP 服务器拒绝新连接并等待旧连接完成、请缓存层同步脏数据到磁盘……这个过程有默认的 10 秒窗口,但窗口长短、信号类型、是否强制终止,全取决于你是否理解底层逻辑,并主动干预。
所以,“停止所有容器”这件事,表面看是 CLI 技巧,内核却是 DevOps 工程师对系统行为边界的清晰认知。它解决的从来不是“多按几次回车”的效率问题,而是 避免状态不一致、防止资源泄漏、规避数据损坏、保障本地环境可重现性 这四类高频事故。适合谁?适合所有每天和容器打交道的人——前端开发者调联调环境时需要秒级重置;后端工程师做数据库迁移前必须确保无残留连接;SRE 在故障复现时需要干净的基线;甚至实习生第一次拉下项目代码,也需要一条不会出错的初始化/清理命令。关键词就三个: 信号(SIGTERM/SIGKILL)、过滤(filter)、可审计(auditability) 。接下来我会带你从内核信号讲起,一层层拆解每种方法背后的取舍,告诉你哪条命令该在什么场景下用,以及——更重要的是——哪条命令你永远不该在生产配置里抄来就用。
2. 容器终止的本质:信号、时序与应用层的契约
2.1 Docker 是怎么“请”容器退出的?不是命令,是谈判
很多人以为 docker stop 就像物理关机按钮,按下就断电。完全错误。Docker 的停止机制,本质是 Unix 进程间通信的一次标准实践:它不杀进程,它 发信号 。更准确地说,它向容器内 PID 1 的主进程发送一个软件中断信号(signal),由该进程自行决定如何响应。这个设计哲学源于 Linux 内核的进程管理模型——信号是操作系统提供给用户态程序的、最轻量也最可控的异步通知机制。
我们来看真实执行链路:
- 你运行
docker stop my-web-app - Docker Daemon 接收到请求,查找容器对应的
containerd-shim进程,再通过runc找到该容器的 PID 1(比如node server.js或java -jar app.jar) - Daemon 向该 PID 1 进程发送
SIGTERM(信号编号 15) - 应用进程捕获
SIGTERM,执行预设的 shutdown handler:关闭 HTTP server、断开数据库连接、flush 日志缓冲区、保存内存状态…… - 如果 10 秒内进程正常退出(exit code 0),Docker 认为任务成功
- 如果超时未退出,Daemon 再发
SIGKILL(信号编号 9),此时内核强制终止进程,不给任何收尾机会
提示:
SIGTERM是“礼貌敲门”,SIGKILL是“破门而入”。前者可被捕获、可延迟、可拒绝(虽然不推荐);后者不可捕获、不可忽略、立即生效。这是所有可靠服务必须区分对待的两条生命线。
我见过太多团队踩坑,根源就是没在应用代码里写 SIGTERM 处理器。比如一个 Express 应用,如果只写了 app.listen(3000) ,没加:
process.on('SIGTERM', () => {
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
});
那么 docker stop 发来的 SIGTERM 就像石沉大海,10 秒后直接 SIGKILL ,正在处理的请求被粗暴中断,数据库连接池未释放,日志最后一行可能卡在缓冲区里——这些都不是 Docker 的错,是应用层没履行与操作系统的契约。
2.2 默认 10 秒够吗?时间不是常数,是业务特征的映射
Docker 默认 --time=10 不是拍脑袋定的。它平衡了“等待太久影响效率”和“等太短导致强制杀进程”之间的张力。但这个值对你的服务是否合理?必须结合业务特征判断:
- Web API 服务 :通常 3~5 秒足够。HTTP server 关闭监听端口、等待活跃连接完成,一般毫秒级。
- 数据库迁移容器 :可能需要 60~300 秒。一个
ALTER TABLE在百万行表上可能锁表几十秒,强制SIGKILL会导致表结构损坏。 - 消息队列消费者 :需等待当前消息处理完毕并 ACK,若消费逻辑复杂(如调用外部 API),10 秒远远不够。
- 批处理作业容器 :比如 ETL 任务,可能运行数小时,
docker stop对它根本不适用——应该用docker kill --signal=SIGUSR2触发自定义暂停,而非终止。
我在线上环境曾将一个 Kafka 消费者服务的 --time 从 10 秒调到 120 秒,原因很简单:它的 onMessage() 方法里包含一次远程 S3 文件上传,网络抖动时上传可能卡住 90 秒。之前用默认值,每次部署都触发 SIGKILL ,导致消息重复消费(因为未 ACK)。调整后,99.8% 的停止都走 SIGTERM 优雅路径。
计算合理 timeout 的经验公式:
timeout = (最长单次业务操作耗时 × 2) + 网络波动缓冲(建议 10~30 秒) + 进程自身清理耗时(日志 flush、连接池 close 等,通常 <5 秒)
例如:一个处理 PDF 渲染的微服务,单次渲染平均 8 秒,P99 达 22 秒,网络调用最多 15 秒,则建议 --time=60 。
2.3 docker stop vs docker kill :不是“慢”和“快”的区别,是“可控”和“失控”的分界
这两条命令常被混用,但它们代表两种截然不同的运维哲学:
| 维度 | docker stop |
docker kill |
|---|---|---|
| 核心意图 | 请求优雅退出,尊重应用生命周期 | 强制终止,绕过所有应用层逻辑 |
| 默认信号 | SIGTERM (可配置) |
SIGKILL (不可配置) |
| 超时机制 | 有,可调( -t ) |
无,立即生效 |
| 适用阶段 | 日常维护、滚动更新、CI/CD 流水线 | 容器僵死( ps aux 显示 <defunct> )、 docker stop 超时失败、紧急故障隔离 |
| 风险等级 | 低(前提是应用有 SIGTERM handler) | 高(必然丢失未持久化状态、可能损坏文件系统) |
注意:
docker kill支持--signal,但SIGKILL是唯一保证生效的信号。其他信号(如SIGINT、SIGUSR1)能否生效,完全取决于容器内进程是否注册了对应 handler。没有 handler 的信号,进程会直接忽略。
真实案例:我们有个 Java Spring Boot 服务,开发时没加 @PreDestroy 或 Runtime.addShutdownHook ,上线后每次 docker stop 都超时,最后 SIGKILL 。结果是:HikariCP 连接池未关闭,数据库侧积累大量 idle in transaction 连接,三天后 PostgreSQL 连接数打满,整个集群雪崩。解决办法不是换 docker kill ,而是补上 shutdown hook,并将 --time 设为 30 秒。
3. 四种实操方案深度解析:从基础到生产就绪
3.1 基础方案: docker stop $(docker ps -q) —— 快速但需警惕的“万能钥匙”
这是最广为人知的命令,也是新手最容易误用的。我们来逐层拆解它的真实行为:
# 第一步:docker ps -q
# 输出:所有运行中容器的短 ID(7位十六进制),每行一个
a1b2c3d
e4f5g6h
i7j8k9l
# 第二步:$() 命令替换,将输出作为参数传给 docker stop
docker stop a1b2c3d e4f5g6h i7j8k9l
# 第三步:docker stop 依次处理每个 ID(注意:是串行,非并行)
# 先 stop a1b2c3d(等它退出或超时),再 stop e4f5g6h……
优点 :极简、跨平台(Linux/macOS/WSL)、无需额外工具。
致命缺陷 :它停止 所有 运行中的容器,毫无区分度。如果你本地同时开着:
- 一个正在跑 CI 测试的
test-db容器(你不想停) - 一个共享的
redis-cache容器(被多个项目共用) - 一个后台
portainer管理界面(你正用它查日志)
这条命令会一锅端。更糟的是,它不校验容器状态——如果某个容器已处于 exited 状态但未被 rm , docker ps -q 不会列出它,所以没问题;但如果容器因 OOM 被内核杀死, docker ps 可能仍显示 Up 2 hours (状态未及时刷新),这时 stop 会失败并返回非零码,但脚本若没检查 $? ,就会静默跳过,留下隐患。
实操优化技巧 :
- 加
-a参数预览 :执行前先运行docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}",人工确认列表 - 加
--time统一超时 :docker stop -t 30 $(docker ps -q),避免个别慢容器拖累全局 - 用
xargs替代命令替换(更健壮) :docker ps -q | xargs -r docker stop -t 30xargs -r表示输入为空时不执行后续命令(docker ps -q无输出时,xargs直接退出,不会执行docker stop空参数,后者会报错)
我自己的 .zshrc 里定义的 alias 是:
alias dstop-all='docker ps -q | xargs -r docker stop -t 30 2>/dev/null || echo "No running containers to stop"'
2>/dev/null 屏蔽 docker stop 对已停止容器的警告(如 Error response from daemon: No such container: xxx ), || 后的 echo 提供友好反馈。
3.2 过滤方案:精准打击,按需关停—— docker ps --filter 的实战组合技
当你的环境容器数量超过 10 个,或存在多套隔离环境(dev/staging/prod),盲目 stop-all 就是自找麻烦。Docker 的 --filter 是真正的生产力杠杆,它让你像写 SQL WHERE 子句一样筛选目标。
核心过滤器详解(附真实场景)
| 过滤器语法 | 作用 | 实际案例 | 注意事项 |
|---|---|---|---|
--filter "status=running" |
只匹配运行中容器( docker ps 默认就是,但显式写出更清晰) |
docker stop $(docker ps -q --filter "status=running") |
无 |
--filter "ancestor=image:tag" |
匹配基于指定镜像构建的容器 | docker stop $(docker ps -q --filter "ancestor=my-app:dev") (停掉所有 dev 版本) |
ancestor 匹配镜像名+tag, my-app:latest 和 my-app:1.2.0 是不同祖先 |
--filter "label=env=dev" |
匹配带指定 label 的容器 | docker-compose.yml 中 labels: ["env=dev"] ,则此命令只停 dev 环境 |
Label 是最推荐的标记方式,语义清晰,易管理 |
--filter "name=^db-" |
名称正则匹配( ^ 开头, $ 结尾) |
docker stop $(docker ps -q --filter "name=^db-") (停所有 db- 开头的容器) |
需 Docker 20.10+,且正则引擎是 Go 的 regexp ,不支持 \d 等高级特性 |
--filter "expose=3000" |
匹配暴露了指定端口的容器 | docker stop $(docker ps -q --filter "expose=3000") (停所有对外提供 3000 端口的服务) |
expose 是 Dockerfile 的 EXPOSE 指令,非 -p 映射的端口 |
组合过滤实战 :假设你有以下容器:
CONTAINER ID NAMES IMAGE STATUS LABELS
a1b2c3d web-dev nginx:alpine Up 2 hours env=dev,service=web
e4f5g6h db-dev postgres:13 Up 3 hours env=dev,service=db
i7j8k9l web-stg nginx:alpine Up 1 hour env=staging,service=web
j0k1l2m redis-prod redis:7-alpine Up 5 hours env=prod,service=cache
-
只停 dev 环境的 web 和 db :
docker stop $(docker ps -q --filter "label=env=dev" --filter "label=service=web" --filter "label=service=db")
(注意:--filter是 AND 关系,多个 filter 同时满足) -
停所有非 prod 环境的容器 (排除法):
docker stop $(docker ps -q --filter "label=env!=prod") -
停所有暴露 5432 端口的容器(不管环境) :
docker stop $(docker ps -q --filter "expose=5432")
提示:
docker ps --filter的输出是容器 ID 列表,但docker stop接受容器名、ID、ID 前缀。所以docker stop web-dev和docker stop a1b2c3d效果相同。用名称更易读,用 ID 更精确(避免名称冲突)。
3.3 编排方案: docker-compose stop —— 微服务时代的黄金标准
当你用 docker-compose.yml 定义服务, docker-compose stop 就是停止整套环境的唯一正确姿势。它远不止是批量 stop,而是 按依赖顺序、带超时控制、尊重服务定义的智能协调器 。
看一个典型 docker-compose.yml :
version: '3.8'
services:
web:
image: my-web:latest
ports: ["3000:3000"]
depends_on: [db, cache]
restart: unless-stopped
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: password
volumes: ["./pgdata:/var/lib/postgresql/data"]
cache:
image: redis:7-alpine
command: redis-server --appendonly yes
执行 docker-compose stop 时,Docker Compose 会:
- 解析
depends_on,确定停止顺序:web→cache→db(依赖关系的逆序,确保上游服务先停) - 对每个服务,读取其
stop_grace_period(默认 10 秒),向容器发SIGTERM - 如果服务定义了
stop_signal(如stop_signal: SIGINT),则发该信号而非SIGTERM - 等待所有容器退出,任一超时则报错,但不影响其他容器继续停止流程
关键优势 :
- 依赖感知 :避免
db先停,web还在连它导致连接异常 - 配置继承 :
stop_grace_period可在docker-compose.yml中为每个服务单独设置,比全局--time精细得多 - 状态隔离 :
docker-compose stop只影响当前目录下的docker-compose.yml定义的服务,不会波及其他docker run启动的容器
生产级增强技巧 :
- 为关键服务延长 grace period :
services: db: # ... 其他配置 stop_grace_period: 120s # 数据库停机需更长时间 migration: image: my-migration:latest stop_grace_period: 300s # 迁移脚本可能很慢 - 用
--timeout覆盖 yml 配置 :docker-compose stop --timeout 60(单位秒),覆盖所有服务的stop_grace_period - 选择性停止部分服务 :
docker-compose stop web db(只停 web 和 db,保留 cache)
我所在团队的 CI 流水线,在部署新版本前,固定执行:
# 停止旧服务(带超时)
docker-compose -f docker-compose.prod.yml stop --timeout 60
# 删除旧容器(不删 volume)
docker-compose -f docker-compose.prod.yml down --remove-orphans
# 启动新版本
docker-compose -f docker-compose.prod.yml up -d
这套组合拳确保了零停机发布(blue-green 前置步骤)。
3.4 自动化方案:Shell Alias 与脚本封装——让安全变成肌肉记忆
把常用命令固化为 alias 或脚本,不是偷懒,是把防御性编程写进 shell 环境。我的原则是: 所有涉及 docker stop 的操作,必须带明确的 scope、timeout 和 dry-run 选项 。
我的 alias 体系( .zshrc 片段)
# 【安全第一】带预览的 stop-all(显示将停哪些容器)
alias dstop-preview='echo "=== WILL STOP THESE CONTAINERS ==="; docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"; echo "=== CONFIRM WITH: dstop-exec ==="'
# 【执行】真正停止,带统一 30 秒超时,静默错误
alias dstop-exec='docker ps -q | xargs -r docker stop -t 30 2>/dev/null && echo "✅ All containers stopped" || echo "⚠️ Some containers failed to stop"'
# 【按标签精准停】dev 环境专用
alias dstop-dev='docker ps -q --filter "label=env=dev" | xargs -r docker stop -t 30 2>/dev/null && echo "✅ Dev containers stopped"'
# 【按名称模式停】停所有以 "test-" 开头的容器
alias dstop-test='docker ps -q --filter "name=^test-" | xargs -r docker stop -t 10 2>/dev/null && echo "✅ Test containers stopped"'
# 【Composed 停】自动识别当前目录的 docker-compose.yml 并停止
alias dstop-compose='if [ -f docker-compose.yml ]; then docker-compose stop --timeout 60; else echo "❌ No docker-compose.yml found"; fi'
进阶:Bash 脚本实现带 dry-run 的智能停止
一个更强大的方案是写成脚本,支持 --dry-run 模式:
#!/bin/bash
# save as ~/bin/dstop-safe
set -euo pipefail
DRY_RUN=false
TIMEOUT=30
FILTERS=()
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN=true
shift
;;
-t|--timeout)
TIMEOUT="$2"
shift 2
;;
--filter)
FILTERS+=("--filter" "$2")
shift 2
;;
*)
echo "Usage: $0 [--dry-run] [-t SECONDS] [--filter 'key=value']..." >&2
exit 1
;;
esac
done
# 构建 docker ps 命令
PS_CMD="docker ps -q"
for filter in "${FILTERS[@]}"; do
PS_CMD="$PS_CMD $filter"
done
if [ "$DRY_RUN" = true ]; then
echo "🔍 DRY RUN: Would execute:"
echo " $PS_CMD | xargs -r docker stop -t $TIMEOUT"
echo ""
echo "📋 Containers that would be stopped:"
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}" "${FILTERS[@]}"
exit 0
fi
# 执行真实停止
echo "🛑 Stopping containers with timeout $TIMEOUTs..."
if docker ps -q "${FILTERS[@]}" | xargs -r docker stop -t "$TIMEOUT" 2>/dev/null; then
echo "✅ Success"
else
echo "⚠️ Some containers failed to stop (check logs above)"
exit 1
fi
使用方式:
# 预览将停哪些容器(不执行)
dstop-safe --dry-run --filter "label=env=dev"
# 真实执行,停 dev 环境,30 秒超时
dstop-safe -t 30 --filter "label=env=dev"
# 停所有暴露 8080 端口的容器
dstop-safe --filter "expose=8080"
这个脚本的价值在于:它把“确认”变成了强制步骤。没人能绕过 --dry-run 直接执行高危操作,这比任何文档都管用。
4. 生产环境避坑指南:那些文档里不会写的血泪教训
4.1 “僵尸容器”与“孤儿卷”:强制终止后的隐形债务
docker kill 或 docker stop 超时后 SIGKILL ,看似任务完成,实则埋下两颗雷:
雷一:僵尸进程(Zombie Process)
当容器主进程被 SIGKILL ,但其子进程(如 sh -c "python script.py" 中的 python )未被父进程回收,它们会变成僵尸进程,持续占用 PID 和少量内存。 docker ps 看不到它们,但 ps aux | grep defunct 能看到。
解决方案 :
- 启动容器时加
--init(推荐):docker run --init -d my-image。Docker 会注入一个轻量 init 进程(tini),它能自动收割僵尸子进程。 - 或在 Dockerfile 中用
ENTRYPOINT ["tini", "--"](需提前安装 tini)。
雷二:孤儿卷(Orphaned Volumes) docker stop 只停容器,不删卷。如果容器挂载了命名卷(named volume),且该卷不再被任何容器引用,它就成了“孤儿”, docker volume ls 里能看到,但 docker system prune 默认不删它(因为 prune 只删 dangling 卷,即未被任何容器引用的卷)。
后果 :磁盘空间悄悄吃满。一个日志卷每天增长 2GB,半年后就是 360GB 垃圾。
解决方案 :
- 定期清理 :
docker volume prune -f(加-f跳过确认) - 自动化脚本 :在 CI 流水线末尾加:
# 停止所有容器 docker stop $(docker ps -q) 2>/dev/null || true # 删除所有已停止容器 docker rm $(docker ps -a -q) 2>/dev/null || true # 删除所有未被引用的卷(谨慎!确保无重要数据) docker volume prune -f # 删除所有悬空镜像 docker image prune -f
注意:
docker volume prune是危险操作!务必确认卷中无重要数据。生产环境应禁用此命令,改用docker volume ls --filter "dangling=true" -q | xargs -r docker volume rm(只删 dangling 卷)。
4.2 Windows 与 macOS 的特殊陷阱:权限、路径与守护进程
Windows PowerShell 权限问题 :
在 PowerShell 中, docker stop $(docker ps -q) 会报错 The term 'docker' is not recognized 。这是因为 PowerShell 的命令替换语法是 $(...) ,但 docker 命令本身在 PATH 中,而子命令执行环境可能未继承 PATH。更常见的是权限不足。
正确写法 :
# 方案1:用 cmd 语法(兼容性最好)
docker stop $(docker ps -q)
# 方案2:PowerShell 原生语法(需管理员权限)
docker stop (docker ps -q)
# 方案3:绝对路径(最稳妥)
& "C:\Program Files\Docker\Docker\Resources\bin\docker.exe" stop (& "C:\Program Files\Docker\Docker\Resources\bin\docker.exe" ps -q)
macOS 的 Docker Desktop 问题 :
M1/M2 Mac 上,Docker Desktop 的 dockerd 守护进程有时会卡住,表现为 docker ps 返回空,但 ps aux | grep dockerd 显示进程在。此时 docker stop 会 hang 住。
诊断与修复 :
# 检查 dockerd 状态
sudo lsof -i :2376 # Docker Desktop 默认 daemon 端口
# 重启 Docker Desktop(GUI 点击右上角图标 -> Restart)
# 或命令行重启
osascript -e 'quit app "Docker"' && open -a Docker
4.3 CI/CD 流水线中的致命误区:别让 docker stop 成为单点故障
在 Jenkins/GitLab CI 中,我们常写:
before_script:
- docker stop $(docker ps -q) 2>/dev/null || true
- docker system prune -f
这看起来很干净,但有两大风险:
风险一:并发流水线冲突
如果两个分支的 CI job 同时运行,job A 执行 docker ps -q 得到 a1b2c3d ,job B 执行得到 e4f5g6h ,但 job A 的 docker stop a1b2c3d 可能还没结束,job B 就执行了 docker stop e4f5g6h ,如果它们共享同一个 Docker daemon(如自托管 runner),就可能互相干扰。
解决方案 :
- 为每个 job 创建独立网络和卷 :
docker network create ci-${CI_JOB_ID},docker run --network ci-${CI_JOB_ID} ... - 用
--rm启动临时容器 :docker run --rm -v $(pwd):/workspace my-builder:latest make test,测试完自动销毁。
风险二: docker stop 失败导致流水线静默失败 docker stop 对已停止容器返回非零码,但 || true 会掩盖真实错误。如果 docker stop 因权限问题失败,后续 docker build 可能因端口占用而失败,但你根本不知道是前面的 stop 没成功。
解决方案 :
- 显式检查 exit code :
if ! docker stop $(docker ps -q) 2>/dev/null; then echo "⚠️ Failed to stop some containers, proceeding anyway..." fi - 用
docker-compose down替代 :它更健壮,且down会自动删除网络、卷(可选)。
5. 常见问题速查表与独家排查技巧
| 问题现象 | 可能原因 | 排查命令 | 解决方案 | 我的实操心得 |
|---|---|---|---|---|
docker stop 后容器状态仍是 Up X hours |
容器进程未响应 SIGTERM ,或 dockerd 状态未刷新 |
docker inspect <container> | jq '.State.Status,.State.StoppedAt' ps aux | grep <PID> |
1. 检查应用是否有 SIGTERM handler 2. 用 docker kill --signal=SIGUSR2 <container> (若应用支持) 3. 最后用 docker kill <container> |
不要迷信 docker ps 的实时性 。 inspect 才是真相。我遇到过 ps 显示 Up ,但 inspect 显示 StoppedAt 是 5 分钟前,原因是 dockerd 进程卡死,需重启 Docker Desktop。 |
docker stop 返回 Error response from daemon: No such container |
docker ps -q 输出了已停止容器的 ID(罕见,但 dockerd bug 可能导致) |
docker ps -a -q | xargs -r docker inspect -f '{{.ID}} {{.State.Status}}' 2>/dev/null | grep -E "(exited|created)" |
用 docker ps -q --filter "status=running" 替代 docker ps -q |
永远用 --filter "status=running" 。这是我在 37 个生产环境踩坑后总结的铁律。 docker ps -q 的“不严谨”是 Docker 的设计妥协,我们得用过滤器弥补。 |
| 停止后,宿主机端口仍被占用 | 容器内进程未完全退出,或 dockerd 未释放端口绑定 |
lsof -i :<PORT> sudo ss -tulnp | grep <PORT> |
1. docker kill <container> 强制终止 2. sudo fuser -k <PORT>/tcp (Linux) 3. 重启 dockerd |
端口占用是最高频问题 。我写了个小函数 portfree() { sudo lsof -ti:$1 | xargs -r kill; } , portfree 3000 一键清理。 |
docker-compose stop 不按 depends_on 顺序停止 |
depends_on 只控制启动顺序, 不控制停止顺序 (官方文档明确说明) |
docker-compose config --services 查看服务名 docker-compose ps 确认状态 |
1. 用 docker-compose down (它按依赖逆序停止) 2. 或手动 docker-compose stop service1 service2 service3 (按逆序) |
depends_on 是个大坑 。很多教程说它控制启停,其实是误导。 down 才是真·依赖感知。 |
docker stop 超时,但 docker kill 后容器立刻消失,却报 Cannot connect to the Docker daemon |
dockerd 守护进程崩溃 |
systemctl status docker (Linux) brew services list | grep docker (macOS) |
1. sudo systemctl restart docker (Linux) 2. 重启 Docker Desktop(macOS/Windows) |
守护进程崩溃往往伴随磁盘满 。 df -h 是第一排查命令。我见过 /var/lib/docker 分区 100%, dockerd 直接 segfault。 |
独家排查技巧:用 strace 追踪 docker stop 的系统调用
当 docker stop 行为诡异(如卡住、无响应),用 strace 看它在做什么:
# 在另一个终端,找到 docker stop 的 PID
ps aux | grep "docker stop"
# 假设 PID 是 12345
sudo strace -p 12345 -e trace=sendto,recvfrom,connect,openat
你会看到它是否在尝试连接 dockerd 的 Unix socket( /var/run/docker.sock ),或是否在 recvfrom 时阻塞——这直接指向 dockerd 是否存活。
6. 最后分享一个小技巧:用 docker events 监控停止过程
docker events 是 Docker 的实时事件总线,它能告诉你每一个容器状态变化的精确时刻。在调试停止流程时,它比 docker ps 刷新更快、更准。
开启监控:
# 新终端,实时监听 stop 事件
docker events --filter 'event=stop' --format 'Time={{.Time}} Status={{.Status}} Container={{.Actor.Attributes.name}}'
然后在另一个终端执行 docker stop my-container ,你会立刻看到:
Time=2023-10-05T08:23:41.123456789Z Status=stop Container=my-container
进阶用法 :结合 jq 实时分析:
# 统计过去 1 分钟内所有 stop 事件的耗时(需配合 timestamp)
docker events --since '1m'更多推荐
所有评论(0)