强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 Volumepodman volume create,生命周期独立于容器
Bind Mount-v /host:/container,宿主机目录直接映射
SELinux 标签:Z(私有)/ :z(共享),必须在 SELinux 系统上使用
tmpfs内存文件系统,适合临时数据
存储驱动overlay2(推荐)/ fuse-overlayfs(Rootless 旧内核)
共享存储additionalimagestores 节省磁盘空间
清理podman system prune -a 清理未使用资源

下一步


扩展阅读