Docker 目录占用磁盘空间太大?教你安全迁移 Docker 根目录

发布时间:2026/10/1 1:56:49
Docker 目录占用磁盘空间太大?教你安全迁移 Docker 根目录 Docker 目录占用磁盘空间太大教你安全迁移 Docker 根目录一、迁移前确认 Docker 当前根目录二、迁移前是否需要清理数据三、创建新的 Docker 数据目录四、第一次预同步五、预同步过程中可以中断吗六、正式切换前停止 Docker七、执行最终增量同步--delete 并不是删除重复文件八、不确定 --delete 是否安全时先模拟九、验证 rsync 是否真正同步完成十、为什么 rsync 显示 1.8TB但 du 只有 88GB十一、为什么迁移后目标目录可能比源目录大十二、修改 Docker>/opt/docker但/opt分区空间逐渐不足而服务器同时存在容量更大的/mnt数据盘。此时可以将 Docker 根目录从/opt/docker迁移到/mnt/docker同时保证原有容器不丢失镜像不丢失Docker Volume 不丢失容器配置不丢失文件权限、ACL、扩展属性、硬链接正确保留overlay2数据完整出现问题可以快速回滚验证无误后能够安全删除旧目录一、迁移前确认 Docker 当前根目录首先确认 Docker 当前真正使用的数据目录dockerinfo|grepDocker Root Dir或者dockerinfo--format{{.DockerRootDir}}迁移前应该看到/opt/docker检查磁盘df-Th/opt /mnt查看 Docker 总占用du-sh/opt/docker查看一级目录占用du-sh/opt/docker/*|sort-hr例如84G /opt/docker/overlay2 3.2G /opt/docker/volumes 898M /opt/docker/containers 23M /opt/docker/image 6.5M /opt/docker/buildkit通常占用最大的目录是overlay2二、迁移前是否需要清理数据可以先检查 Docker 当前空间使用dockersystemdf查看退出容器dockerps-a查看悬空镜像dockerimages-fdanglingtrue查看未使用 Volumedockervolumels-fdanglingtrue确认确实不需要后可以按模块清理dockercontainer prunedockerimage prunedockerbuilder prune生产环境不建议直接执行dockersystem prune-a--volumes特别是--volumes可能删除当前没有被容器引用、但实际仍需要保留的数据卷。Docker 根目录迁移本身并不要求提前执行清理。三、创建新的 Docker 数据目录创建目标目录sudomkdir-p/mnt/docker确认对应的是目标数据盘df-Th/mnt/docker例如Filesystem Type Size Used Avail Use% Mounted on /dev/sdd1 xfs 500G 130G 370G 26% /mnt四、第一次预同步如果 Docker 数据量已经达到几十 GB、几百 GB甚至更多不建议停机以后才开始第一次完整复制。推荐Docker 运行期间先完成大部分静态数据预同步最后只进行一次短时间停机增量同步。执行sudorsync-aHAXS--numeric-ids\--partial\--human-readable\--infoprogress2\/opt/docker/ /mnt/docker/参数说明参数作用-aArchive 模式保留权限、时间、符号链接等-H保留硬链接-A保留 ACL-X保留扩展属性-S尽可能保留稀疏文件--numeric-ids按实际 UID/GID 数值复制--partial中断后保留未完整传输的数据--human-readable使用可读单位--infoprogress2显示整个迁移任务的总体进度注意/opt/docker/结尾的/非常重要。它表示将/opt/docker目录中的内容同步到/mnt/docker。不会生成/mnt/docker/docker五、预同步过程中可以中断吗可以。直接Ctrl C不会影响/opt/docker源数据。重新执行同一条 rsyncsudorsync-aHAXS--numeric-ids\--partial\--human-readable\--infoprogress2\/opt/docker/ /mnt/docker/rsync 会重新扫描只同步未完成或发生变化的数据。因此大数据量场景下可以提前进行一次甚至多次增量同步。六、正式切换前停止 Docker第一次预同步可以在 Docker 运行时执行。但是最后一次一致性同步必须停止 Docker。否则可能存在容器日志仍在写入Volume 数据继续发生变化MySQL、Redis 等数据库正在写文件overlay2文件发生变化Docker 元数据发生变化停止sudosystemctl stopdocker建议同时停止 Socketsudosystemctl stop docker.socket确认systemctl statusdocker--no-pager也可以ps-ef|grepdocker如果服务器上的 containerd 是 Docker 独占使用可以按实际环境进一步停止sudosystemctl stop containerd如果存在 Kubernetes 或其他程序共享 containerd不要随意停止。七、执行最终增量同步Docker 完全停止后执行最终同步sudorsync-aHAXS--numeric-ids\--delete\--partial\--human-readable\--infoprogress2\/opt/docker/ /mnt/docker/这里增加了--delete含义是删除/mnt/docker中存在、但是/opt/docker已经不存在的文件。最终目标是让/mnt/docker尽可能成为/opt/docker的完整镜像。--delete并不是删除重复文件例如源 /opt/docker/A /opt/docker/B 目标 /mnt/docker/A /mnt/docker/B /mnt/docker/C执行带--delete的 rsync 后/mnt/docker/C会被删除。因此目标目录/mnt/docker应该专门用于此次 Docker 迁移。八、不确定--delete是否安全时先模拟如果目标目录之前存在历史数据可以先执行sudorsync-aHAXS--numeric-ids\--dry-run\--delete\--itemize-changes\/opt/docker/ /mnt/docker/其中--dry-run表示只模拟不真正复制也不真正删除。如果看到*deleting xxx代表真正执行时这个目标文件将会被删除。确认没有问题后再执行正式同步。九、验证 rsync 是否真正同步完成最终同步结束后再进行一次 dry-runsudorsync-aHAXS--numeric-ids\--delete\--dry-run\--stats\/opt/docker/ /mnt/docker/理想结果Number of created files: 0 Number of deleted files: 0 Number of regular files transferred: 0 Total transferred file size: 0 bytes分别意味着没有新增文件需要同步 没有目标端旧文件需要删除 没有普通文件需要重新传输 剩余传输量为 0例如实际可能看到Number of files: 423,912 Number of created files: 0 Number of deleted files: 0 Number of regular files transferred: 0 Total transferred file size: 0 bytes达到这个状态可以认为文件层面的迁移已经完成。十、为什么 rsync 显示 1.8TB但 du 只有 88GBDocker 目录可能出现Total file size: 1,813,126,247,386 bytes也就是逻辑数据约1.81 TB但是du-sh/opt/docker可能只有88G这并不冲突。Dockeroverlay2中可能存在稀疏文件hard linkOverlayFS 数据大量小文件rsync 中的Total file size更接近文件逻辑大小。而du-sh统计的是磁盘实际分配的 Block。可以查看 apparent sizedu-sh--apparent-size /opt/dockerdu-sh--apparent-size /mnt/docker可能都是1.7T这就与 rsync 统计的约 1.8TB 逻辑数据基本对应。十一、为什么迁移后目标目录可能比源目录大例如/opt/docker 88G /mnt/docker 90G不能因此直接判断迁移失败。继续查看du-sh/opt/docker/*|sort-hrdu-sh/mnt/docker/*|sort-hr可能看到/opt/docker/overlay2 84G /mnt/docker/overlay2 86G /opt/docker/volumes 3.2G /mnt/docker/volumes 3.2G /opt/docker/containers 898M /mnt/docker/containers 898M这类 2GB 左右的物理占用差异可能来自Sparse File 实际块分配不同XFS extent 分配不同小文件 Block 利用率不同文件系统内部布局不同即使两边都是XFS 4K Block也不能要求物理空间占用完全一致。因此迁移成功不能简单判断du 大小必须 100% 一致更可靠的依据应该是rsync dry-run created 0 deleted 0 transferred 0同时du-sh--apparent-size /opt/dockerdu-sh--apparent-size /mnt/docker逻辑数据量基本一致。十二、修改 Docker>/etc/docker/daemon.json增加{data-root:/mnt/docker}如果daemon.json中本身存在其他配置不能直接覆盖。例如{log-driver:json-file,log-opts:{max-size:100m,max-file:3}}应该修改为{data-root:/mnt/docker,log-driver:json-file,log-opts:{max-size:100m,max-file:3}}检查cat/etc/docker/daemon.json如果安装了jqjq./etc/docker/daemon.json十三、启动 Docker启动sudosystemctl startdocker如果之前停止了docker.socket也可以恢复sudosystemctl start docker.socket查看状态systemctl statusdocker--no-pager十四、确认 Docker Root Dir 已经切换执行dockerinfo--format{{.DockerRootDir}}必须返回/mnt/docker或者dockerinfo|grepDocker Root Dir显示Docker Root Dir: /mnt/docker如果仍然显示/opt/docker说明新的配置没有生效。这种情况下绝对不能删除/opt/docker。十六、检查容器、镜像和 Volume确认所有容器dockerps-a检查镜像dockerimages检查 Volumedockervolumels检查网络dockernetworkls如果这里原图实际对应的是 Volume、容器或其他验证页面可以继续保持在“迁移后资源验证”这一节中。重点确认MySQLRedisElasticsearchOracleRabbitMQNginx其他业务容器都存在并且状态正常。十七、确认运行容器使用的是/mnt/docker可以抽查一个正在运行的容器dockerinspect$(dockerps-q|head-1)\--format{{json .GraphDriver.Data}}正常应该能够看到/mnt/docker/overlay2/...这说明运行中的容器 Overlay 数据确实已经来自新目录。十八、检查有没有容器 Bind Mount 旧路径Docker 根目录改成/mnt/docker后还有另外一种情况必须注意。某些容器可能在启动时显式配置了/opt/docker/xxx:/xxx这种属于 Bind Mount不受 Dockerdata-root控制。检查dockerinspect$(dockerps-aq)\--format{{.Name}} {{range .Mounts}}{{.Source}} - {{.Destination}} {{end}}\|grep/opt/docker如果没有输出说明当前容器没有直接依赖/opt/docker如果有输出则必须逐项确认这些 Bind Mount 是否还需要。十九、检查旧目录是否仍然被进程访问执行sudolsofD /opt/docker2/dev/null|head如果没有任何输出无输出说明没有发现进程正在打开旧目录文件。继续检查挂载mount|grep/opt/docker理想情况下也没有输出。注意lsof无输出只是一个检查维度不应该单独作为删除旧目录的依据。必须结合Docker Root Dir容器镜像VolumeOverlay2Bind Mount业务验证综合判断。二十、不要直接删除先隔离旧目录即使所有检查都通过也不建议马上sudorm-rf/opt/docker更安全的方法是将旧数据目录先改名使 Docker 无法再通过原路径使用它。先停止 Dockersudosystemctl stopdockersudosystemctl stop docker.socket然后sudomv/opt/docker /opt/docker.bak再启动sudosystemctl startdocker此时旧数据仍然存在/opt/docker.bak但/opt/docker已经不存在。如果 Docker 和业务仍能正常运行就能更有力地证明当前系统确实已经不依赖旧 Docker 目录。二十一、隔离旧目录以后再次验证再次检查dockerinfo--format{{.DockerRootDir}}必须仍然/mnt/docker然后dockerps-adockerimagesdockervolumelsdockernetworkls查看 Docker 服务systemctl statusdocker--no-pager查看日志journalctl-udocker-n100--no-pager最关键的是真正验证业务。对于数据库容器至少需要确认能正常连接数据可以查询可以正常写入容器重启后数据仍然存在对于普通业务页面正常API 正常文件读写正常容器重启正常单纯dockerps显示Up并不能证明数据迁移一定完全正确。二十二、旧/opt/docker什么时候可以删除满足下面这些条件后才建议进入删除阶段检查项目正常结果rsync 最终 dry-runtransferred 0rsync 文件创建created 0rsync 多余文件deleted 0Docker Root Dir/mnt/dockerDocker 启动正常docker ps -a容器完整docker images镜像完整docker volume lsVolume 完整GraphDriver使用/mnt/docker/overlay2/opt/dockerBind Mount无lsof D /opt/docker无mount | grep /opt/docker无/opt/docker改名以后Docker 仍可正常启动关键业务可以正常读写Docker 再次重启正常其中最重要的一次验证是/opt/docker ↓ /opt/docker.bak以后Docker 和业务仍然全部正常。二十三、建议保留旧数据 37 天迁移完成后不建议当天就删除/opt/docker.bak生产环境推荐至少保留37 天重要环境也可以保留更久。期间观察Docker 多次重启是否正常服务器重启后是否正常MySQL 数据是否正常Redis 数据是否正常Elasticsearch 是否正常Volume 是否正常应用读写是否正常Docker 日志是否存在异常确认稳定后再删除。二十四、最终删除旧数据确认无问题以后sudorm-rf/opt/docker.bak查看释放空间df-h/opt此时原/opt分区中的 Docker 数据才真正被释放。二十五、迁移失败如何快速回滚这就是为什么迁移后不要马上删除旧目录。如果/opt/docker已经改成/opt/docker.bak而新目录运行出现问题可以sudosystemctl stopdockersudosystemctl stop docker.socket恢复sudomv/opt/docker.bak /opt/docker把/etc/docker/daemon.json恢复为{data-root:/opt/docker}然后sudosystemctl startdocker确认dockerinfo--format{{.DockerRootDir}}重新输出/opt/docker即可恢复旧数据目录。二十六、推荐生产迁移完整命令1. 创建目录sudomkdir-p/mnt/docker2. 第一次预同步sudorsync-aHAXS--numeric-ids\--partial\--human-readable\--infoprogress2\/opt/docker/ /mnt/docker/3. 停止 Dockersudosystemctl stopdockersudosystemctl stop docker.socket4. 最终一致性同步sudorsync-aHAXS--numeric-ids\--delete\--partial\--human-readable\--infoprogress2\/opt/docker/ /mnt/docker/5. 验证是否还存在数据差异sudorsync-aHAXS--numeric-ids\--delete\--dry-run\--stats\/opt/docker/ /mnt/docker/理想输出Number of created files: 0 Number of deleted files: 0 Number of regular files transferred: 0 Total transferred file size: 0 bytes6. 修改 Docker 根目录{data-root:/mnt/docker}7. 启动 Dockersudosystemctl startdocker8. 验证 Docker Root Dirdockerinfo--format{{.DockerRootDir}}返回/mnt/docker9. 验证 Docker 资源dockerps-adockerimagesdockervolumelsdockernetworkls10. 检查旧目录sudolsofD /opt/docker2/dev/null|headmount|grep/opt/dockerdockerinspect$(dockerps-aq)\--format{{.Name}} {{range .Mounts}}{{.Source}} - {{.Destination}} {{end}}\|grep/opt/docker11. 隔离旧目录sudosystemctl stopdockersudosystemctl stop docker.socketsudomv/opt/docker /opt/docker.baksudosystemctl startdocker12. 再次验证dockerinfo--format{{.DockerRootDir}}dockerps-adockerimagesdockervolumels同时验证实际业务。13. 保留回滚窗口建议37 天14. 最终删除sudorm-rf/opt/docker.bak二十七、最终判断标准迁移是否成功不能只判断du -sh 两边大小是否一样正确的完整判断链路应该是确认原 Docker Root Dir ↓ 第一次 rsync 预同步 ↓ 停止 Docker ↓ 最终增量同步 ↓ rsync dry-run 0 ↓ 修改>FAQ迁移 Docker 数据必须全程停机吗不需要。大数据量推荐Docker 正常运行 ↓ 第一次 rsync ↓ 停止 Docker ↓ 最终增量 rsync ↓ 切换>rsync 中途可以 CtrlC 吗可以。使用--partial后中断后已经完成的文件不会全部重新复制。重新执行 rsync 即可继续增量同步。为什么推荐-aHAXSDocker 数据中可能涉及权限UID/GID符号链接Hard LinkACLExtended AttributeSparse File因此-aHAXS--numeric-ids比cp-r更适合 Docker 数据根目录迁移。为什么/mnt/docker比/opt/docker大一点例如/opt/docker 88G /mnt/docker 90G不代表一定多迁移了数据。如果rsync dry-run: created 0 deleted 0 transferred 0并且du-sh--apparent-size /opt/dockerdu-sh--apparent-size /mnt/docker逻辑大小相同或基本相同则更可能是 Sparse File、XFS extent 和实际 Block 分配方式产生的差异。可以直接rm -rf /opt/docker吗不建议。推荐/opt/docker ↓ /opt/docker.bak ↓ 重启 Docker ↓ 验证所有业务 ↓ 观察 37 天 ↓ rm -rf /opt/docker.bak这样出现异常时仍有完整回滚数据。总结Docker 根目录迁移真正重要的不是单纯执行一次rsync而是建立一个完整迁移闭环预同步 ↓ 停止 Docker ↓ 最终同步 ↓ 文件一致性验证 ↓ 修改>原目录/opt/docker 新目录/mnt/docker最终至少应该确认dockerinfo--format{{.DockerRootDir}}输出/mnt/docker同时最终sudorsync-aHAXS--numeric-ids\--delete\--dry-run\--stats\/opt/docker/ /mnt/docker/显示Number of created files: 0 Number of deleted files: 0 Number of regular files transferred: 0 Total transferred file size: 0 bytes最后再通过/opt/docker → /opt/docker.bak完成隔离验证。确认 Docker、容器、Volume、数据库和实际业务均正常后保留一段时间再最终删除sudorm-rf/opt/docker.bak这样才是一套相对完整、安全、可回滚的 Docker 根目录生产迁移流程。