Podman 完全指南 / 05 - Rootless 模式
第 05 章 — Rootless 模式
5.1 为什么 Rootless 很重要?
Rootless(无 root 权限)容器是安全容器化的基石。Podman 从设计之初就将 Rootless 作为默认运行模式。
安全对比
Root 模式下的风险:
┌──────────────────────────┐
│ 宿主机 root │ ← dockerd / podman 以 root 运行
│ ┌────────────────────┐ │
│ │ 容器 (逃逸漏洞) │ │ → 攻击者直接获得宿主机 root 权限
│ └────────────────────┘ │
└──────────────────────────┘
Rootless 模式下的防护:
┌──────────────────────────┐
│ 普通用户 uid=1000 │ ← podman 以普通用户运行
│ ┌────────────────────┐ │
│ │ 用户命名空间 │ │
│ │ ┌────────────────┐ │ │
│ │ │ 容器 (逃逸漏洞) │ │ │ → 攻击者只获得 uid=1000
│ │ └────────────────┘ │ │
│ └────────────────────┘ │
└──────────────────────────┘
| 安全维度 | Root 模式 | Rootless 模式 |
|---|---|---|
| 攻击面 | 守护进程以 root 运行 | 每个进程独立,无守护进程 |
| 容器逃逸后果 | 获得宿主机 root | 仅获得普通用户权限 |
| 多用户隔离 | 弱 | 强(用户命名空间隔离) |
| CVE 影响范围 | 大 | 显著缩小 |
| 特权端口 | 直接绑定 | 需额外配置 |
5.2 工作原理
5.2.1 用户命名空间(User Namespace)
Rootless 的核心技术是 用户命名空间映射。容器内的 root (uid=0) 被映射为宿主机的普通用户:
┌─────────────────────────────────────┐
│ 宿主机 (uid=1000) │
│ │
│ /etc/subuid: │
│ alice:100000:65536 │
│ │
│ 映射关系: │
│ 宿主机 uid=1000 ← 容器内 uid=0 │
│ 宿主机 uid=100000 ← 容器内 uid=1 │
│ 宿主机 uid=100001 ← 容器内 uid=2 │
│ ... │
│ 宿主机 uid=165535 ← 容器内 uid=65535│
│ │
│ ┌───────────────────────────────┐ │
│ │ 用户命名空间 │ │
│ │ 容器视角: │ │
│ │ uid=0 (root) │ │
│ │ uid=1000 (app user) │ │
│ │ uid=65534 (nobody) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
5.2.2 查看 UID 映射
# 查看当前用户的 UID 映射
podman unshare cat /proc/self/uid_map
# 0 1000 1
# 1 100000 65535
# 查看 GID 映射
podman unshare cat /proc/self/gid_map
# 0 1000 1
# 1 100000 65535
# 进入用户命名空间的 Shell
podman unshare bash
# 在命名空间内查看身份
id
# uid=0(root) gid=0(root)
# 注意:这是命名空间内的虚拟 root,宿主机上仍是 uid=1000
# 退出
exit
5.2.3 配置文件
Rootless 的 UID/GID 映射由以下文件控制:
# /etc/subuid — 用户级子 UID 范围
# 格式: 用户名:起始UID:数量
alice:100000:65536
bob:200000:65536
# /etc/subgid — 用户级子 GID 范围
# 格式: 用户名:起始GID:数量
alice:100000:65536
bob:200000:65536
# 查看当前用户的映射
grep $(whoami) /etc/subuid
grep $(whoami) /etc/subgid
# 添加映射(需要 root)
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)
# 或使用 useradd 命令
sudo useradd --system --shell /usr/sbin/nologin containeruser
sudo usermod --add-subuids 200000-265535 --add-subgids 200000-265535 containeruser
5.3 存储驱动配置
Rootless 模式有专门的存储配置需求。
5.3.1 OverlayFS vs FUSE-OverlayFS
| 存储驱动 | Root 模式 | Rootless 模式 | 性能 |
|---|---|---|---|
| overlay2 | ✅ 原生支持 | 需 Linux 5.11+ | ⭐⭐⭐ |
| fuse-overlayfs | 可用 | ✅ 推荐(内核 < 5.11) | ⭐⭐ |
| vfs | 可用 | ✅ 后备方案 | ⭐ |
# 检查内核版本
uname -r
# Linux 5.11+:原生 OverlayFS 支持(无需 fuse-overlayfs)
# Linux < 5.11:需要 fuse-overlayfs
5.3.2 存储配置文件
编辑 ~/.config/containers/storage.conf:
# ~/.config/containers/storage.conf
[storage]
# 存储目录(Rootless 默认为 ~/.local/share/containers/storage)
# home = "/home/alice/.local/share/containers/storage"
driver = "overlay"
[storage.options]
# 额外存储路径(可选)
# additionalimagestores = [
# "/mnt/shared/images",
# ]
[storage.options.overlay]
# Linux 5.11+ 无需指定 mount_program
# Linux < 5.11 需要:
mount_program = "/usr/bin/fuse-overlayfs"
mountopt = "nodev,metacopy=on"
5.3.3 共享镜像存储
多用户共享同一台服务器时,可以配置共享镜像存储以节省磁盘空间:
# 以 root 创建共享存储目录
sudo mkdir -p /var/lib/shared-containers/storage
sudo chmod 777 /var/lib/shared-containers/storage
# 拉取共享镜像(以 root)
sudo podman --root /var/lib/shared-containers/storage pull alpine:3.20
# 用户级配置添加只读共享存储
cat >> ~/.config/containers/storage.conf << 'EOF'
[storage.options]
additionalimagestores = [
"/var/lib/shared-containers/storage",
]
EOF
# 验证:用户可以直接使用共享镜像
podman images
# 应该可以看到 alpine:3.20(来自共享存储)
5.4 网络配置
5.4.1 Rootless 网络后端
| 网络后端 | 版本要求 | 说明 |
|---|---|---|
| slirp4netns | Podman 所有版本 | 用户空间网络栈,兼容性好,性能一般 |
| pasta | Podman 4.3+ | 新一代网络栈,性能接近 root 模式 |
| lxc-user-nic | 特殊配置 | LXC 网络,较少使用 |
# 查看当前网络后端
podman info | grep networkBackend
# 切换到 pasta(推荐,需 Podman 4.3+)
# 编辑 ~/.config/containers/containers.conf
[containers]
netns = "pasta"
# 或在运行时指定
podman run --network pasta alpine ping -c1 8.8.8.8
5.4.2 端口映射限制
# Rootless 默认不能绑定 < 1024 的端口
podman run -p 80:80 nginx # ❌ 失败
# 解决方案一:调整 sysctl(推荐)
sudo sysctl net.ipv4.ip_unprivileged_port_start=80
# 永久生效
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/99-podman.conf
# 解决方案二:setcap
sudo setcap cap_net_bind_service=ep /usr/bin/podman
# 解决方案三:使用端口转发
# iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
# 解决方案四:使用反向代理(最推荐的生产方案)
podman run -d -p 8080:80 nginx # 绑定非特权端口
# 然后配置 nginx/haproxy/caddy 从 80 转发到 8080
5.5 cgroup 管理
5.5.1 cgroup v2(推荐)
# 检查 cgroup 版本
stat -fc %T /sys/fs/cgroup
# cgroup2fs — 表示 cgroup v2
# cgroup v2 下 Rootless 资源管理
podman run --rm --memory 512m --cpus 1.5 alpine cat /sys/fs/cgroup/memory.max
# 配置 Rootless 用户的 cgroup delegation
# 需要 systemd 245+
sudo systemctl enable --now user@$(id -u).service
# 验证 delegation
systemctl --user status
5.5.2 用户级 systemd slice 配置
# 为容器用户创建专属 systemd slice
mkdir -p ~/.config/systemd/user/
cat > ~/.config/systemd/user/container-limits.conf << 'EOF'
[Slice]
# 限制该用户所有容器的总内存
MemoryMax=4G
# 限制 CPU
CPUQuota=200%
EOF
# 重新加载
systemctl --user daemon-reload
5.6 常见限制与解决方案
限制汇总表
| 限制 | 说明 | 解决方案 |
|---|---|---|
| 特权端口 | 不能绑定 < 1024 | sysctl / setcap / 反向代理 |
| 设备访问 | 不能访问宿主机设备 | 需要 root 或适当权限 |
| 内核模块 | 不能加载内核模块 | 需要 root |
| ping | 普通用户不能 ping | 设置 sysctl 或文件权限 |
| /etc/resolv.conf | 不可修改 | 使用 --dns 参数 |
| 网络性能 | slirp4netns 有开销 | 使用 pasta 替代 |
| NFS 挂载 | 不支持 NFS overlay | 使用 bind mount |
| AppArmor | 不支持 | 使用 SELinux(Podman 支持) |
解决 ping 权限
# 方法一:sysctl
sudo sysctl net.ipv4.ping_group_range="0 1000"
# 永久生效
echo "net.ipv4.ping_group_range=0 1000" | sudo tee /etc/sysctl.d/99-podman-ping.conf
sudo sysctl --system
# 方法二:文件权限
sudo setcap cap_net_raw+ep /usr/bin/ping
5.7 最佳实践
✅ Do’s
# 1. 始终使用 Rootless(除非有明确理由不用)
podman run ...
# 2. 使用 crun 而非 runc(内存更少,速度更快)
# ~/.config/containers/containers.conf
[engine]
runtime = "crun"
# 3. 配置足够的 subuid/subgid 范围
# /etc/subuid
alice:100000:65536 # 65536 个 UID 通常足够
# 4. 使用 pasta 替代 slirp4netns(Podman 4.3+)
# 5. 确保 cgroup v2 已启用
# 6. 使用 --userns=keep-id 让容器内 UID 与宿主机一致
podman run -v ~/data:/data:Z --userns=keep-id myapp
❌ Don’ts
# 1. 避免使用 --privileged(等同于放弃 Rootless 的安全优势)
podman run --privileged ... # ❌ 仅在调试时使用
# 2. 避免挂载宿主机敏感目录
podman run -v /etc:/host-etc ... # ❌ 危险
# 3. 不要在容器内使用 host 网络
podman run --network host ... # ❌ 失去网络隔离
# 4. 避免在生产环境使用 latest 标签
podman run nginx:latest # ❌ 使用明确版本
5.8 本章小结
| 知识点 | 要点 |
|---|---|
| 核心机制 | 用户命名空间映射(uid_map/gid_map) |
| 配置文件 | /etc/subuid + /etc/subgid |
| 存储驱动 | Linux 5.11+ 用原生 overlay,旧版用 fuse-overlayfs |
| 网络后端 | 推荐 pasta(Podman 4.3+) |
| 端口限制 | sysctl 或 setcap 绑定特权端口 |
| 最佳实践 | crun 运行时、keep-id 挂载、避免 privileged |
下一步
- 👉 第 06 章:Systemd 集成 — 让容器像原生服务一样运行