服务器跑 Docker 一段时间后,最常见的事故就是 / 分区被 /var/lib/docker 吃满——尤其是 /var 单独分区且容量规划不足的老机器。overlay2 一旦把某个容器层写满,容器起不来、镜像拉不下,连 docker ps 都可能 hang。
这一篇把"如何把 Docker 数据目录整体搬家到大容量磁盘"的完整流程串起来,覆盖 --graph(旧)、data-root(新)、systemd 配置改写、systemd 重新加载四大关键步骤。
阅读对象:在生产环境管理 Docker 主机、被 /var/lib/docker 撑爆过的运维同学
覆盖范围:磁盘诊断 + 备份停机 + 移动 / 软链 / 修改 data-root + systemd reload 验证
一、为什么需要搬家
典型事故现场:
1
2
3
4
5
6
7
8
| $ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 11G 8.4G 2.0G 82% /
/dev/sda5 2.3G 2.3G 0 100% /var
/dev/sda8 82G 204M 78G 1% /home
# overlay 挂载点
overlay 2.3G 2.3G 0 100% /var/lib/docker/overlay2/f18.../merged
|
/var 满了,/var/lib/docker/overlay2/ 的容器可写层跟着满。容器进程写文件会 ENOSPC,dockerd 也会大量报错。
常见解法(按推荐度):
- 加大
/var 分区(最干净,但需要停机 + 重新分区) - 迁移到
/home 大分区(推荐,本文重点) - 清理无用镜像 / 卷 / 容器(治标不治本)
- LVM 动态扩容(要预先用 LVM)
优先选 2:风险可控、几分钟内完成、不需要动分区表。
二、核心机制:Docker 把数据放哪
Docker 把所有持久化数据(images、containers、volumes、networks、build cache、overlay2 层)放在一个根目录,Linux 上默认是 /var/lib/docker。
1
2
3
| $ docker info | grep -i 'root dir\|storage driver'
Docker Root Dir: /var/lib/docker
Storage Driver: overlay2
|
Docker 1.3+(2014-10 GA)开始支持自定义根目录:
- Docker 1.3 ~ 17.05:用
--graph 参数(旧) - Docker 17.05+(2017-05 GA):用
daemon.json 的 data-root 字段(新)
两种方式二选一,不要混用。
三、搬家完整流程
3.1 第一步:停 Docker
必须先停 daemon——/var/lib/docker 在被读写时直接 mv 会损坏文件系统层。
1
2
3
4
5
6
7
8
| # 停服务
systemctl stop docker
# 停 containerd(如果装了新版 Docker)
systemctl stop containerd
# 验证:进程应该没了
ps -ef | grep -E 'dockerd|containerd' | grep -v grep
|
不要用 kill -9:dockerd 收到 SIGKILL 不会做优雅清理,可能留下半完成的 overlay 层。
3.2 第二步:移动目录
1
2
3
4
5
6
7
8
| # 假设 /home 是大容量盘
mv /var/lib/docker /home/docker-data
# 验证大小一致
du -sh /var/lib/docker /home/docker-data
# 验证空间释放
df -h
|
Why 必须 mv 而不是 cp:mv 在同文件系统是 rename(原子操作),跨文件系统 mv 是 copy + delete。跨盘 mv 等价于 cp——这是 Linux 文件系统层面的事,确保 mv 源和目标是同一文件系统。
1
2
3
4
| # 验证是否同文件系统
stat -c '%m' /var /home
# 输出相同数字 = 同文件系统(不需要重命名)
# 输出不同 = 跨文件系统(需要走 rsync 校验)
|
跨文件系统的"安全搬家"姿势:
1
2
3
4
5
| rsync -aHAX /var/lib/docker/ /home/docker-data/
# 验证 md5 一致
diff -r /var/lib/docker /home/docker-data # 不应有输出
# 然后删旧的
rm -rf /var/lib/docker
|
3.3 第三步:告诉 Docker 新位置
方式 A(推荐,新写法):编辑 daemon.json
1
| vim /etc/docker/daemon.json
|
1
2
3
| {
"data-root": "/home/docker-data"
}
|
Why 优先 data-root:JSON 配置可被 dockerd 热加载(SIGHUP 触发),版本升级时不会因为 docker.service 被包管理器重写而失效。--graph 在 docker.service 里,包升级可能被覆盖。
方式 B(旧写法,已不推荐):改 docker.service 的 ExecStart
1
2
3
4
| [Service]
ExecStart=/usr/bin/dockerd \
-H fd:// --containerd=/run/containerd/containerd.sock \
--graph /home/docker-data
|
3.4 第四步:让 systemd 重新加载
这一步 90% 的事故都出在这里。
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 重新加载 systemd 的 unit 文件
systemctl daemon-reload
# 2. 重新 enable(重要!某些版本不 re-enable 会用旧路径)
systemctl disable docker
systemctl enable docker
# 3. 启动
systemctl start docker
# 4. 验证
systemctl status docker
|
常见错误:跳过 daemon-reload。systemd 把 unit 文件缓存在内存里,不 reload 就 start 等于用旧配置。
3.5 第五步:验证
1
2
3
4
5
6
7
8
9
10
11
| # 1. 看根目录
docker info | grep 'Docker Root Dir'
# 期望:Docker Root Dir: /home/docker-data
# 2. 看镜像和容器还在不在
docker images
docker ps -a
# 3. 看磁盘
df -h
# /home 应该有显著使用量
|
完整验证输出:
1
2
3
| $ docker info | grep -E 'Root Dir|Storage Driver'
Docker Root Dir: /home/docker-data
Storage Driver: overlay2
|
四、补充:用软链代替改配置
如果不想动 daemon.json / docker.service,最轻量的方案是软链:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 停 docker
systemctl stop docker
# 移动数据
mv /var/lib/docker /home/docker-data
# 创建软链
ln -s /home/docker-data /var/lib/docker
# 启动
systemctl start docker
# 验证
ls -l /var/lib/docker
# 期望:/var/lib/docker -> /home/docker-data
|
优缺点:
- 不改任何配置,包升级不会破坏
- 适合"快速止血"——几秒钟搞定
- 软链对部分工具不友好(如
realpath、监控脚本可能误判) - 不能跨机器迁移
五、多机同步:批量改所有 Docker 节点
在 K8s 集群或 Docker Swarm 集群里,所有节点的 Docker 数据目录最好一致。用 SSH 批量同步:
1
2
3
4
5
6
7
8
9
| # 假设 master1 已经改好
for NODE in master2 master3 worker1 worker2 worker3; do
echo "=== $NODE ==="
ssh root@$NODE "systemctl stop docker && \
rsync -aHAX --delete root@master1:/home/docker-data/ /home/docker-data/ && \
cp /etc/docker/daemon.json /etc/docker/ && \
systemctl daemon-reload && \
systemctl start docker"
done
|
或者用 scp 单文件同步(更轻量,但只适合 daemon.json 小配置):
1
2
3
4
5
6
| # 同步 daemon.json
for NODE in worker1 worker2; do
echo $NODE
scp /etc/docker/daemon.json $NODE:/etc/docker/
ssh root@$NODE "systemctl daemon-reload && systemctl reload docker && systemctl restart docker"
done
|
生产警示:在 K8s 集群里改 data-root,需要先 drain 节点(kubectl drain <node> --ignore-daemonsets),否则上面跑的 Pod 会被 dockerd 重启打断。
六、回滚
万一新位置起不来:
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 停 docker
systemctl stop docker
# 2. 改回原路径
# 方式 A:改 daemon.json 的 "data-root" 为 /var/lib/docker
# 方式 B:删软链,恢复原目录
rm /var/lib/docker
mv /home/docker-data /var/lib/docker
# 3. 重启
systemctl daemon-reload
systemctl start docker
|
保留原目录一段时间再删——线上至少保留 24-48h,确认新位置稳定后再清理。
七、要点回顾
- 停 docker → mv → 改配置 → daemon-reload → start,五步缺一不可
- 优先用
daemon.json 的 data-root(2017-05+),--graph 是旧写法 - 必须
systemctl daemon-reload,否则 systemd 用缓存的旧 unit 文件 - 跨文件系统搬家用
rsync -aHAX,再用 diff -r 校验 - 软链是快速止血方案,但不适合监控 / 巡检脚本
- K8s 集群里改
data-root 要先 kubectl drain
八、小结
Docker 数据目录搬家是最朴素也最容易踩坑的运维操作——错一步可能就是"daemon 起不来 + 容器全失联"。把"停 → 移 → 配 → reload → 验"这套流程跑成肌肉记忆,比记命令更重要。
下一步:理解数据目录结构后,下一层是清理策略——docker system prune 是什么、dangling 镜像和未使用卷如何批量清,以及怎么写一个不丢数据的"安全清理"脚本。/var/lib/docker 的体积管理是一个长期工程。
参考资料