Docker data-root迁移完整指南:停服复制改配置启动清理
如果你接手过一台跑了一段时间的服务器多半会遇到“明明没装多少东西系统盘却莫名其妙满了”的情况。打开df -h一看/var/lib/docker已经吃掉了几十 GB镜像、容器层、命名卷和日志全堆在容量有限的分区里而旁边那块大容量数据盘却空空如也。Docker 默认把数据放在>docker info --format {{.DockerRootDir}}同时确认存储驱动docker info --format {{.Driver}}绝大多数现代环境输出是overlay2如果你看到vfs说明存储开销会异常大迁移后建议重新考虑配置。还要注意rootless 模式下 Docker 数据根目录通常不在/var/lib/docker而在用户目录下比如~/.local/share/docker。本文主要面向传统的 rootful 模式但在动手前先确认总没错。2.2 统计磁盘占用选对目标盘确认当前路径后要看数据量到底有多大sudo du -sh /var/lib/docker sudo du -sh /var/lib/docker/* | sort -h然后看目标位置的空间是否充足df -h /data df -h /var/lib/docker目标盘剩余空间最好大于当前 Docker 数据量的 1.2 倍。为什么留 20% 余量因为 Docker 运行期间还会继续产生容器日志、镜像临时层和构建缓存迁移完成初期你不会立刻清理所有旧数据目标盘如果刚好卡在临界值几天后又会告警。除了空间还要确认目标盘的文件系统。一般用 ext4 或 xfs 就足够稳定尽量避免把 Docker>docker ps -aq /backup/container-list.txt docker volume ls -q /backup/volume-list.txt如果容器里有数据库这类强一致性的服务最好让应用先优雅停服不要只依赖systemctl stop docker去强停。数据量几十 GB 时rsync 复制可能需要几分钟到几十分钟上百 GB 就更要预留足够时间。3. 完整迁移实操停服—复制—改配置—启动—清理3.1 停止 Docker 并防止服务被自动拉起很多人只执行sudo systemctl stop docker然后开始复制结果复制到一半发现 Docker 又活了。原因是 systemd 环境下还有docker.socket单元只要有客户端调用 Docker 命令socket 可能触发守护进程自动启动。稳妥的做法是同时停掉 service 和 socketsudo systemctl stop docker.service docker.socket停完以后检查确认真没有 dockerd 进程残留pgrep -a dockerd || echo dockerd is stopped这里要强调不要用kill -9强行杀掉 dockerd。强制终止可能导致容器的写层、镜像元数据没有刷盘在某些存储驱动下会留下需要人工修复的 overlay 状态。正常systemctl stop docker会等待容器进入停止状态数据一致性更有保障。3.2 用 rsync 完整复制数据参数要理解清楚创建目标目录sudo mkdir -p /data/docker然后执行复制sudo rsync -aHAXS --numeric-ids \ /var/lib/docker/ \ /data/docker/这几个参数不是随手写的每个都对应 Docker 数据目录的特殊性-a归档模式保留权限、属主、时间戳和符号链接-H保留硬链接。overlay2 的镜像层之间广泛使用硬链接如果丢了这个参数复制出来的镜像层可能物理上变成多份空间占用翻倍-A和-X保留 ACL 和扩展属性SELinux 环境下尤其需要-S稀疏文件处理避免日志、容器可写层里的空洞文件复制后占满额外空间--numeric-ids按数字 UID/GID 保留属主而不是按用户名翻译避免目标机器用户名映射不一致导致权限错乱。这里最容易犯的错误是漏掉源路径末尾的/。rsync -a /var/lib/docker /data/docker会把/var/lib/docker作为子目录放到/data/docker里面最终得到/data/docker/var/lib/docker只有写成/var/lib/docker/才会把目录内容直接复制到/data/docker下。复制完成后可以再跑一遍同样的 rsync 增量同步并把输出打开sudo rsync -aHAXS --numeric-ids --itemize-changes \ /var/lib/docker/ \ /data/docker/如果没有任何f或类似输出说明源和目标已经一致。增量同步在迁移场景下是好习惯第一次复制后有小文件或元数据变化第二遍能补上。3.3 修改 daemon.json把>{ data-root: /data/docker, log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }提交前检查 JSON 语法不要出现多余逗号字段名是>systemctl cat docker看到包含--data-root的内容就说明存在参数冲突。3.4 启动 Docker验证迁移结果配置完启动服务sudo systemctl daemon-reload sudo systemctl start docker确认新路径已经生效docker info --format {{.DockerRootDir}}如果输出/data/docker说明>sudo mv /var/lib/docker /var/lib/docker.old-$(date %Y%m%d.%H%M)保留旧路径的好处是万一新目录有问题改回daemon.json里的>sudo rm -rf /var/lib/docker.old-*这里要注意旧目录没有删除前系统盘空间并不会释放。如果系统盘已经 90% 饱和重命名只是让/var/lib/docker这个路径空出来但旧目录仍占着同样的空间。这种情况就必须在业务验证后尽快删除旧数据或者先备份到目标盘再删。4. 容易踩坑的细节容器、卷、镜像与日志4.1 运行中的容器数据真的不会丢吗迁移期间 Docker 已经停止容器进程被强制或正常终止。只要systemctl stop docker正常完成容器写层已经落盘rsync 复制的是静止状态的数据一致性没有问题。真正的风险点是那些设置了restart: always的容器。Docker 守护进程启动后这类容器会自动拉起如果你还没准备好它们可能在验证阶段突然开始跑业务、写数据。更好的做法是在停 Docker 之前先把关键容器的重启策略临时关掉docker update --restartno 容器名等全部验证完了再docker start并恢复原来的重启策略。我在实际迁移中见过因为没关这个策略Docker 一启动数据库容器就自动拉起结果另一套备份任务也同时触发磁盘压力瞬间飙升迁移窗口被拉长。所以“停机前先处理 restart policy”这话很值得刻在脑子里。4.2 命名卷与 bind mount不是所有数据都在>sudo ls /data/docker sudo ls /data/docker/var/lib/docker如果看到这种嵌套就说明复制路径搞错了。第三步查权限rsync -a能保留属主但如果目标目录创建时用了错误的所有者Docker 可能无法读取元数据。用ls -ld /data/docker确认属主是root:root。5.2 磁盘空间迟迟不释放最常见的原因就是旧目录还在。mv重命名后旧数据依然占据系统盘必须等确认无误后删除。如果删除了旧目录系统盘却还是满的检查是否有进程占用了已删除文件的句柄lsof L1 | grep deleted如果看到dockerd或容器进程还在旧路径上打开文件删掉的文件不会真正释放空间需要重启相关进程。另外别忘了排查宿主日志和临时目录。可以用du -h -x -d1 /从根开始逐层看找出真正的空间大户。5.3 daemon.json 配置导致服务起不来daemon.json 出问题最典型的表现是journalctl -u docker里出现 JSON 解析错误比如invalid character } looking for beginning of object key string。这多半是手写配置时逗号位置不对。另一个常见坑是>sudo restorecon -R /data/dockerrootless Docker 的迁移思路类似但涉及 systemd user 服务和路径下的~/.local/share/docker并且不能直接用systemctl stop docker这种系统级命令需要单独写一篇。普通场景不需要过度担心但至少要意识到“rootful 和 rootless 是两个世界”。6. 长期运维建议与自动化思路6.1 在新机器初始化阶段就规划好数据盘迁移一次后你就明白与其等系统盘报警再折腾不如新机器初始化时直接把 Docker 数据目录放到独立数据盘上。两种方式一种是把数据盘直接挂载到/var/lib/docker另一种是保留/var/lib/docker默认路径在 daemon.json 里设置>docker system df df -h /data/docker df -h / du -sh /var/lib/docker /data/docker 2/dev/null重点不是看当前用了多少而是看增长趋势。如果docker system df里 Build Cache 一直在涨可以定期docker builder prune -f如果容器日志越来越大就去检查log-opts是否生效。清理镜像缓存用docker image prune -a时要谨慎-a会删除所有未被容器引用的镜像很多补救依赖旧镜像回到稳定版本删了就拉不回来了。我个人的原则是镜像可以按需清理但卷数据不要随意docker volume prune脏数据比多占点空间可怕得多。6.3 把迁移封装成可复用的检查脚本迁移本身是一次性动作但如果你经常帮团队承接新服务器把流程脚本化会很有价值。一个最小化脚本至少应该包含检查当前>