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|grep"Docker 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-fdangling=true查看未使用 Volume:
dockervolumels-fdangling=true确认确实不需要后,可以按模块清理:
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\--info=progress2\/opt/docker/ /mnt/docker/参数说明:
参数 作用 -aArchive 模式,保留权限、时间、符号链接等 -H保留硬链接 -A保留 ACL -X保留扩展属性 -S尽可能保留稀疏文件 --numeric-ids按实际 UID/GID 数值复制 --partial中断后保留未完整传输的数据 --human-readable使用可读单位 --info=progress2显示整个迁移任务的总体进度 注意:
/opt/docker/结尾的
/非常重要。它表示:
将
/opt/docker目录中的内容同步到/mnt/docker。不会生成:
/mnt/docker/docker五、预同步过程中可以中断吗?
可以。
直接:
Ctrl + C不会影响
/opt/docker源数据。重新执行同一条 rsync:
sudorsync-aHAXS--numeric-ids\--partial\--human-readable\--info=progress2\/opt/docker/ /mnt/docker/rsync 会重新扫描,只同步未完成或发生变化的数据。
因此大数据量场景下,可以提前进行一次甚至多次增量同步。
六、正式切换前停止 Docker
第一次预同步可以在 Docker 运行时执行。
但是:
最后一次一致性同步必须停止 Docker。
否则可能存在:
- 容器日志仍在写入
- Volume 数据继续发生变化
- MySQL、Redis 等数据库正在写文件
overlay2文件发生变化- Docker 元数据发生变化
停止:
sudosystemctl stopdocker建议同时停止 Socket:
sudosystemctl 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\--info=progress2\/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-run:
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分别意味着:
没有新增文件需要同步 没有目标端旧文件需要删除 没有普通文件需要重新传输 剩余传输量为 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 只有 88GB?
Docker 目录可能出现:
Total file size: 1,813,126,247,386 bytes也就是逻辑数据约:
1.81 TB但是:
du-sh/opt/docker可能只有:
88G这并不冲突。
Docker
overlay2中可能存在:- 稀疏文件
- hard link
- OverlayFS 数据
- 大量小文件
rsync 中的:
Total file size更接近文件逻辑大小。
而:
du-sh统计的是磁盘实际分配的 Block。
可以查看 apparent size:
du-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如果安装了
jq:jq./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|grep"Docker Root Dir"显示:
Docker Root Dir: /mnt/docker如果仍然显示:
/opt/docker说明新的配置没有生效。
这种情况下:
绝对不能删除
/opt/docker。十六、检查容器、镜像和 Volume
确认所有容器:
dockerps-a检查镜像:
dockerimages检查 Volume:
dockervolumels检查网络:
dockernetworkls如果这里原图实际对应的是 Volume、容器或其他验证页面,可以继续保持在“迁移后资源验证”这一节中。
重点确认:
- MySQL
- Redis
- Elasticsearch
- Oracle
- RabbitMQ
- Nginx
- 其他业务容器
都存在并且状态正常。
十七、确认运行容器使用的是
/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,不受 Docker
data-root控制。检查:
dockerinspect$(dockerps-aq)\--format'{{.Name}} {{range .Mounts}}{{.Source}} -> {{.Destination}} {{end}}'\|grep'/opt/docker'如果没有输出,说明当前容器没有直接依赖:
/opt/docker如果有输出,则必须逐项确认这些 Bind Mount 是否还需要。
十九、检查旧目录是否仍然被进程访问
执行:
sudolsof+D /opt/docker2>/dev/null|head如果没有任何输出:
<无输出>说明没有发现进程正在打开旧目录文件。
继续检查挂载:
mount|grep'/opt/docker'理想情况下也没有输出。
注意:
lsof无输出只是一个检查维度,不应该单独作为删除旧目录的依据。必须结合:
- Docker Root Dir
- 容器
- 镜像
- Volume
- Overlay2
- Bind Mount
- 业务验证
综合判断。
二十、不要直接删除,先隔离旧目录
即使所有检查都通过,也不建议马上:
sudorm-rf/opt/docker更安全的方法是:
将旧数据目录先改名,使 Docker 无法再通过原路径使用它。
先停止 Docker:
sudosystemctl 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-run transferred = 0 rsync 文件创建 created = 0 rsync 多余文件 deleted = 0 Docker 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 和业务仍然全部正常。
二十三、建议保留旧数据 3~7 天
迁移完成后,不建议当天就删除:
/opt/docker.bak生产环境推荐至少保留:
3~7 天重要环境也可以保留更久。
期间观察:
- 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\--info=progress2\/opt/docker/ /mnt/docker/3. 停止 Docker
sudosystemctl stopdockersudosystemctl stop docker.socket4. 最终一致性同步
sudorsync-aHAXS--numeric-ids\--delete\--partial\--human-readable\--info=progress2\/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. 启动 Docker
sudosystemctl startdocker8. 验证 Docker Root Dir
dockerinfo--format'{{.DockerRootDir}}'返回:
/mnt/docker9. 验证 Docker 资源
dockerps-adockerimagesdockervolumelsdockernetworkls10. 检查旧目录
sudolsof+D /opt/docker2>/dev/null|headmount|grep'/opt/docker'dockerinspect$(dockerps-aq)\--format'{{.Name}} {{range .Mounts}}{{.Source}} -> {{.Destination}} {{end}}'\|grep'/opt/docker'11. 隔离旧目录
sudosystemctl stopdockersudosystemctl stop docker.socketsudomv/opt/docker /opt/docker.baksudosystemctl startdocker12. 再次验证
dockerinfo--format'{{.DockerRootDir}}'dockerps-adockerimagesdockervolumels同时验证实际业务。
13. 保留回滚窗口
建议:
3~7 天14. 最终删除
sudorm-rf/opt/docker.bak二十七、最终判断标准
迁移是否成功不能只判断:
du -sh 两边大小是否一样正确的完整判断链路应该是:
确认原 Docker Root Dir ↓ 第一次 rsync 预同步 ↓ 停止 Docker ↓ 最终增量同步 ↓ rsync dry-run = 0 ↓ 修改>FAQ迁移 Docker 数据必须全程停机吗?
不需要。
大数据量推荐:
Docker 正常运行 ↓ 第一次 rsync ↓ 停止 Docker ↓ 最终增量 rsync ↓ 切换>rsync 中途可以 Ctrl+C 吗?可以。
使用:
--partial后,中断后已经完成的文件不会全部重新复制。
重新执行 rsync 即可继续增量同步。
为什么推荐
-aHAXS?Docker 数据中可能涉及:
- 权限
- UID/GID
- 符号链接
- Hard Link
- ACL
- Extended Attribute
- Sparse 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 ↓ 验证所有业务 ↓ 观察 3~7 天 ↓ 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 根目录生产迁移流程。