
遇到过这种场景的朋友应该知道那种酸爽宿主机断电、物理机强制重启、或是虚机管理平台上一键“强制关机”等虚机再起来的时候一切看着都正常唯独systemctl start docker给你甩下一句冷冰冰的Failed to start docker.service。连续敲两下systemctl status docker屏幕上全是红色的failed日志翻来覆去就那么几行很多人这时候就开始慌了。这篇文章专门聊这件事虚机异常关闭之后Docker服务为什么起不来报错Failed to start docker.service到底卡在哪一步以及我用过、验证过的几种修复方法。场景主要针对Linux虚机内跑的docker.service也就是常见的那种systemd管理模式覆盖网络桥接冲突、锁文件残留、overlay2存储异常、containerd状态错乱这些高频病因。适合正在半夜抢救线上容器的运维也适合刚接触Docker、第一次遇到服务起不来的新手照着一步步排查。1. 问题初现一次“非正常关机”引发的连锁故障1.1 故障现象还原虚机异常关闭这句话说起来轻巧背后场景可以五花八门宿主机断电、虚机被强制关机、云平台“强制停止”按钮被误点、virsh destroy、Hyper-V里直接“关机”而不是“关闭操作系统”。无论哪种虚机再次启动之后Docker服务大概率会出现类似下面的状态$ systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) Active: failed (Result: exit-code) since Mon 2024-03-04 09:12:33 CST; 3s ago Process: 31245 ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/containerd.sock (codeexited, status1/FAILURE)这个status1/FAILURE基本等于没说它只告诉我“进程退出码是1”至于为什么退出得继续往下挖。继续执行journalctl -u docker.service -n 50才可能看到真正能定位问题的内容比如Error starting daemon: Error initializing network controller: Error creating default bridge network: cannot create network docker0: conflicts with existing network with name docker0 in bridge-db还有另一类failed to start daemon: pid file found, ensure docker is not running or delete /var/run/docker.pid这两种报错是我实际工作中遇到最多的全部指向异常关机后的“残留状态”。Docker守护进程本身不复杂但它启动时要做很多环境检查包括网络命名空间、底层存储驱动、与containerd的socket通信。任何一个环节残留了上一次异常运行的状态都会在新一轮启动时校验失败进而直接退出。1.2 为什么“异常关闭”是Docker的头号杀手很多人不理解Docker不过是个进程虚机重启了进程重新拉起来不就行了为什么偏偏Docker这么矫情关键在于Docker守护进程不是一个无状态进程。dockerd在运行期间会维护大量元数据容器网络数据库、镜像层信息、overlay2挂载点、容器与镜像的写时复制关系、以及和containerd之间的通信状态。正常关机会触发systemd的stop流程dockerd能按顺序卸载文件系统、清理网络、关闭socket、删除临时文件。但虚机强制关机这个清理流程根本没机会执行结果就是磁盘上留下大量“半写半不写”的状态文件内存里的网络配置、路由表、iptables规则也没来得及清理重启后Docker自己都会困惑。打个比方正常关机相当于你睡前锁好门、关好窗、叠好被子异常关闭就是有人半夜把整栋楼电闸拉了第二天醒来发现客厅灯还亮着、卧室门反锁了、厨房水龙头也没关。Docker启动失败就是在重新收拾这个烂摊子。这也就是为什么一定要按“先诊断、再动手、最后预防”的顺序处理直接systemctl restart docker碰运气十有八九还是失败而且还可能把原本可以恢复的数据搞得更糟。2. 诊断三板斧第一步永远是看日志2.1 systemctl status 与 journalctl 的正确用法我见过不少新手上来就systemctl restart docker连续重启三次日志一眼不看最后才想起来求助。实际上诊断效率最高、最快能定位方向的就是系统日志。第一板斧先把journal打开看全journalctl -u docker.service -n 100 --no-pager journalctl -u docker.service --since 5 minutes ago -p err --no-pager如果journal里内容太少还可以直接看syslog或messagesgrep -i docker /var/log/syslog | tail -n 50 grep -i docker /var/log/messages | tail -n 50日志不是只看“docker”相关的我通常还会拉出containerd的日志看看。现代Docker都内置了containerd异常关机后containerd自己也可能处于半死状态Docker起不来原因恰恰是它连不上containerd的socket。多一层日志多一条线索journalctl -u containerd -n 50 --no-pager2.2 三类最常见的日志报错特征根据我处理过的十几个类似案例异常关机后的docker.service启动失败报错基本可以归成三类每一类的修复思路完全不同。第一类是网络初始化失败。典型特征是日志里有Error initializing network controller、cannot create network docker0、conflicts with existing network这类字眼。这代表上次异常关闭时Docker网络数据库/var/lib/docker/network/files/local-kv.db里还保留着docker0的旧记录但系统里的docker0网桥已经不存在或者状态不对新启动的Docker在重建网络时发现和数据库冲突于是放弃启动。第二类是pid文件或锁文件残留。典型特征是pid file found, ensure docker is not running or delete /var/run/docker.pid。这时Docker认为已经有一个实例在运行拒绝启动第二个但实际根本没有进程就是上次残留的pid文件在“诈尸”。第三类是存储驱动或容器数据受损。日志里可能能看到failed to mount overlay、Error creating default network之外的各种stat /var/lib/docker/overlay2/...相关错误甚至input/output error。这类问题往往不只是残留而是底层文件系统出现了真正的损坏后续修复成本也最高。2.3 顺手摸一遍环境磁盘、挂载、内核态在决定用哪套修复方案之前我还会做一次快速体检防止修复到一半才发现底子就是坏的。df -h /var/lib/docker mount | grep -E docker|overlay dmesg | grep -iE xfs|ext4|error|corrupt ip link show docker0检查三件事/var/lib/docker所在分区是否已满、磁盘是否只读、底层文件系统有没有I/O错误。虚机异常关闭后云盘或虚拟磁盘偶尔会出现文件系统挂载异常Linux会把它降级为只读挂载这时候任何写入操作都会失败Docker起不来是必然的。如果dmesg里出现EXT4-fs error或XFS ... corruption这类信息优先级最高的不是修Docker是先修文件系统。3. 逐个击破四种高频修复方案实战3.1 网络桥接冲突删掉残留的docker0网络记录如果你看到的是网络初始化失败别急着删文件。先确认系统里当前没有运行着的容器然后一步一步来systemctl stop docker systemctl stop containerd ip link show docker0如果docker0网桥本身没有残留输出为空或是does not exist那问题就锁定在数据库记录。备份并删除Docker网络数据库cp -a /var/lib/docker/network/files/local-kv.db /var/lib/docker/network/files/local-kv.db.bak rm -f /var/lib/docker/network/files/local-kv.db systemctl start containerd systemctl start docker启动后第一件事检查docker network ls是否恢复了默认的bridge、host、none三个网络。如果只缺少bridge就用命令手动重建docker network create \ --driver bridge \ --subnet172.17.0.0/16 \ --ip-range172.17.0.0/16 \ --gateway172.17.0.1 \ bridge这里有个细节如果你之前给bridge网络配置过自定义网段删数据库之后会被重置成Docker默认的172.17.0.0/16。生产环境里容器IP变了可能导致服务发现、防火墙规则、数据库白名单全部失效。所以删local-kv.db之前一定要先docker network inspect bridge把subnet和gateway记下来重建时填回去。3.2 锁文件与pid文件残留清理后再启动如果日志里出现的不是网络错误而是pid文件相关的提示处理方式就简单很多systemctl stop docker systemctl stop containerd ls -l /var/run/docker.pid cat /var/run/docker.pid rm -f /var/run/docker.pid systemctl start docker/var/run在部分系统上是个软链接指向/run所以清理前最好确认一下真实路径。另外不要太粗暴不要看到docker目录就一把rm -rf。.pid、.sock、.lock这三类文件是残留重灾区把它们挑出来清理就行/var/lib/docker下的数据一个都不能乱动。锁文件如果还残留在别的位置比如/var/lib/docker/.lock同样会导致启动失败。当前没有容器运行时可以一并清理find /var/lib/docker -maxdepth 1 -name *.lock -ls rm -f /var/lib/docker/.lock3.3 overlay2存储损坏从保守修复到干净重置这是三类问题里最麻烦的一种。overlay2存储驱动依赖底层文件系统提供正确的d_type支持异常关机后底层文件系统一旦出现部分元数据损坏Docker在挂载镜像层时会报failed to mount overlay或input/output error。先做保守尝试启动时切换到vfs存储驱动看Docker能不能把服务拉起来systemctl stop docker dockerd --storage-drivervfs这里不建议直接编辑daemon.json先用命令行方式跑能起来说明Docker本体和网络都没事问题确定在overlay2层。vfs只是个诊断手段性能没法用于生产但可以用它将容器数据提交导出完成“抢救”。确认是overlay2层损坏后进一步定位到具体目录ls -l /var/lib/docker/overlay2/ dmesg | grep overlay2常见的情况是某个容器的可写层目录文件不完整Docker启动时扫描所有镜像层和容器层遇到损坏目录就直接退出。遇到这种情况可以临时把这些损坏目录隔离掉不让Docker扫描到但它对应的容器会丢失可写层数据。操作办法是把overlay2目录下的子目录名和/var/lib/docker/image/overlay2/layerdb里的记录交叉比对找到对应容器或镜像确认可以放弃后将损坏目录移到备份位置mv /var/lib/docker/overlay2/损坏目录 /var/lib/docker/overlay2/损坏目录.corrupted systemctl start docker如果损坏面大无法精准定位或者dmesg里明确报了底层文件系统错误那就得先修底层盘。比如虚拟机磁盘用的是ext4先卸载对应分区再fsck -y或者重启进入维护模式执行e2fsck -f -y /dev/vdaX。修完之后再启动Docker很多“起不来”的问题就能恢复。实在不行我只能采取最后手段清空整个overlay2存储池。这么做的前提是你确认所有容器都可以重建、镜像都可以重新拉取或者已经完成了数据备份。清空时保留其他目录systemctl stop docker rm -rf /var/lib/docker/overlay2 systemctl start docker此时容器全部没了镜像标签也可能不全。但至少Docker服务能恢复不会持续影响宿主机上的其他业务。3.4 containerd状态异常别忽略它的独立运行还有一个容易被忽略的环节Docker和containerd的关系。Docker 18.09之后的版本镜像管理、容器运行的底层能力都交给了containerddockerd只是上层控制面。虚机异常关闭后containerd可能残留一堆“任务”状态导致Docker启动时和containerd通信超时或状态校验失败。日志里如果看到Failed to connect to containerd、failed to dial /run/containerd/containerd.sock先处理containerdsystemctl stop docker systemctl stop containerd rm -rf /run/containerd rm -rf /var/lib/docker/containerd systemctl start containerd systemctl start docker删除/var/lib/docker/containerd这个操作要谨慎它保存了containerd的容器元数据。如果这个目录损坏即便Docker能启动所有容器状态也会被重置。但反过来如果它损坏了还硬撑着Docker服务就永远起不来。两害相权取其轻确认没有正在运行的业务容器时可以这么处理。4. 修复过程中的保底操作数据与镜像的备份抢救4.1 在一切操作前先做一次数据快照我发现自己有个习惯修复前都会先给自己留一条退路。无论问题看起来多简单执行rm或mv之前都先做一层快照。虚机上可以直接用LVM快照或者直接复制整个/var/lib/docker目录cp -a /var/lib/docker /var/lib/docker.bak不要小看这一步Docker的数据目录在服务停止时是可以整体复制的它的体积可能很大但为了数据安全这个空间占用是值得的。如果后续操作把数据搞得更糟至少还能退回去重新诊断不至于直接宣布“数据丢了”。4.2 即使服务起不来也要把镜像和容器数据捞出来如果Docker无论如何都起不来systemctl start docker反复失败还有两个办法能捞数据。第一个办法用dockerd命令行直接启动并指定一个干净的存储目录比如dockerd --data-root /var/lib/docker-rescue --storage-drivervfs这样Docker会以全新状态启动不会碰到原来损坏的目录。只要服务起来了就可以把原来的镜像重新打标或导出。第二个办法如果完全不想碰Docker进程直接去文件系统层面抢救数据卷数据在/var/lib/docker/volumes/下每个卷一个目录容器可写层在/var/lib/docker/overlay2/ID/diff/下。直接把这些目录复制出来就算Docker彻底废了业务数据大概率也保住了。容器虽然停了但它的配置和挂载关系都在/var/lib/docker/containers/容器ID/config.v2.json里这个文件也能作为事后重建容器的参考。5. 常见问题与排查技巧实录5.1 “Failed to start docker.service”但日志只有一行怎么查有一种场景很气人systemctl status docker只显示一行Failed to start docker.servicejournal里也没有更多细节。这种情况多半是systemd的unit配置或执行环境出了问题。先看systemctl cat docker.service执行ExecStart那一行是不是正常的。有些环境里Docker的systemd unit被云平台初始化脚本改过或者/etc/docker/daemon.json里写入了无法解析的配置导致dockerd还没起来就崩了。这种情况很好查dockerd --validate如果daemon.json有语法错误这个命令会直接报出来。还可以直接把daemon.json临时改名用默认参数启动试试mv /etc/docker/daemon.json /etc/docker/daemon.json.bak systemctl start docker能启动说明问题出在自定义配置项上。逐个排查daemon.json里的每一项重点看storage-driver、>docker logs --tail 50 容器名 docker inspect 容器名 --format {{.State.ExitCode}} {{.State.Error}}很多容器异常关闭后PID 1进程没有正常退出留下了一些子进程或锁文件。虚机重启后容器内的应用状态早已错乱。常见做法是docker start之前在容器外做一次恢复比如数据库容器先临时把入口命令改成sleep或bash进入容器把残留的pid、socket、临时表清理掉再恢复原入口命令。如果某个容器无论如何都起不来就把它先提交成快照镜像再用快照重新启动一个临时容器拉数据docker commit 容器名 rescue-image:latest docker run -it --rm rescue-image:latest /bin/bash这样一来即使原容器废了文件系统层面的数据还在。5.3 同一套方案换台机器不起作用先想这三件事你在网上查到一个方案照着执行结果在A机器上有效在B机器上报错完全一样却怎么都不行。这种经历我相信很多人都遇到过。我的经验是先检查三件事。第一两边的Docker版本和存储驱动是否一致。同一报错overlay2上的处理办法在vfs上可能完全不适用。第二/var/lib/docker的挂载文件系统类型是否一致。有的虚拟机制作镜像时用了XFS有的用了ext4有的把/var/lib/docker单独挂了一块盘。没有覆盖这一层差异的“通用方案”是不存在的。第三是否有外部工具干扰Docker运行。虚机上常驻的云监控agent、安全加固脚本、自定义iptables规则都会在系统启动时动Docker的东西。修复方案本身没问题却被这些第三方软件反复覆盖配置导致看起来是Docker的问题实际上是它被“拉偏架”了。5.4 一个额外的经验不要忽略系统时间和cgroup异常虚机异常关闭后系统时间可能被严重重置dockerd启动时如果校验镜像层时间戳可能报出奇怪的file exists或operation not permitted错误。先用date看一眼当前时间必要时systemctl restart systemd-timesyncd或chronyd同步一次。cgroup也是容易踩坑的地方。虚机重启后内核的cgroup挂载可能变成“只读”或根本没挂载Docker启动时会报failed to mount cgroup。此时检查mount -l | grep cgroup如果cgroup相关挂载为空尝试重新挂载mount -t cgroup2 none /sys/fs/cgroup或者恢复systemd的cgroup管理systemctl reset-failed docker.service6. 如何避免下次再折腾给虚机Docker环境上的三道保险6.1 配置可靠的存储驱动与文件系统Docker虚拟机最常见的坑是在虚机制作镜像时把系统盘格式化成XFS但创建时没开ftype1导致Overlay2无法正常工作。新环境第一次运行Docker不会暴露问题等虚机异常关闭重启后才集中爆发。检查一下当前文件系统的ftypexfs_info /var/lib/docker | grep ftype如果输出是ftype0说明底层文件系统不支持Overlay2所需的d_type能力。最好的办法是重做虚机磁盘用ftype1创建XFS或者干脆换成ext4。/var/lib/docker单独挂一块盘也是个好习惯这样即使系统盘被搞残Docker数据还在。6.2 给虚机设置优雅关机策略虚机平台上的“关机关机”和物理机直接断电有时候是等价的。在KVM/libvirt环境里shutdown命令需要虚机里装了qemu-guest-agent才能传递到操作系统VMware里对应的是VMware Tools。没有guest agent你点“关机”本质上就是强制断电Docker每经历一次就多一次启动失败的概率。给每台虚机安装对应平台的guest agent同时把Docker的systemd unit设置成优雅关闭systemctl enable docker正常来说systemd stop时会发送SIGTERM给dockerddockerd会尽量恢复现场把容器安全停掉。但如果有容器卡死Docker会一直等待导致虚机关机超时被强制kill。为了规避这个问题可以在/etc/systemd/system/docker.service.d/override.conf里调整超时时间[Service] TimeoutStopSec30这样给Docker一个明确的关闭窗口避免虚机平台等不及直接断电。6.3 日常巡检与备份计划一份小到可以手工执行的备份脚本价值远大于花里胡哨的灾备方案。我最常用的方式是每天对镜像做一次docker save对卷目录做一次增量同步tar czf /backup/docker-images-$(date %F).tar.gz 镜像列表 tar czf /backup/docker-volumes-$(date %F).tar.gz /var/lib/docker/volumes容器配置不用每天备份docker inspect导出的JSON结构里都有需要的时候重建就行但它对应的业务数据没有备份就真的没了。考虑到虚机异常关闭不可控至少保证“容器没了能重建数据丢了能恢复”这条底线。我最后再分享一个实际操作中的习惯每次修复完Failed to start docker.service之后我都会顺手执行一遍docker run --rm hello-world来验证整个容器生命周期而不只是看服务状态是active。服务状态正常和容器能真正跑起来是两码事。这个验证动作只要几秒钟却能提前发现网络、存储、cgroup层面的隐藏问题省得业务方半夜再把你叫起来。