Featured image of post Docker 数据目录搬家:磁盘满的根治方案

Docker 数据目录搬家:磁盘满的根治方案

Docker 1.3+ 的 --graph 参数与 daemon.json data-root:把 /var/lib/docker 整体迁移到大容量磁盘,并保证 systemd 正确加载

服务器跑 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 也会大量报错。

常见解法(按推荐度):

  1. 加大 /var 分区(最干净,但需要停机 + 重新分区)
  2. 迁移到 /home 大分区(推荐,本文重点)
  3. 清理无用镜像 / 卷 / 容器(治标不治本)
  4. 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.jsondata-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 被包管理器重写而失效。--graphdocker.service 里,包升级可能被覆盖。

方式 B(旧写法,已不推荐):改 docker.serviceExecStart

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,确认新位置稳定后再清理。

七、要点回顾

  1. 停 docker → mv → 改配置 → daemon-reload → start,五步缺一不可
  2. 优先用 daemon.jsondata-root(2017-05+),--graph 是旧写法
  3. 必须 systemctl daemon-reload,否则 systemd 用缓存的旧 unit 文件
  4. 跨文件系统搬家用 rsync -aHAX,再用 diff -r 校验
  5. 软链是快速止血方案,但不适合监控 / 巡检脚本
  6. K8s 集群里改 data-root 要先 kubectl drain

八、小结

Docker 数据目录搬家是最朴素也最容易踩坑的运维操作——错一步可能就是"daemon 起不来 + 容器全失联"。把"停 → 移 → 配 → reload → 验"这套流程跑成肌肉记忆,比记命令更重要

下一步:理解数据目录结构后,下一层是清理策略——docker system prune 是什么、dangling 镜像和未使用卷如何批量清,以及怎么写一个不丢数据的"安全清理"脚本。/var/lib/docker 的体积管理是一个长期工程。

参考资料

使用 Hugo 构建
主题 StackJimmy 设计