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

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 网络后端

网络后端版本要求说明
slirp4netnsPodman 所有版本用户空间网络栈,兼容性好,性能一般
pastaPodman 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 常见限制与解决方案

限制汇总表

限制说明解决方案
特权端口不能绑定 < 1024sysctl / 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

下一步


扩展阅读