08 - 存储管理
第 08 章 — 存储管理
8.1 容器存储基础
容器默认使用临时文件系统(Copy-on-Write),容器删除后数据即丢失。数据持久化是生产环境的必备需求。
┌─────────────── 容器文件系统 ───────────────┐
│ │
│ ┌────────────────────────────────────┐ │
│ │ 可写层 (Container Layer) │ │ ← 容器删除后丢失
│ │ ├── /app/data/modified.txt │ │
│ │ └── /tmp/cache.db │ │
│ ├────────────────────────────────────┤ │
│ │ 只读层 (Image Layer) │ │ ← 镜像层,不可变
│ │ ├── /usr/bin/python │ │
│ │ ├── /app/main.py │ │
│ │ └── /etc/app/config.yaml │ │
│ └────────────────────────────────────┘ │
│ │
│ 持久化方式: │
│ ├── Named Volume (podman volume create) │ ← 推荐
│ └── Bind Mount (-v /host:/container) │ ← 灵活
└────────────────────────────────────────────┘
8.2 Named Volume(命名卷)
命名卷由 Podman 管理,存储在用户的数据目录下,生命周期独立于容器。
8.2.1 创建与管理
# 创建命名卷
podman volume create app-data
# 创建带标签的卷
podman volume create --label env=production --label app=myapp pgdata
# 创建带驱动选项的卷
podman volume create --opt type=tmpfs --opt device=tmpfs --opt o=size=100m tmp-vol
# 列出所有卷
podman volume ls
# 查看卷详情
podman volume inspect app-data
# 删除卷
podman volume rm app-data
# 清理未使用的卷
podman volume prune
8.2.2 使用命名卷
# 基本使用
podman run -d --name db \
-v pgdata:/var/lib/postgresql/data:Z \
-e POSTGRES_PASSWORD=secret \
postgres:16-alpine
# 只读挂载
podman run -d --name app \
-v app-config:/app/config:ro,Z \
myapp:latest
# 多个卷
podman run -d --name web \
-v web-content:/usr/share/nginx/html:Z \
-v web-logs:/var/log/nginx:Z \
nginx:1.27-alpine
8.2.3 卷存储位置
# Rootless 卷存储路径
ls ~/.local/share/containers/storage/volumes/
# Root 卷存储路径
ls /var/lib/containers/storage/volumes/
# 查看特定卷的实际路径
podman volume inspect --format '{{.Mountpoint}}' app-data
8.3 Bind Mount(绑定挂载)
Bind Mount 直接将宿主机的目录或文件挂载到容器中。
8.3.1 基本用法
# 挂载目录
podman run -d --name web \
-v /home/user/website:/usr/share/nginx/html:ro,Z \
nginx:1.27-alpine
# 挂载单个文件
podman run -d --name web \
-v /home/user/nginx.conf:/etc/nginx/nginx.conf:ro,Z \
nginx:1.27-alpine
# 使用 --mount 语法(更明确)
podman run -d --name web \
--mount type=bind,source=/home/user/website,target=/usr/share/nginx/html,readonly \
nginx:1.27-alpine
# 绝对路径 vs 相对路径
podman run -v ./data:/data ... # ❌ 不推荐,语义不明确
podman run -v /home/user/data:/data ... # ✅ 推荐,绝对路径
⚠️ 注意
Bind Mount 时宿主机路径必须存在,否则 Podman 会以 root 权限创建该目录(可能引发权限问题)。
8.4 SELinux 标签
在启用了 SELinux 的系统上(RHEL、Fedora、CentOS),挂载卷时必须正确设置 SELinux 标签。
8.4.1 SELinux 标签选项
| 标签 | 含义 | 使用场景 |
|---|---|---|
:Z | 私有标签(relabel) | 容器独占该目录 |
:z | 共享标签 | 多个容器共享该目录 |
| 无标签 | 不做标记 | SELinux 可能阻止访问 |
# ✅ 正确:私有挂载(容器独占)
podman run -d -v /data/app:/app:Z myapp:latest
# ✅ 正确:共享挂载(多容器共享)
podman run -d -v /data/shared:/shared:z app1:latest
podman run -d -v /data/shared:/shared:z app2:latest
# ❌ 错误:缺少标签(SELinux 会阻止)
podman run -d -v /data/app:/app myapp:latest
# 容器内可能无法读写 /app
8.4.2 SELinux 标签的工作原理
# :Z 会重新标记目录的 SELinux 上下文
# 查看标签前
ls -Z /data/app
# unconfined_u:object_r:default_t:s0 /data/app
# 挂载后(:Z 自动 re-label)
podman run -d -v /data/app:/app:Z myapp:latest
# 查看标签后
ls -Z /data/app
# system_u:object_r:container_file_t:s0:c123,c456 /data/app
💡 提示
如果不使用 SELinux(如 Ubuntu 默认不启用),
:Z和:z不会产生影响,但建议始终加上以保持跨平台一致性。
8.5 tmpfs 挂载
# tmpfs 挂载(内存文件系统,适合临时数据)
podman run -d --name app \
--tmpfs /tmp:rw,size=100m,mode=1777 \
myapp:latest
# 使用 --mount 语法
podman run -d --name app \
--mount type=tmpfs,target=/tmp,tmpfs-size=100m \
myapp:latest
# 多个 tmpfs
podman run -d --name app \
--tmpfs /tmp:rw,size=100m \
--tmpfs /run:rw,size=50m \
--tmpfs /var/cache:rw,size=200m \
myapp:latest
8.6 存储驱动
8.6.1 常见存储驱动对比
| 驱动 | 性能 | 稳定性 | Rootless | 推荐场景 |
|---|---|---|---|---|
| overlay2 | ⭐⭐⭐ | ⭐⭐⭐ | Linux 5.11+ | 生产首选 |
| fuse-overlayfs | ⭐⭐ | ⭐⭐⭐ | ✅ | Rootless + 旧内核 |
| vfs | ⭐ | ⭐⭐⭐ | ✅ | 后备方案,无 CoW |
| devmapper | ⭐⭐ | ⭐⭐ | ❌ | 特殊场景 |
| btrfs | ⭐⭐⭐ | ⭐⭐⭐ | ✅ | btrfs 文件系统 |
| zfs | ⭐⭐⭐ | ⭐⭐⭐ | ✅ | zfs 文件系统 |
8.6.2 查看和配置存储驱动
# 查看当前存储驱动
podman info --format '{{.Store.GraphDriverName}}'
# 查看存储详情
podman system info | grep -A10 store
# 配置存储驱动(全局)
sudo tee /etc/containers/storage.conf << 'EOF'
[storage]
driver = "overlay"
[storage.options]
# 额外的镜像存储位置
additionalimagestores = [
"/mnt/shared/images",
]
[storage.options.overlay]
mountopt = "nodev,metacopy=on"
# 限制镜像层大小(可选)
size = ""
EOF
# 配置存储驱动(用户级)
mkdir -p ~/.config/containers
tee ~/.config/containers/storage.conf << 'EOF'
[storage]
driver = "overlay"
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
mountopt = "nodev,metacopy=on"
EOF
8.7 镜像层与 CoW(Copy-on-Write)
┌──────────────────────────────────────────┐
│ OverlayFS 分层存储 │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 容器可写层 (Upper Layer) │ │ ← 容器运行时的修改
│ │ ├── modified_file.txt │ │
│ │ └── new_file.txt │ │
│ ├────────────────────────────────────┤ │
│ │ 镜像层 3 (Layer 3) │ │
│ │ └── /app/config.yaml │ │
│ ├────────────────────────────────────┤ │
│ │ 镜像层 2 (Layer 2) │ │
│ │ └── /app/main.py │ │
│ ├────────────────────────────────────┤ │
│ │ 镜像层 1 (Base Layer) │ │
│ │ ├── /usr/bin/python │ │
│ │ └── /etc/os-release │ │
│ └────────────────────────────────────┘ │
│ │
│ 多个容器共享相同的只读镜像层 │
│ 每个容器只有很小的可写层 │
└──────────────────────────────────────────┘
# 查看镜像分层
podman history nginx:1.27-alpine
# 查看容器存储使用
podman system df -v
8.8 生产场景
场景一:数据库数据持久化
# PostgreSQL 持久化
podman volume create pgdata
podman run -d --name postgres \
-v pgdata:/var/lib/postgresql/data:Z \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=appdb \
-p 5432:5432 \
postgres:16-alpine
# 备份数据
podman exec postgres pg_dump -U postgres appdb > backup.sql
# 使用专用备份卷
podman volume create pg-backup
podman run --rm -v pg-backup:/backup:Z -v pgdata:/data:ro,Z \
busybox tar czf /backup/pg-$(date +%Y%m%d).tar.gz /data
场景二:Web 应用多目录挂载
# 创建多个目录
mkdir -p /opt/webapp/{html,config,logs}
# 只读挂载配置和内容,读写挂载日志
podman run -d --name webapp \
-v /opt/webapp/html:/usr/share/nginx/html:ro,Z \
-v /opt/webapp/config:/etc/nginx/conf.d:ro,Z \
-v /opt/webapp/logs:/var/log/nginx:Z \
-p 80:80 \
nginx:1.27-alpine
场景三:开发环境热重载
# 使用 Bind Mount 实现代码热重载
podman run -d --name dev-app \
-v $(pwd)/src:/app/src:Z \
-v $(pwd)/config:/app/config:Z \
-p 3000:3000 \
node:20-alpine \
npm run dev
# 代码修改会立即反映到容器中
场景四:共享镜像存储(多用户服务器)
# 管理员创建共享镜像存储
sudo mkdir -p /mnt/shared/images
sudo podman --root /mnt/shared/images pull alpine:3.20
sudo podman --root /mnt/shared/images pull nginx:1.27-alpine
# 普通用户配置使用共享存储
cat >> ~/.config/containers/storage.conf << 'EOF'
[storage.options]
additionalimagestores = [
"/mnt/shared/images",
]
EOF
# 用户可以直接使用共享镜像(无需重新拉取)
podman images
8.9 存储空间管理
# 查看系统存储使用情况
podman system df
# 详细信息
podman system df -v
# 清理所有未使用的资源
podman system prune -a
# 仅清理镜像
podman image prune -a
# 仅清理卷
podman volume prune
# 仅清理容器
podman container prune
# 迁移存储目录(数据迁移)
# 1. 停止所有容器
podman stop --all
# 2. 备份
rsync -aP ~/.local/share/containers/storage/ /backup/containers/
# 3. 修改配置
# ~/.config/containers/storage.conf
[storage]
graphRoot = "/mnt/new-storage/containers/storage"
runRoot = "/run/user/1000/containers"
# 4. 重新初始化
podman system reset
8.10 本章小结
| 知识点 | 要点 |
|---|---|
| Named Volume | podman volume create,生命周期独立于容器 |
| Bind Mount | -v /host:/container,宿主机目录直接映射 |
| SELinux 标签 | :Z(私有)/ :z(共享),必须在 SELinux 系统上使用 |
| tmpfs | 内存文件系统,适合临时数据 |
| 存储驱动 | overlay2(推荐)/ fuse-overlayfs(Rootless 旧内核) |
| 共享存储 | additionalimagestores 节省磁盘空间 |
| 清理 | podman system prune -a 清理未使用资源 |
下一步
- 👉 第 09 章:Podman Compose — 多容器编排