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 30
xargs -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)