数据得活得比容器长
一句话总结
容器的可写层会随容器一起消失。需要长期保存的数据必须放在容器外部,使用卷或绑定挂载。
为什么需要它
重新部署容器后,所有上传文件都消失了——这样的故障报告至今仍很常见。原因不是缺陷,而是设计如此。可写层依附于容器对象,删除容器时,该层也会一并消失。
与此同时还有性能问题。修改下层中的文件时,必须把整个文件复制到上层(copy-up),对数据库文件这类大文件非常不利。因此,大规模写入必须移到卷中已成为一条常用准则。
工作原理
有三种方式,特性各不相同。
| 项目 | 绑定挂载 | 命名卷 | tmpfs |
|---|---|---|---|
| 位置 | 直接指定主机路径 | 由运行时管理 | 内存 |
| 持久性 | 永久保留在主机 | 永久保留在卷中 | 结束时消失 |
| 可移植性 | 低(依赖路径) | 高 | 高 |
| 备份 | 自行管理 | 专用命令 | 不可备份 |
| 主要用途 | 配置文件、开发中的源代码 | 数据库数据、上传文件 | 临时文件、秘密 |
语法也有两种:简短形式 -v source:target:opts,以及显式形式 --mount type=...,source=...,target=...,readonly。后者会显示选项名称,因此在代码审查中不易产生误解。
只读挂载比想象中更重要。以 :ro 挂载配置文件后,无论是误操作还是入侵,容器都无法修改配置。应用程序尝试写入时会收到 Read-only file system 错误,而该错误正是“这个容器正在写入不该写的位置”的信号。
实际现场中的情况
备份模式已形成一种固定用法:启动一个临时容器,同时挂载数据卷和备份目录,并在其中创建归档。
docker run --rm -v dk-data:/source:ro -v /root/backup:/backup alpine:3.20 tar czf /backup/dk-data.tgz -C /source .
关键是通过 -C 进入源目录后,使用相对路径归档。这样还原时路径才不会错位。
在 rootless 环境中,经常会遇到所有权问题。这是因为主机与容器的 UID 映射不同。此时不应通过 chmod 777 开放文件,而应理解并正确匹配映射关系。放开权限不仅会对该容器开放,也会对同一主机上的其他进程开放。
如何选择卷与 bind mount
两者名称相似,性质却不同。
| 命名卷 | bind mount | |
|---|---|---|
| 管理者 | Docker | 人员 |
| 位置 | /var/lib/docker/volumes/… |
主机上的任意路径 |
| 备份 | 用 docker run --rm -v vol:/d … 导出 |
直接使用主机文件 |
| 权限 | 按镜像中的 UID 初始化 | 完全沿用主机权限 |
| 开发便利性 | 看不到源代码修改 | 立即反映 |
权限这一行是实践中的陷阱。bind mount 会原样继承主机文件的所有者和权限模式。因此,如果容器以 nonroot(UID 65532)运行,而主机文件是 root:root 0644,就会无法写入。相反,命名卷首次创建时会复制容器内目标路径的权限进行初始化,通常可以直接使用。
只在第一次复制
命名卷的初始化只会在卷为空时执行一次。
빈 볼륨을 /app/config 에 붙이면
→ 이미지의 /app/config 내용이 볼륨으로 복사된다
이미 내용이 있는 볼륨을 붙이면
→ 이미지의 내용은 가려진다. 복사하지 않는다
因此,即使重新构建镜像并修改默认配置文件,使用旧卷的容器仍会看到旧文件。 这是“镜像明明修好了,却没有生效”的常见原因。最好不要把配置放在卷中,而应通过 ConfigMap 或环境变量注入。
在 Kubernetes 中的对应关系
| Docker | Kubernetes | 使用场景 |
|---|---|---|
| 命名卷 | PVC | 数据库数据 |
| bind mount | hostPath | 访问节点文件(应尽量避免) |
| tmpfs | emptyDir: {medium: Memory} |
秘密、临时内容 |
| — | emptyDir: {} |
容器间共享(生命周期与 Pod 相同) |
emptyDir 会在Pod 被删除时消失,节点重启时也会消失。但在 Pod 重启(只有容器退出)的情况下仍会保留,因此测试时很容易产生误判。
权限问题可通过 fsGroup 解决。
spec:
securityContext:
fsGroup: 2000 # 볼륨의 그룹 소유자를 2000 으로 바꿔 준다
runAsUser: 1000
runAsNonRoot: true
fsGroup 会递归更改卷内所有文件的所有权,因此,文件数量非常多时,Pod 启动会变慢。 此时可使用 fsGroupChangePolicy: OnRootMismatch,只检查根目录。
下一项实验要做什么
创建并使用一个卷,确认删除容器后数据是否仍然存在;亲自触发只读挂载的错误消息;然后完整进行一次备份与还原。