ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Docker生产环境应急处理指南:故障排查与常用命令速查

2026/9/11 19:55:48 拓冰建站 浏览量
Docker生产环境应急处理指南:故障排查与常用命令速查 凌晨两点半手机震了。生产环境一台跑着订单服务的宿主机拉响告警容器批量进入 Exited 状态磁盘使用率飙到 97%docker ps刷出来的全是血红一片。那一刻你脑子里有没有一张图——先看哪条命令、再翻哪个文件、最后怎么止损老实说我见过太多同事在这种时候抓起键盘乱敲越敲越乱最后只能重启宿主机硬扛。如果你不想成为那个在告警群里手足无措的人这篇东西就是为你准备的。这篇文章不是什么官方文档翻译而是一份我自己整理、日常也在用的 Docker 应急处理方案和常用命令速查手册。它不解决怎么用 Docker 开发这种入门问题聚焦的都是线上事故容器起不来、镜像拉不动、磁盘被打满、端口被占用、数据差点丢光。全文按应急场景组织每个小节都有命令、有原理、有坑适合运维工程师、后端开发、DevOps 以及所有需要自己扛 Docker 生产环境的人。看完之后你可以直接把它打印出来贴在工位上也可以存到自己的知识库里当逃生手册。1. 凌晨三点接到告警Docker 应急的核心思维1.1 为什么应急手册必须前置准备很多团队把 Docker 命令当成用到再搜的东西平时开发环境怎么跑都行真出了问题才发现自己连docker logs的正确参数都没记住。这里有个残酷的事实生产事故中你不会有多余的时间去查文档每多花一分钟定位问题业务损失就多一分。所以应急手册不是写给新手看的是写给正在经历事故的自己看的。我自己的习惯是把命令按照故障链路组织而不是按照 Docker 官方文档的章节组织。官方文档把命令分成容器、镜像、网络、数据卷四大类但真实事故从来不会按这个分类发生。磁盘满了你会需要同时用到docker system df、du -sh /var/lib/docker、docker ps -a、docker rm这一串命令它们分属不同章节但在应急场景里就是一条链路。所以这份手册的第一原则是按故障场景组织命令而不是按命令类型组织。1.2 应急处理的三个原则先止血、再定位、后根治Docker 应急和急诊室分诊是一个逻辑。病人进来先保命不是先查病因。对应到容器事故上三个原则缺一不可先止血无论什么故障先把业务恢复起来。容器挂了就重启宿主机快满了就清掉无用镜像和停止的容器服务起不来就回滚到上一个可用版本。这个阶段不要追求搞清楚为什么只追求让业务先跑起来。再定位业务恢复后保留现场。导出日志、保存docker inspect信息、抓取当时的磁盘和内存状态。这些都是事后定位问题的关键证据千万别一上来就docker system prune -af把证据全清光了。后根治根据证据找到根因改配置、改代码、加监控确保同类事故不再发生。这三个原则听起来简单但我在实际应急中见过太多人跳过第一步上来就抓着日志分析半天结果业务断了半小时最后发现只是镜像 tag 打错了。先恢复再排查这个顺序必须刻在脑子里。1.3 一份能救命的操作手册应该长什么样结合我自己的踩坑经历一份靠谱的应急手册至少要包含四块内容快速定位命令一列出来就能知道哪里出了问题比如docker ps -a、docker logs、docker inspect、docker stats。止损恢复命令kill 容器、清理资源、回滚镜像、重建容器这些操作要写得足够肌肉记忆。数据安全命令备份数据卷、导出容器、保存镜像防止二次伤害。根因排查指南磁盘、内存、网络、配置每个方向都有对应的排查链路。下面每个章节都会按照这个思路展开不绕弯子直接上干货。2. 容器起不来或反复重启从定位到恢复的完整命令链2.1 第一件事docker ps -a 之外你还需要看什么容器异常时大家第一反应都是docker ps -a看状态。这个没错但很多人在这一步就走偏了——看着一堆Exited状态发呆不知道下一步干啥。正确的打开方式是这样的# 查看所有容器状态包括已退出和异常退出的 docker ps -a # 只看处于异常状态Exited/Dead/Restarting的容器 docker ps -a --filter statusexited --filter statusdead --filter statusrestarting拿到容器 ID 之后别急着 restart先看退出码# 查看容器的详细状态重点关注 ExitCode 和 Error 字段 docker inspect container_id --format{{.State.ExitCode}} {{.State.Error}} {{.State.OOMKilled}}这三项信息量极大。ExitCode是容器主进程退出时的返回码非 0 说明进程异常退出Error字段会显示底层错误信息OOMKilled如果是true说明容器是被内核 OOM Killer 干掉的。有一次我排查一个频繁重启的服务docker inspect出来OOMKilled: true瞬间锁定方向——不是代码问题是内存上限设得太死应用还在正常启动就被杀了。如果只看日志能绕一两个小时弯路。2.2 日志永远是第一现场docker logs 的正确打开方式很多新手看日志就是一把梭docker logs container_id然后被几万行输出淹没啥也看不出来。我的经验是# 查看最近 200 行日志 docker logs --tail 200 container_id # 带时间戳查看方便和监控系统对齐 docker logs --tail 200 -t container_id # 实时跟随日志适合观察启动过程 docker logs -f --tail 100 container_id # 查看指定时间之后的日志比如排查凌晨2:00到2:10之间的异常 docker logs --since 2025-01-10T02:00:00 --until 2025-01-10T02:10:00 container_id这里有个实操要点docker logs只能看到被 Docker 捕获的标准输出和标准错误如果你的应用把日志写进了容器内的某个文件这个命令就看不到。判断方法很简单——docker logs如果没有输出或者输出极少直接exec进容器看日志文件。另外要注意日志驱动默认是json-file如果容器是用--log-driver journald或其他驱动启动的docker logs的行为会不一样应急时容易踩坑。2.3 进不去容器时的替代方案docker inspect 与 docker exec容器处于Exited状态时你无法docker exec进去这是很多人卡住的地方。这时候能干的事情有两件一是用docker inspect拉出容器的完整配置二是用docker commit把当前容器的文件系统保存下来即使容器已退出文件系统还在。# 拉取容器完整配置输出为 JSON docker inspect container_id container_config.json # 提取关键信息挂载的数据卷 docker inspect container_id --format{{range .Mounts}}{{.Source}} - {{.Destination}}{{println}}{{end}} # 提取环境变量排查配置项是否传错 docker inspect container_id --format{{range .Config.Env}}{{println .}}{{end}}如果容器处于Restarting状态说明它在不断重启exec同样不稳定。一个技巧是用docker start配合临时覆盖入口命令把容器拉起来但不跑原来的启动逻辑# 临时覆盖入口命令启动后不执行业务方便排查 docker start -ai container_id /bin/bash这个方式只对当前这个容器临时生效不会改标签和镜像排查完可以直接删掉不影响原来的规范流程。2.4 容器重启策略与健康检查容器反复重启还有一个常见原因restart policy配置不当。比如用--restart unless-stopped启动的容器如果进程持续 crashDocker 会按退避算法不断重启它表面上看就是容器永远起不来。这时候要看两个地方# 查看重启次数和重启策略 docker inspect container_id --format{{.RestartCount}} {{.HostConfig.RestartPolicy.Name}}我自己踩过的坑是一个批量任务容器崩溃了但因为配置了always重启策略它在 24 小时内被拉起了几百次日志文件暴涨把磁盘打满。正确姿势是对一次性任务容器不要配always配on-failure:3最多对常驻服务配unless-stopped就好方便手动停止后不被自动拉起。3. 镜像仓库拉取失败的应急与加速3.1 镜像下载慢与超时的真实原因docker pull卡住、超时、报net/http: TLS handshake timeout这类问题在 Docker 运维里太常见了。很多人第一反应是网络不好但根因往往不止一个镜像比较大而宿主机到默认仓库的网络链路不稳定宿主机 DNS 解析异常导致镜像仓库域名解析超时本地磁盘空间不足导致镜像层解压失败报no space left on device并发拉取太多或者是防火墙/安全策略拦截了 443 端口的长连接。应急时建议按这个顺序排查先确认 DNS 和网络连通性再用docker pull的详细输出判断卡在哪个环节然后看磁盘空间最后考虑配置镜像加速。# 检查到仓库的连通性 ping registry-1.docker.io curl -I https://registry-1.docker.io/v2/ # 检查磁盘空间 df -h df -i3.2 配置镜像加速器的合规方案针对镜像拉取慢的问题最有效的常规做法是配置registry-mirrors。修改/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] }改完后重启 Dockersystemctl daemon-reload systemctl restart docker这里有几个坑要注意。第一不同加速器服务的稳定性和速度差异很大而且随着时间推移可能失效建议配置多个Docker 拉取时会自动选择可用的那个。第二配置加速器只影响从 Docker Hub 拉取公共镜像如果你拉的是私有仓库镜像需要在镜像地址前加上完整的仓库域名比如registry.example.com/nginx:1.25加速器不会对这种情况生效。第三改了daemon.json后docker pull仍然走旧配置的情况偶有发生重启之后要立刻用docker info验证一下Registry Mirrors有没有生效。3.3 docker pull 失败后的处理技巧镜像拉了一半失败了Docker 会留下不完整的镜像层下次 pull 时可能报错。这时候先清理再重试# 清理悬空镜像和未使用的镜像层 docker image prune -f # 如果某个镜像的版本拉不下来先确认 tag 是否存在 docker manifest inspect image:tag # 重新拉取加详细输出来看卡点 docker pull image:tag --quiet还有一个非常实用的技巧如果你在服务器上拉不下来的镜像但在另一台网络环境更好的机器上能拉到可以直接用docker save打包传过去# 在能拉到的机器上执行 docker pull nginx:1.25 docker save nginx:1.25 -o nginx-1.25.tar # 把 tar 包传到目标机器后执行 docker load -i nginx-1.25.tardocker save/docker load是应急场景里最可靠的离线搬运方案比docker export多了保留镜像完整历史信息和标签的能力。但它们打出的 tar 包会比较臃肿因为包含完整的镜像层历史如果同一条链路里只需要容器文件系统快照才考虑docker export配合docker import。这个区别务必记清楚关键时刻可以救你一命。4. 数据不能丢数据卷备份、迁移与恢复实战4.1 数据卷的三种挂载方式与选择搞 Docker 的人最怕两件事一是容器删了数据没了二是误删了数据卷数据。要想应急先得理解 Docker 的三种数据存储方式方式特点常见场景bind mount将宿主机的目录直接挂载进容器权限需自行管理给容器传配置文件、日志目录映射volumeDocker 管理的卷默认存放在/var/lib/docker/volumes/支持备份迁移最方便数据库数据目录、应用持久化数据tmpfs存储在内存中重启容器即清空不能用于持久化临时缓存、敏感数据不外落盘应急场景里我最推荐的是优先用 volume 而不是 bind mount。原因很简单volume 的备份、恢复、迁移都有一整套工具链而 bind mount 一旦宿主机的目录没了数据就彻底没救了。你可以在创建容器时用-v直接指定 volume# 创建卷 docker volume create mysql_data # 挂载卷启动容器 docker run -d --name mysql \ -v mysql_data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDyourpass \ mysql:8.04.2 容器数据备份的完整链路真正到了要备份的时候最怕的是你连卷挂在哪个目录都不知道。先定位再打快照# 查看所有 volume docker volume ls # 查看容器挂载了哪些卷以及宿主机上的真实路径 docker inspect mysql --format{{range .Mounts}}{{.Name}} - {{.Destination}} ({{.Type}}){{println}}{{end}}确认卷之后备份的核心思路是先把数据目录打包成 tar再从容器里拷出来# 方式一直接打包卷到宿主机 docker run --rm \ -v mysql_data:/source:ro \ -v /backup:/backup \ alpine tar czf /backup/mysql_data_$(date %Y%m%d).tar.gz -C /source . # 方式二如果容器还活着直接用 docker cp 拷出容器内数据目录 docker cp mysql:/var/lib/mysql /backup/mysql_data_$(date %Y%m%d)这里有个小经验打包数据卷时一定要加--rm用完即删不然你会收获一堆残留的临时容器。另外如果是数据库类应用备份前最好先做一致性处理MySQL 可以先用mysqldump导出逻辑备份再对文件系统做物理备份物理备份期间要注意写入锁否则备份出来的数据可能不一致。这个细节很多人忽略恢复的时候才后悔。4.3 从备份恢复到新容器的操作恢复操作的核心就一句话——把备份包解压回卷目录然后用新容器挂载同一个卷。步骤拆开是这样的# 1. 先创建目标卷如果不存在 docker volume create mysql_data_new # 2. 把备份解压到卷目录 docker run --rm \ -v mysql_data_new:/target \ -v /backup:/backup \ alpine sh -c tar xzf /backup/mysql_data_20250110.tar.gz -C /target # 3. 启动新容器挂载这个卷 docker run -d --name mysql_restored \ -v mysql_data_new:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDyourpass \ mysql:8.0如果是docker cp方式备份的就更简单直接docker cp回容器里然后重启容器。但我必须提醒一句恢复后不要立刻把旧容器删掉。先保留旧容器和新容器并行等到业务验证完全正常再清理旧资源。我见过有人恢复完太激动一个docker rm -f把旧容器删了结果新容器有 bug 起不来数据备份又是增量搞的最后只能回滚到更早的备份点白白损失了几个小时的数据。4.4 镜像级备份docker commit 的使用边界docker commit可以把一个容器的当前状态保存成新镜像这听起来很适合做整体备份但我要泼盆冷水——它不是专业备份方案它适合做现场快照不适合做长期数据备份。原因有三个docker commit保存的是容器可写层的文件系统快照不会包含 volume 里的数据提交出来的镜像体积会不断膨胀因为每个文件系统操作都会保留在可写层它做不到一致性备份如果容器里跑着数据库提交出来的镜像可能处于不一致状态。那什么场景下用docker commit我的建议是用来保现场。比如一个容器被改了配置文件、装了调试工具你想先把当前状态留个底再去做排查操作这时候 commit 一下很值docker commit container_id myapp:debug-snapshot-202501105. 资源耗尽与性能故障磁盘、内存、inode 的排查清单5.1 磁盘写满的罪魁祸首容器日志与镜像层Docker 宿主机磁盘被打满基本是三个大户容器日志文件、悬空镜像和停止状态的容器可写层、数据卷本身。先说日志Docker 默认会把容器日志存在/var/lib/docker/containers/container_id/*-json.log如果不加限制一个高频打印日志的容器能把磁盘写穿。应急时先定位哪个文件最大# 查看整个 docker 目录占用 du -sh /var/lib/docker # 定位具体是哪个容器日志最大 du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -20 # 查看所有容器日志大小按占用排序 for c in $(docker ps -aq); do size$(docker inspect $c --format{{.LogPath}} | xargs ls -lh 2/dev/null | awk {print $5}) echo $c $size done找到元凶之后应急可以先把日志文件清空用truncate而不是rm因为 Docker 进程持有文件句柄直接删文件不会释放空间truncate -s 0 /var/lib/docker/containers/container_id/*-json.log治本的办法是给 Docker 全局加上日志轮转配置。推荐写到/etc/docker/daemon.json对新建容器生效{ log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }这里有个大坑max-size和max-file只对新建容器生效已经存在的容器不会自动应用。所以配置完还得把存量容器挨个 recreate或者在容器编排层面统一配置。5.2 系统层面的内存与 inode 问题内存故障在 Docker 场景里有两种宿主机内存不足或者容器触发了OOMKilled。如果是前者先看宿主机# 查看宿主机内存使用 free -h # 查看哪些进程在吃内存docker 进程的子进程尤其要注意 top -b -n 1 | head -30 ps aux --sort-%mem | head -20如果是单容器 OOM用docker inspect确认OOMKilled字段然后调整容器内存上限# 更新容器内存限制需要容器支持 update 操作 docker update --memory 4g --memory-swap 4g container_id # 或者直接删掉容器用更大的内存限制重建 docker rm -f container_id docker run -d --name myapp -m 4g --memory-swap 4g ...inode 耗尽的排查比较容易漏很多人df -h看磁盘还有几个 G就觉得没问题结果报No space left on device。一定要同时看df -i如果 inode 使用率 100%说明小文件太多了通常是容器产生海量小缓存文件、或者日志轮转没生效导致海量小日志文件定位思路和磁盘排查类似用find /var/lib/docker -type f | wc -l先看总量。5.3 docker system df 快速体检排查资源类故障有一个命令能帮你在三秒内建立起全局观docker system df输出会显示镜像、容器、数据卷、构建缓存各自的占用量以及 RECLAIMABLE 列——标记多少资源是可以安全回收的。应急时可以这样判断如果可回收空间很大直接执行docker system prune -f但注意不要加-a和--volumes否则会删掉所有未使用的镜像和数据卷可能造成不可逆损失如果只想清悬空镜像用docker image prune -f如果想清掉停止的容器用docker container prune -f。我自己在应急时从来不用全量 prune都是分步来先看输出再决定删哪些避免误删。毕竟先止血不等于把腿锯了止血。6. 网络与端口冲突生产环境最常见的故障现场6.1 端口被占用的排查方法端口冲突是 Docker 上线时最典型的故障常见的报错长这样Bind for 0.0.0.0:8080 failed: port is already allocated。原因要么是其他进程占了端口要么是另一个容器已经映射了这个端口。排查顺序# 检查是哪个容器占用了端口 docker ps --format table {{.Names}}\t{{.Ports}} | grep 8080 # 检查宿主机上是哪个进程占用了端口 ss -tlnp | grep 8080 lsof -i :8080如果发现是别的进程占用而那个进程又是宿主机上必须跑的服务那就别硬抢端口给容器映射别的端口更优雅# 将容器的 8080 端口映射到宿主机的 18080 docker run -d -p 18080:8080 --name myapp myapp:latest但要注意-p 18080:8080这种写法会让容器监听在宿主机所有网卡上生产环境最好指定 IP缩小暴露面docker run -d -p 127.0.0.1:18080:8080 --name myapp myapp:latest6.2 docker network 的几种模式与排障网络模式选错是很多容器互通问题的根源。Docker 常见的网络模式就四种bridge默认容器通过虚拟网桥和宿主机通信容器间默认互通适合大部分场景host容器直接使用宿主机网络栈性能好但无法做端口映射也不能单独设置网络参数none容器没有网络栈适合某些安全要求极高的离网任务overlay跨宿主机容器通信主要用于 Swarm 或 Kubernetes 场景。排查容器互通问题先确认它们在同一张网桥上# 查看容器的网络连接 docker inspect container_id --format{{range $k, $v : .NetworkSettings.Networks}}{{$k}}{{println}}{{end}} # 查看自建网络详情 docker network inspect my-network如果是跨容器访问服务名能 ping 通但连接超时优先检查是不是容器防火墙策略或者应用绑定地址的问题。一个很容易踩的坑是应用在容器里监听了127.0.0.1只对容器内部开放外部容器访问不了。Linux 下用ss -tlnp看监听地址如果是127.0.0.1:port要改成0.0.0.0:port才能被其他容器访问。6.3 容器间通信异常的处置容器间通信异常第一步不是抓包而是先确认基础连通性# 从容器 A 里 ping 容器 B 的 IP docker exec -it container_a ping container_b_ip # 从容器 A 里测试端口连通性 docker exec -it container_a nc -vz container_b_ip 8080 # 查看容器 B 的 IP docker inspect container_b --format{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}如果 ping 不通大概率是网络配置或安全组问题如果 ping 通但端口不通那问题出在目标容器或其上层的防火墙规则上。这里有一个实践技巧很多排查指令在容器里是没有的比如nc、telnet都没有应急可以启动一个带网络工具的临时容器和出问题的容器放在同一网络里docker run --rm -it --network my-network alpine sh apk add iputils iproute2 curl # 然后就可以 ping、curl、nc 一条龙排查这个临时容器用--rm排查完自动清理不会在宿主机上留垃圾是我最常用的网络应急手法。7. 拿去即用的常用命令速查表前面六章是应急思路和排查链路最后我把这些命令按操作类型整理成速查表贴在工位上或者存到手机里都是好用的。为了方便阅读我把它们拆成四个小表。7.1 镜像管理速查操作命令查看本机镜像docker images或docker image ls拉取镜像docker pull image:tag推送镜像到仓库docker push image:tag删除指定镜像docker rmi image:tag删除悬空镜像docker image prune -f删除所有未使用镜像docker image prune -a慎用打新标签docker tag image:tag new-image:new-tag保存镜像为 tar 包docker save -o filename.tar image:tag从 tar 包载入镜像docker load -i filename.tar查看镜像历史层docker history image:tag7.2 容器生命周期速查操作命令查看运行中容器docker ps查看所有容器含停止docker ps -a启动容器docker start container停止容器docker stop container发 SIGTERM强制停止容器docker kill container发 SIGKILL重启容器docker restart container删除停止的容器docker rm container强制删除运行中的容器docker rm -f container删除所有停止的容器docker container prune -f创建并运行容器docker run -d --name name -p 8080:80 image:tag进入运行中的容器docker exec -it container /bin/bash查看容器进程docker top container7.3 数据与网络速查操作命令查看所有数据卷docker volume ls创建数据卷docker volume create volume_name删除未使用数据卷docker volume prune -f拷贝文件到容器docker cp host_path container:container_path从容器拷贝文件docker cp container:container_path host_path查看容器 IPdocker inspect container --format{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}查看网络列表docker network ls创建桥接网络docker network create network_name将容器接入网络docker network connect network_name container将容器移出网络docker network disconnect network_name container7.4 监控与清理速查操作命令查看容器资源占用docker stats查看 Docker 整体占用docker system df查看实时日志docker logs -f --tail 200 container清理全部未使用资源docker system prune -f清理全部包含未使用镜像和卷docker system prune -af --volumes慎用查看 Docker 版本docker version查看 Docker 系统信息docker info8. 最后说几个应急时的保命经验手册主体到这里差不多讲完了但在真实事故中很多救命的经验是命令之外的东西。借这篇文章分享几个我踩过的坑希望对你有帮助。经验一所有的 prune 命令都是高风险手术。执行docker system prune -af之前务必先跑一遍docker system df看清楚会删掉多少东西再想想里面有没有你舍不得的资源。如果你不确定就不要加-a和--volumes。经验二应急时手速再快也要留现场。我见过太多事故业务恢复后想复盘发现日志被自己清了、容器被自己删了、docker inspect信息没留存整个排障过程变成了凭记忆猜。现在我的习惯是任何容器异常先跑一条docker inspect inspect_backup.txt再跑一条docker logs --tail 1000 logs_backup.txt哪怕最后问题解决了不需要复盘这两个文件也能让你心里有底。经验三帮你救命的往往不是 docker 命令而是 Linux 基础命令。ss、lsof、du、df、free、top、ps这些命令在 Docker 排障里出现的频率比docker本身还高。尤其是df -i查 inode、du -sh定位大文件、ss -tlnp查端口关键时刻一个比一个好用多花点时间把它们练熟。经验四把这份手册按自己的环境改造一遍。我的命令里用了docker.m.daocloud.io作为镜像加速不一定适合你的网络环境我的数据卷策略是 volume 优先如果你的团队习惯 bind mount恢复流程会完全不同。建议你拿到这份速查表后在自己的测试环境跑一遍把不合适的命令删掉加上你们自己的镜像仓库地址、数据卷命名规范、网络规划做成真正属于你的应急手册。经验五应急手册要放在手边不是放在收藏夹。我的做法是在个人知识库建了一个on-call 手册目录用快捷键一键调出 Docker 专题页另外在工位贴了一份纸质速查表。事故来的时候人是慌的再好的手册如果打开需要五步操作你根本不会用它。