Podman 常用运维指令手册

运维指令手册 49.6 KB 原文

Podman 常用运维指令手册

适用版本:Podman 4.4+ / 5.x(稳定线)/ 6.x(当前) 宿主系统:RHEL 8/9/10、Fedora、Ubuntu 22.04+、Debian 12+、openEuler、macOS/Windows(Podman Machine) 约定:# root 或 sudo,$ 普通用户,<xxx> 为占位符。 差异标注:正文用 > **版本差异** 标注跨版本不一致之处,无标注表示 5.x/6.x 通用。 更新时间:2026-11(版本事实核对至 2026-11-05)


目录

  1. 定位与架构
  2. 版本与关键变更
  3. 安装部署
  4. 配置体系
  5. rootless 深入
  6. 镜像管理
  7. 容器运维
  8. Pod 与 kube play
  9. Quadlet(systemd 原生集成)
  10. systemd 集成与定时任务
  11. 卷与存储
  12. 网络
  13. Compose
  14. 自动更新与镜像签名
  15. Podman Machine
  16. 与 Docker 的差异与迁移
  17. 日志与监控
  18. 备份与迁移
  19. 卸载与清理
  20. 故障速查
  21. 附录

1. 定位与架构

1.1 无 daemon 意味着什么

Docker 是 C/S 架构:CLI → dockerd(root 常驻)→ containerd → runc。 Podman 是 fork-exec 架构:CLI 自己完成全部操作,通过 conmon 守护每个容器。

podman run
   │
   ├─ 解析镜像(containers/image)
   ├─ 解包层(containers/storage)
   ├─ 建 netns(netavark)
   ├─ fork conmon ──┬─ fork runc → 业务进程
   │                └─ 负责日志转发、退出状态回收
   └─ CLI 退出(容器继续跑)
维度 影响
没有常驻 daemon 无单点故障;systemctl restart 不存在;不能"重启 podman 服务"
每个容器一个 conmon 容器数量多时进程数多(ps 会看到大量 conmon)
无 API 默认监听 远程操作需 podman system service 显式开 socket
权限模型跟随调用者 普通用户跑就是 rootless,root 跑就是 rootful

1.2 与 Docker 的架构对照

能力 Docker Podman
运行时守护 dockerd 常驻 无(conmon 随容器)
容器父进程 containerd-shim conmon
镜像构建 BuildKit(内置) Buildah(库形式调用)
镜像搬运 内置 Skopeo(库形式调用)
网络 libnetwork + docker-proxy Netavark + aardvark-dns + pasta
rootless 需额外配置,功能受限 原生支持
k8s 集成 需 cri-dockerd 原生 kube play / Quadlet .kube
API REST 常驻 REST 按需(system service)

1.3 什么时候选 Podman

适合:单机多租户、需要普通用户跑容器、RHEL 系(系统自带)、边缘/嵌入式、想用 systemd 原生管理容器、k8s YAML 直接落地到单机。

不适合:需要 Docker Swarm、依赖 Docker 专有 API 的工具链、需要 docker build 之外的特殊 BuildKit 特性(如 --mount=type=secret 需 buildah 版本配合)、团队工具链强绑 docker.sock。


2. 版本与关键变更

2.1 版本事实(2026-11)

项目 状态
稳定线 Podman 5.8.x(5.8.9 / 2026-10-12)
新版 Podman 6.0 已发布
底层配套 Buildah 1.43、containers/storage 1.62、containers/image 5.39、Netavark、Pasta
网络后端 5.x 默认 netavark;4.0 以前是 CNI
数据库 5.8 起自动把 BoltDB 迁移到 SQLite;6.0 移除 BoltDB

2.2 各版本关键变化

版本 变化 运维影响
4.4 podman generate systemd 标记弃用 新部署改用 Quadlet
4.9 podman farm 多机构建 跨架构构建更简单
5.0 全新 podman machine;Netavark 默认;pasta 默认 无 daemon 的 Mac/Win 体验改善
5.2 --device 与 --privileged 同时生效(不再忽略) 特权容器行为变化
5.4 podman update 支持健康检查;--hosts-file 可不重建容器改配置
5.8 自动迁移 SQLite;podman update --ulimit;exec --no-session 升级后首次启动会迁移数据库
6.0 移除 BoltDB;网络彻底转 Netavark/Pasta/nftables;Quadlet REST API 升级前必须确认 nftables 与内核版本

版本差异:Podman 6.0 的迁移是不可逆的(BoltDB → SQLite)。升级前必须备份 ~/.local/share/containers/storage 与 /var/lib/containers/storage。

2.3 版本识别

podman version
# Client:       Podman Engine
# Version:      5.8.9
# API Version:  5.8.9
# Go Version:   go1.24.x
# Built:        Thu Oct 12 ...
# OS/Arch:      linux/amd64

podman info
podman info --format '{{.Version.Version}} {{.Host.CgroupVersion}} {{.Host.NetworkBackend}} {{.Host.Security.Rootless}}'
podman info --format '{{json .Host}}' | jq

# 配套工具版本
buildah --version
skopeo --version
runc --version
crun --version
pasta --version
netavark --version

3. 安装部署

3.1 RHEL / CentOS Stream / Rocky / Alma / openEuler

# 系统自带,直接装
dnf install -y podman

# 按需补充
dnf install -y podman-docker      # 提供 docker 命令兼容包装
dnf install -y podman-compose     # 或用 docker-compose(见 13 章)
dnf install -y buildah skopeo     # 构建与镜像搬运
dnf install -y netavark aardvark-dns pasta   # 网络三件套(9+ 默认已装)
dnf install -y podman-tui         # 文本界面(可选)
dnf install -y udica              # SELinux 策略生成

podman version

3.2 Ubuntu / Debian

# 官方源(版本较旧,但稳定)
apt update && apt install -y podman

# 需要新版本(5.x/6.x):openSUSE Kubic 源
. /etc/os-release
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" \
  > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
curl -fsSL "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key" \
  | gpg --dearmor -o /etc/apt/trusted.gpg.d/kubic.gpg
apt update && apt install -y podman

# rootless 依赖(Ubuntu 需手动装)
apt install -y uidmap slirp4netns fuse-overlayfs passt

坑点:Ubuntu 上 rootless Podman 需要 uidmap(提供 newuidmap/newgidmap)。缺了会报 cannot clone: Operation not permitted 或 newuidmap: not found。

3.3 Fedora(版本最新)

dnf install -y podman podman-docker buildah skopeo
# Fedora 通常最快拿到最新 Podman

3.4 macOS / Windows

# 方式一:Podman Desktop(图形化,推荐新手)
# https://podman-desktop.io

# 方式二:命令行
brew install podman            # macOS
winget install RedHat.Podman   # Windows

# 初始化虚拟机(首次必须)
podman machine init
podman machine start
podman machine list
podman machine ssh              # 进 VM
podman run --rm hello-world

3.5 安装后验证清单

podman info                                   # 无报错即基本可用
podman run --rm hello-world
podman run --rm alpine id                     # rootless 下 uid=0 但映射到宿主普通 UID

# rootless 关键项
podman unshare cat /proc/self/uid_map          # 应有 0 1000 1 / 1 100000 65536
loginctl show-user $USER | grep Linger         # 需 Linger=yes
sysctl net.ipv4.ip_unprivileged_port_start     # 需 ≤ 80 才能绑 80 端口

# 网络
podman network ls
podman network inspect podman

# 存储
podman system df
podman info --format '{{.Store.GraphRoot}}'
# 让 docker 命令走 podman(podman-docker 提供)
dnf install -y podman-docker
docker version        # 会提示 "Emulate Docker CLI using podman"
systemctl enable --now podman.socket     # 让 docker API 客户端可用
curl --unix-socket /run/podman/podman.sock http://d/v1.40/libpod/version

4. 配置体系

4.1 四类配置文件

文件 作用 系统级 用户级
containers.conf 引擎行为(运行时、cgroup、日志、网络) /etc/containers/containers.conf ~/.config/containers/containers.conf
registries.conf 镜像仓库、短名解析、镜像加速 /etc/containers/registries.conf ~/.config/containers/registries.conf
storage.conf 存储驱动、graphroot、额外镜像存储 /etc/containers/storage.conf ~/.config/containers/storage.conf
policy.json 镜像签名验证策略 /etc/containers/policy.json ~/.config/containers/policy.json

优先级:命令行参数 > 用户级 > 系统级 > 内置默认。配置文件本身不合并,用户级只要存在就整体覆盖系统级(同类配置项)。

# 查看最终生效配置(重要,排查配置不生效必用)
podman info
podman info --format '{{json .Host}}' | jq
podman system check

4.2 containers.conf 常用段

# /etc/containers/containers.conf
[containers]
# 默认环境变量
env = ["TZ=Asia/Shanghai", "LANG=zh_CN.UTF-8"]
# 默认 ulimit
default_ulimits = ["nofile=65536:65536"]
# 默认日志驱动
log_driver = "journald"
# 默认 DNS
dns_servers = ["10.0.0.2"]
# 默认停止超时
stop_timeout = 30
# 禁止在容器内看到宿主 /dev
devices = []
# 默认 seccomp
seccomp_profile = "/usr/share/containers/seccomp.json"
# 默认 capabilities
default_capabilities = [
  "CHOWN","DAC_OVERRIDE","FOWNER","FSETID","KILL","NET_BIND_SERVICE",
  "SETFCAP","SETGID","SETPCAP","SETUID","SYS_CHROOT"
]

[engine]
# 运行时(crun 更快、内存占用低;runc 兼容性最好)
runtime = "crun"
# 镜像并行拉取
image_parallel_copies = 0
# 退出时清理
# 事件日志后端
events_logger = "journald"
# 临时目录
tmp_dir = "/var/tmp"

[network]
# 网络后端:netavark(默认)/ cni
network_backend = "netavark"
# 默认网络配置
default_network = "podman"
# 默认子网(避免与内网冲突)
default_subnet = "10.88.0.0/16"
# 查看默认配置(带注释的完整版)
cat /usr/share/containers/containers.conf
podman --help | head -40

4.3 registries.conf(镜像加速与短名解析)

# /etc/containers/registries.conf
unqualified-search-registries = ["docker.io", "harbor.internal"]

# 镜像加速
[[registry]]
prefix = "docker.io"
location = "docker.m.daocloud.io"
insecure = false

# 私有仓库(HTTP)
[[registry]]
prefix = "harbor.internal"
location = "harbor.internal:80"
insecure = true

# 镜像别名(把公网镜像指向内部镜像)
[[registry]]
prefix = "docker.io/library/nginx"
location = "harbor.internal/mirror/nginx"
# 也可以用分片目录(更干净)
mkdir -p /etc/containers/registries.conf.d
cat >/etc/containers/registries.conf.d/00-mirror.conf <<'EOF'
[[registry]]
prefix = "docker.io"
location = "docker.m.daocloud.io"
EOF

# 验证短名解析
podman search nginx
podman pull nginx              # 会按 unqualified-search-registries 顺序找
podman pull docker.io/library/nginx:1.25    # 显式全名,最稳

坑点:短名(nginx)需要交互式选择仓库,脚本里会失败。生产脚本一律写全名 docker.io/library/nginx:1.25。

4.4 storage.conf(多存储与驱动)

# /etc/containers/storage.conf
[storage]
driver = "overlay"
# rootless 在内核不支持 overlay 时用 fuse-overlayfs
# driver = "fuse-overlayfs"
graphroot = "/var/lib/containers/storage"
runroot = "/run/containers/storage"

[storage.options.overlay]
# 单容器可写层上限(需 xfs pquota)
# size = "20G"
mount_program = "/usr/bin/fuse-overlayfs"    # 仅 fuse-overlayfs 模式
mountopt = "nodev,metacopy=on"
podman info --format '{{.Store.GraphDriverName}} {{.Store.GraphRoot}}'
podman system df

4.5 配置生效

# 大多数配置改完直接生效(无 daemon 可重启)
podman info | grep -A3 network

# 个别需要重载:storage.conf 改了 driver 需迁移
podman system migrate
podman system reset           # ★ 清空所有镜像容器(危险)

5. rootless 深入

5.1 原理

rootless Podman 用 user namespace 把容器内的 root 映射到宿主的一个非特权 UID 区间:

容器内 uid 0     → 宿主 uid 1000(你的账号)
容器内 uid 1..65536 → 宿主 uid 100000..165535(subuid 区间)

所以容器内的 root 在宿主上只是普通用户 —— 即使逃逸也拿不到真正的 root。

# 查看映射
podman unshare cat /proc/self/uid_map
#          0       1000          1
#          1     100000      65535

podman unshare cat /proc/self/gid_map

5.2 前置条件逐项检查

# 1) subuid/subgid
grep "^$USER" /etc/subuid /etc/subgid
# 无输出则添加:
usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
# 或直接编辑 /etc/subuid、/etc/subgid 加一行:user:100000:65536

# 2) newuidmap/newgidmap(uidmap 包)
which newuidmap newgidmap
# 必须带 suid 位
ls -l /usr/bin/newuidmap

# 3) 用户命名空间上限
sysctl user.max_user_namespaces
# Ubuntu 还需
cat /proc/sys/kernel/unprivileged_userns_clone   # 需 1

# 4) 低端口绑定
sysctl net.ipv4.ip_unprivileged_port_start
# 需要绑 80/443 时设 80
echo 'net.ipv4.ip_unprivileged_port_start=80' >/etc/sysctl.d/99-podman.conf
sysctl --system

# 5) linger(退出登录后容器继续跑)
loginctl enable-linger $USER
loginctl show-user $USER | grep Linger

# 6) cgroup v2 委派(systemd 用户会话要能管 cgroup)
stat -fc %T /sys/fs/cgroup/      # 需 cgroup2fs
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers
# 应有 cpu memory pids 等

5.3 cgroup v2 委派问题(rootless 最常见的坑)

现象:rootless 容器里 --memory 不生效、podman stats 无数据、systemd 报 Failed to create cgroup。

# 检查当前会话的委派
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.subtree_control
# 空或只有 pids → 委派不全

# 解决:让 systemd 用户会话委派 cgroup
# Debian/Ubuntu 需在 /etc/systemd/system/user@.service.d/delegate.conf
mkdir -p /etc/systemd/system/user@.service.d
cat >/etc/systemd/system/user@.service.d/delegate.conf <<'EOF'
[Service]
Delegate=yes
EOF
systemctl daemon-reload
# 需要重新登录(或重启 user@<uid>.service)才生效

# 验证
systemctl restart user@$(id -u).service
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers

5.4 rootless 的存储限制

限制 说明 绕过
只能用 overlay(内核 ≥ 5.11)或 fuse-overlayfs 老内核不支持非特权 overlay 装 fuse-overlayfs
默认不能读写宿主 root 的文件 uid 映射决定 podman unshare 下操作
--privileged 能力受限 仍是用户态 需要真特权就用 rootful
不能绑 < 1024 端口(默认) 内核限制 ip_unprivileged_port_start
网络不能用某些高级特性 pasta/netavark 用户态 用 rootful 或 --network host
# 在映射后的 namespace 里改文件属主(rootless 操作卷数据的正确姿势)
podman unshare chown -R 1000:1000 /data/app
podman unshare ls -ln /data/app
podman unshare rm -rf /data/app/cache

5.5 rootless vs rootful 的选择

# 同一台机器可以共存,存储完全隔离
podman info --format '{{.Store.GraphRoot}}'
# rootful: /var/lib/containers/storage
# rootless: ~/.local/share/containers/storage
场景 选择
开发、CI、多租户 rootless
需要绑 80/443 且不想调内核参数 rootful 或 rootless + sysctl
需要 --privileged、真实设备、NFS 挂载 rootful
系统服务(开机自启、与系统生命周期一致) rootful + Quadlet
个人服务(用户级自启) rootless + --user + linger

注意:rootless 与 rootful 的镜像是两套,podman images 结果不同。切换身份排查时先确认 podman info --format '{{.Store.GraphRoot}}'。


6. 镜像管理

6.1 基础命令

podman images
podman images -a
podman images --filter dangling=true
podman images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.Created}}'

podman pull docker.io/library/nginx:1.25
podman pull --arch arm64 docker.io/library/nginx:1.25
podman pull --os linux --arch amd64 --variant v8 nginx
podman pull --tls-verify=false harbor.internal:5000/app:1.0
podman pull --authfile ~/.config/containers/auth.json harbor.internal/app:1.0

podman search nginx
podman search --limit 10 --filter is-official=true nginx

podman inspect nginx:1.25
podman inspect --format '{{.Config.Env}}' nginx:1.25
podman history nginx:1.25
podman tag nginx:1.25 harbor.internal/web/nginx:1.25.3
podman push harbor.internal/web/nginx:1.25.3
podman push --all-tags harbor.internal/web/nginx
podman rmi nginx:1.25
podman image prune -a

6.2 登录与凭据

podman login harbor.internal -u admin --password-stdin < ~/.pass
podman login --tls-verify=false 10.0.0.50:5000
podman logout harbor.internal

# 凭据文件(与 Docker 格式兼容,可复用)
cat ~/.config/containers/auth.json
cat ${XDG_RUNTIME_DIR}/containers/auth.json

# 多用户共享凭据(rootful 全局)
cat /run/containers/0/auth.json

6.3 导入导出

# OCI 归档(推荐,保留元数据)
podman save --format oci-archive -o nginx-oci.tar nginx:1.25
podman load -i nginx-oci.tar

# docker-archive(与 docker load 互通)
podman save --format docker-archive -o nginx-docker.tar nginx:1.25
docker load -i nginx-docker.tar

# 目录形式(可查看、增量)
podman save --format oci-dir -o ./nginx-oci nginx:1.25

# 容器文件系统快照
podman export -o rootfs.tar <container>
podman import rootfs.tar myapp:snapshot

# 压缩
podman save nginx:1.25 | gzip > nginx.tar.gz

6.4 skopeo(不落地搬运镜像)

# 镜像复制(跨仓库/跨机器,无需本地存储)
skopeo copy docker://docker.io/library/nginx:1.25 \
  docker://harbor.internal/mirror/nginx:1.25

# 检查远端镜像(不拉取)
skopeo inspect docker://docker.io/library/nginx:1.25
skopeo inspect --raw docker://docker.io/library/nginx:1.25 | jq

# 删除远端镜像
skopeo delete docker://harbor.internal/mirror/nginx:1.25

# 同步整个仓库(备份/迁移神器)
skopeo sync --src docker --dest dir docker.io/library/nginx /backup/images
skopeo sync --src dir --dest docker /backup/images harbor.internal/mirror
skopeo sync --src yaml --dest docker sync.yaml harbor.internal/

# 批量复制所有 tag
skopeo list-tags docker://docker.io/library/nginx | jq -r '.Tags[]' | \
  while read t; do skopeo copy docker://nginx:$t docker://harbor.internal/mirror/nginx:$t; done

6.5 buildah 构建

# Dockerfile 构建(podman build 内部就是 buildah)
podman build -t myapp:1.0.0 .
podman build -f docker/Dockerfile.prod -t myapp:1.0.0 .
podman build --build-arg VERSION=1.0.0 .
podman build --layers -t myapp:1.0.0 .              # 启用层缓存
podman build --no-cache -t myapp:1.0.0 .
podman build --platform linux/amd64,linux/arm64 -t myapp:1.0.0 --manifest multi .

# buildah 脚本式构建(无需 Dockerfile)
buildah from scratch
buildah add <ctr> ./app /app
buildah config --entrypoint '["/app"]' <ctr>
buildah commit <ctr> myapp:1.0.0

# 多架构 manifest
podman manifest create myapp:multi
podman build --platform linux/amd64 -t myapp:amd64 .
podman manifest add myapp:multi containers-storage:localhost/myapp:amd64
podman manifest push --all myapp:multi docker://harbor.internal/web/myapp:1.0.0

# 检查
podman manifest inspect myapp:multi

版本差异:Podman 5.x 的 podman build 需要 buildah 后端(多数发行版已内置);--layers 在 rootless + fuse-overlayfs 下缓存效果较差,建议用 overlay。

6.6 镜像清理

podman system df
podman image prune          # 悬空
podman image prune -a       # 无引用
podman image prune -a --filter until=720h
podman system prune         # 容器+镜像+网络+构建缓存
podman system prune -a --volumes    # ★ 含卷,危险

7. 容器运维

7.1 run 参数(与 docker 的差异点标注)

podman run -d \
  --name web \
  --restart unless-stopped \
  -p 8080:80 \
  -e TZ=Asia/Shanghai \
  -v /data/web:/usr/share/nginx/html:ro \
  --network appnet \
  --cpus 2 -m 2g \
  --health-cmd "curl -fs http://127.0.0.1/healthz || exit 1" \
  --health-interval 30s --health-retries 3 \
  --label env=prod \
  docker.io/library/nginx:1.25

Podman 特有/行为不同的参数

参数 差异
--userns=keep-id 把当前用户 UID 映射进容器同 UID(rootless 读写宿主文件必备)
--userns=host 不做用户命名空间隔离(接近 rootful 行为)
--userns=auto 自动分配 UID 区间,支持 size=
--group-add keep-groups 保留宿主附加组(访问 NFS/共享目录必需)
--security-opt label=disable 关闭 SELinux 标签(临时排障)
--security-opt label=type:container_t 指定 SELinux 类型
--sdnotify=conmon 让 systemd 感知容器就绪
--restart 支持 no/on-failure/always/unless-stopped,但底层是 podman-restart.service
--http-proxy 默认把宿主代理环境变量注入容器(Docker 不注入)
--passwd 自动生成 /etc/passwd(解决 uid 无用户名问题)
--replace 同名容器存在时直接替换(比 rm && run 方便)
# rootless 读写宿主目录的三种正确姿势
podman run --userns=keep-id -v /home/user/data:/data alpine touch /data/x
podman run -v /home/user/data:/data:Z alpine touch /data/x   # SELinux 场景
podman unshare chown -R 1000:1000 /home/user/data && podman run -v /home/user/data:/data:Z alpine touch /data/x

# 访问宿主附加组才能读的目录(如 NFS)
podman run --group-add keep-groups -v /mnt/nfs:/data alpine ls /data

7.2 生命周期

podman ps
podman ps -a
podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}\t{{.Image}}'
podman ps --filter status=exited
podman ps --filter label=env=prod
podman ps -s                             # 含可写层大小

podman start web
podman stop web
podman stop -t 30 web
podman restart web
podman kill -s SIGHUP web
podman pause web && podman unpause web
podman rm web
podman rm -f web
podman rm -v web                         # 连匿名卷
podman rename web web-old

# 热更新(5.4+)
podman update --memory 4g --cpus 4 web
podman update --env TZ=UTC --env APP_ENV=prod web
podman update --unsetenv DEBUG web
podman update --ulimit nofile=4096:8192 web     # 5.8+
podman update --restart=no web
podman update --health-cmd "..." web

7.3 执行与调试

podman exec -it web sh
podman exec -u root web id
podman exec -w /app web ls
podman exec -e LANG=C web locale
podman exec --no-session -it web sh        # 5.8+,短命令更快

podman attach web
podman top web
podman diff web
podman stats
podman stats --no-stream
podman healthcheck run web
podman inspect web --format '{{json .State}}' | jq

# 生成 k8s 风格 yaml(迁移/审计)
podman generate kube web > web.yaml        # 旧写法(已弃用)
podman kube generate web > web.yaml        # 6.0 推荐

7.4 退出码与 OOM

podman inspect web --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
journalctl --user -u container-web.service -n 50    # Quadlet 起的容器
dmesg -T | grep -i 'killed process'
码 含义
0 正常
1 应用错误
125 podman 自身错误
126/127 命令不可执行/不存在
137 SIGKILL / OOM
143 SIGTERM

7.5 提交与快照

podman commit -m "hotfix" -a "ops" web myapp:hotfix
podman export web | gzip > web-$(date +%F).tar.gz

8. Pod 与 kube play

8.1 Pod 概念

Podman 的 pod 与 k8s 的 pod 语义一致:共享网络命名空间(同一 IP、同一端口空间)、可共享 IPC/UTS、可共享存储卷。实现上用一个 infra 容器(pause)持有 namespace。

版本差异:Podman 5.x 起 infra 容器使用最小根文件系统里内置的 catatonit,不再依赖 pause 镜像(老版本会看到 k8s.gcr.io/pause 镜像)。

# 创建 pod(发布端口在 pod 级别)
podman pod create --name webpod -p 8080:80 -p 9090:9090
podman pod ls
podman pod inspect webpod
podman pod ps --format 'table {{.Name}}\t{{.Status}}\t{{.NumContainers}}\t{{.InfraID}}'

# 往 pod 里加容器(不要写 -p,端口在 pod 上)
podman run -d --pod webpod --name web docker.io/library/nginx:1.25
podman run -d --pod webpod --name sidecar docker.io/library/busybox:1.36 sleep 3600

# 生命周期
podman pod stop webpod
podman pod start webpod
podman pod restart webpod
podman pod pause webpod
podman pod rm -f webpod
podman pod prune

# 资源与统计
podman pod stats
podman pod top webpod

8.2 Pod 内共享卷

podman volume create shared
podman pod create --name apppod -p 8080:80 -v shared:/shared
podman run -d --pod apppod --name writer -v shared:/data alpine sh -c 'while true; do date >> /data/t.log; sleep 5; done'
podman run -d --pod apppod --name reader -v shared:/data:ro alpine tail -f /data/t.log

8.3 kube play(单机跑 k8s YAML)

# web.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels: { app: web }
spec:
  containers:
    - name: nginx
      image: docker.io/library/nginx:1.25
      ports:
        - containerPort: 80
          hostPort: 8080
      env:
        - name: TZ
          value: Asia/Shanghai
      volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
  volumes:
    - name: html
      hostPath: { path: /data/web, type: Directory }
podman kube play web.yaml
podman kube play --down web.yaml
podman kube play --replace web.yaml
podman kube play --network appnet web.yaml
podman kube play --publish 8080:80 web.yaml

# 支持的资源:Pod、Deployment、DaemonSet、Job、CronJob、ConfigMap、Secret、PersistentVolumeClaim
# 不支持的:Service(部分)、Ingress、CRD、RBAC

# 从容器反向生成(迁移到 k8s 用这个)
podman kube generate web > web.yaml
podman kube generate --type deployment web > web-deploy.yaml

实践建议:kube play 适合开发/边缘/单机交付。它的 Service 支持有限,网络语义与 k8s 不完全一致,不要用它模拟生产 k8s。


9. Quadlet(systemd 原生集成)

9.1 为什么用 Quadlet

方式 状态 问题
podman run 手工 — 重启丢失,无自启
--restart 可用 依赖 podman-restart.service,顺序不可控
podman generate systemd 弃用(4.4+) 生成静态 unit,改配置要重新生成
Quadlet 官方推荐(5.x+) 声明式、由 systemd 生成器解析、与 systemd 深度集成

9.2 文件位置与命名

类型 后缀 系统级 用户级
容器 .container /etc/containers/systemd/ ~/.config/containers/systemd/
卷 .volume 同上 同上
网络 .network 同上 同上
Pod .pod 同上 同上
Kube .kube 同上 同上
镜像构建 .build 同上 同上

文件名即服务名的前缀:web.container → web.service。

9.3 .container 完整语法

# /etc/containers/systemd/web.container
[Unit]
Description=Web frontend
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target
# 依赖其他 Quadlet 单元
Requires=db.service
After=db.service

[Container]
# 镜像(支持变量)
Image=docker.io/library/nginx:1.25
ContainerName=web
# 端口(写法同 -p)
PublishPort=8080:80
PublishPort=127.0.0.1:9090:9090
# 卷(支持具名卷 xxx.volume 与宿主路径)
Volume=web-html.volume:/usr/share/nginx/html:ro,Z
Volume=/data/logs:/var/log/nginx:Z
# 环境变量
Environment=TZ=Asia/Shanghai
Environment=APP_ENV=prod
EnvironmentFile=/etc/web.env
# 网络
Network=appnet.network
NetworkAlias=frontend
# 资源限制
PodmanArgs=--cpus=2 --memory=2g
# 健康检查
HealthCmd=curl -fs http://127.0.0.1/healthz || exit 1
HealthInterval=30s
HealthTimeout=3s
HealthRetries=3
HealthStartPeriod=20s
HealthOnFailure=restart
# 自动更新
AutoUpdate=registry
# 启动顺序依赖挂载
RequiresMountsFor=/data/logs
# SELinux
SecurityLabelType=container_t
# 只读根 + 临时目录
ReadOnly=true
Tmpfs=/tmp:size=64m
# 用户
User=1000:1000
# 日志
LogDriver=journald
# 拉取策略:always / missing / never / newer
Pull=newer

[Service]
Restart=always
RestartSec=5
TimeoutStartSec=300
TimeoutStopSec=60
# 优雅停止
KillSignal=SIGTERM

[Install]
WantedBy=multi-user.target

常用键对照表

Quadlet 键 等价 podman 参数
Image 镜像位置参数
ContainerName --name
PublishPort -p
Volume -v
Environment -e
EnvironmentFile --env-file
Network --network
Pod --pod
User / Group --user
ReadOnly --read-only
Tmpfs --tmpfs
PodmanArgs 任意其他参数(逃生口)
AutoUpdate --label io.containers.autoupdate=registry
HealthCmd 等 --health-*
Pull --pull

9.4 .volume / .network / .pod

# /etc/containers/systemd/web-html.volume
[Volume]
VolumeName=web-html
Driver=local
# 绑定到宿主目录
# Driver=image 不需要
Options=type=none,device=/data/html,o=bind
# /etc/containers/systemd/appnet.network
[Network]
NetworkName=appnet
Driver=bridge
Subnet=10.89.0.0/24
Gateway=10.89.0.1
# 内部网络(不出网)
Internal=true
# 端口转发
PublishPort=8080:80
# /etc/containers/systemd/webpod.pod
[Pod]
PodName=webpod
PublishPort=8080:80
Network=appnet.network
# 容器加入 pod
[Container]
Image=docker.io/library/nginx:1.25
Pod=webpod.pod

9.5 .kube(直接跑 k8s YAML)

# /etc/containers/systemd/app.kube
[Unit]
Description=App from k8s yaml

[Kube]
Yaml=/etc/containers/kubernetes/app.yaml
# 覆盖 YAML 中的镜像
# Set WorkingDir=/opt
# PublishPort=8080:80

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

9.6 部署与验证流程

# 1) 放文件
cp web.container /etc/containers/systemd/

# 2) 重载生成器(关键:不 reload 就没有 service)
systemctl daemon-reload

# 3) 查看生成的单元
systemctl cat web.service

# 4) 启动
systemctl start web.service
systemctl status web.service

# 5) 日志
journalctl -u web.service -f

# 6) 语法检查(不实际启动)
/usr/libexec/podman/quadlet -dryrun -f /etc/containers/systemd/web.container
# 或
/usr/libexec/podman/quadlet -dryrun -v

# 7) 调试:看生成器输出
/usr/libexec/podman/quadlet -version
journalctl -u web.service --no-pager | head -30

# 8) 变更后
systemctl daemon-reload && systemctl restart web.service

9.7 用户级 Quadlet(rootless 自启)

mkdir -p ~/.config/containers/systemd
cp web.container ~/.config/containers/systemd/

systemctl --user daemon-reload
systemctl --user start web.service
systemctl --user status web.service
journalctl --user -u web.service -f

# 必须开 linger,否则退出登录容器就停
loginctl enable-linger $USER
loginctl show-user $USER | grep Linger

# 开机自启(user 级默认跟随 linger)
systemctl --user is-enabled web.service

9.8 Quadlet 常见错误

报错 原因 处理
Failed to start web.service: Unit web.service not found 没 daemon-reload 或后缀/路径错 确认在正确目录,daemon-reload
no such image Pull=never 且本地无镜像 先 podman pull 或改 Pull=missing
Error: creating container storage: ... exists 同名容器已存在(非 Quadlet 创建) podman rm -f <name>
启动后立刻退出 容器内进程非前台/秒退 journalctl 看日志
权限拒绝(SELinux) 卷缺 :Z Volume=/data:/data:Z
Unit is masked 被 mask systemctl unmask web.service
改了 .container 不生效 未 reload systemctl daemon-reload && restart

10. systemd 集成与定时任务

10.1 generate systemd(仅维护旧部署时使用)

# 已弃用,但仍可用
podman generate systemd --new --name web --files --restart-policy=always
# 输出 container-web.service 到当前目录
cp container-web.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now container-web

# --new:每次启动重建容器(配置跟随 systemd,推荐)
# 不加 --new:复用已有容器(容器被删就起不来)

新部署请直接用 Quadlet。generate systemd 在 Podman 6.0 中仍在,但已无新特性支持。

10.2 podman-restart.service

# 让 --restart=always 的容器在开机后自动起来
systemctl enable --now podman-restart.service
systemctl status podman-restart

# 用户级
systemctl --user enable --now podman-restart.service

# 注意:它只拉起标记了 restart policy 的容器,且不保证顺序

10.3 定时任务

# 方式 A:systemd timer(推荐)
cat >/etc/systemd/system/backup-container.service <<'EOF'
[Unit]
Description=Backup container data
[Service]
Type=oneshot
ExecStart=/usr/bin/podman run --rm -v app-data:/data:ro -v /backup:/backup:Z \
  docker.io/library/alpine tar czf /backup/app-$(date +%%F).tar.gz -C /data .
EOF

cat >/etc/systemd/system/backup-container.timer <<'EOF'
[Unit]
Description=Daily container backup
[Timer]
Persistent=true
RandomizedDelaySec=600
[Install]
WantedBy=timers.target
EOF

systemctl daemon-reload
systemctl enable --now backup-container.timer
systemctl list-timers --all

# 方式 B:cron(rootless 需注意 PATH 与 XDG_RUNTIME_DIR)
crontab -e
# 0 2 * * * XDG_RUNTIME_DIR=/run/user/$(id -u) /usr/bin/podman run --rm alpine echo hi

坑点:cron 里跑 rootless podman 必须显式设 XDG_RUNTIME_DIR=/run/user/<uid>,否则报 cannot access storage。systemd timer 无此问题。


11. 卷与存储

11.1 卷操作

podman volume create app-data
podman volume create --opt type=none --opt device=/data/app --opt o=bind app-bind
podman volume create --opt type=nfs --opt o=addr=10.0.0.40,rw,nfsvers=4.1 \
  --opt device=:/export/data nfs-data

podman volume ls
podman volume ls --filter dangling=true
podman volume inspect app-data
podman volume rm app-data
podman volume prune
podman volume export app-data -o app-data.tar     # 直接导出
podman volume import app-data app-data.tar

# 物理路径
podman volume inspect app-data -f '{{.Mountpoint}}'

11.2 存储驱动

podman info --format '{{.Store.GraphDriverName}}'
# overlay(rootful 默认,rootless 内核 ≥5.11 也可)
# fuse-overlayfs(rootless 老内核回退)
# vfs(慢,兼容性最好)
# btrfs / zfs

# rootless 强制用 fuse-overlayfs
# ~/.config/containers/storage.conf
# [storage]
# driver = "fuse-overlayfs"
# [storage.options]
# mount_program = "/usr/bin/fuse-overlayfs"

11.3 空间治理

podman system df
podman system df -v
du -sh $(podman info --format '{{.Store.GraphRoot}}')
du -sh $(podman info --format '{{.Store.GraphRoot}}')/overlay

# 容器可写层
podman ps -s --format 'table {{.Names}}\t{{.Size}}'

# 日志位置(journald 走 journal;k8s-file 走文件)
podman inspect web -f '{{.HostConfig.LogConfig.Type}}'
ls -l $(podman info --format '{{.Store.RunRoot}}')/containers/

11.4 权限与 SELinux

# rootless 下容器写宿主目录
podman unshare chown -R 1000:1000 /data/app
podman run --userns=keep-id -v /data/app:/app:Z ...

# 检查标签
ls -Z /data/app
# 正确:system_u:object_r:container_file_t:s0

# 永久策略
semanage fcontext -a -t container_file_t "/data/app(/.*)?"
restorecon -Rv /data/app

# 自动生成策略(推荐,比手工精确)
dnf install -y udica
podman inspect web > web.json
udica -j web.json web_container
semodule -i web_container.pp

12. 网络

12.1 网络后端演进

版本 默认后端 组件
≤ 4.0 CNI cni-plugins + dnsname
4.1 ~ 5.x netavark netavark + aardvark-dns
6.0 netavark + pasta 弱化 slirp4netns 与 iptables,走 nftables
podman info --format '{{.Host.NetworkBackend}}'
# netavark
rpm -q netavark aardvark-dns

12.2 网络模式

podman run --network podman ...              # 默认 bridge(名为 podman)
podman run --network host ...                # 共享宿主网络(rootless 也可,见下)
podman run --network none ...                # 无网络
podman run --network container:web ...       # 共享另一容器
podman run --network appnet ...              # 自定义网络
podman run --network pasta ...               # 显式用 pasta(5.x+)
podman run --network slirp4netns:port_handler=slirp4netns ...  # 老方案
podman run --network private ...             # 仅 lo + 端口转发

rootless 的 host 网络:Podman 支持 rootless --network host(共享宿主 netns,无需额外权限)。这是与 Docker rootless 的重要差异——Docker rootless 的 host 模式受限。

12.3 自定义网络

podman network create appnet
podman network create --subnet 10.89.5.0/24 --gateway 10.89.5.1 appnet
podman network create --internal backend        # 不出网
podman network create --driver macvlan \
  --opt parent=eth0 --subnet 192.168.1.0/24 --gateway 192.168.1.1 mvneta

podman network ls
podman network inspect appnet
podman network connect appnet web
podman network disconnect appnet web
podman network rm appnet
podman network prune

# 容器内 DNS(aardvark-dns 提供,支持容器名解析)
podman run -d --name db --network appnet docker.io/library/mysql:8.4
podman run -d --name app --network appnet docker.io/library/alpine sleep 3600
podman exec app ping -c1 db
podman exec app cat /etc/resolv.conf

12.4 端口发布与 rootless

podman run -d -p 8080:80 nginx
podman run -d -p 127.0.0.1:8080:80 nginx
podman port web
podman inspect web -f '{{json .NetworkSettings.Ports}}'

# rootless 绑 80 端口
sysctl net.ipv4.ip_unprivileged_port_start=80
# 或 rootless 用 pasta 的 --map-guest-addr 等高级特性

12.5 网络排障

podman network inspect appnet | jq '.[0].subnets'
podman exec web ip addr
podman exec web ip route
podman exec web cat /etc/resolv.conf
podman exec web ping -c1 10.89.0.1
hostname -I

# 进容器 netns(rootful)
nsenter -t $(podman inspect -f '{{.State.Pid}}' web) -n ss -lntp

# rootless 下看 netns
podman unshare --rootless-netns ip addr
podman unshare --rootless-netns ss -lntp

# aardvark-dns 状态
podman info --format '{{.Host.NetworkBackendInfo}}'
journalctl --user -u aardvark-dns -n 50 2>/dev/null
ps -ef | grep aardvark
现象 原因 处理
容器间不能按名字解析 不在同一自定义网络 --network 同一网络
rootless 端口映射不通 pasta/slirp4netns 未装 apt install passt 或 slirp4netns
容器无外网 宿主转发关闭 net.ipv4.ip_forward=1
创建网络报 subnet already in use 网段冲突 --subnet 指定其他段
pasta 报 socket 错误 版本不匹配 升级 passt 或退回 slirp4netns
大量 conmon 进程 容器多,正常 无需处理,或改用 Quadlet 减少探测容器

13. Compose

13.1 三种 provider

provider 说明 适合
podman compose(内置调用外部) 5.x 起为包装器,自动找 docker-compose 或 podman-compose 通用
podman-compose(Python 独立项目) 独立实现,功能略少于 docker compose 无 docker-compose 时
docker-compose(v2 二进制) 通过 DOCKER_HOST 指向 podman socket,兼容性最好 复杂 compose 文件
# 检查实际调用了谁
podman compose version

# 安装 provider
dnf install -y podman-compose
# 或
pip install podman-compose

# 用 docker compose v2 作为后端(兼容性最佳)
dnf install -y docker-compose-plugin     # 只拿二进制,不装 docker
systemctl --user enable --now podman.socket
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
docker compose up -d

13.2 常用命令

podman compose up -d
podman compose up -d --build
podman compose down
podman compose down -v
podman compose ps
podman compose logs -f api
podman compose exec api sh
podman compose build
podman compose pull
podman compose config
podman compose restart api
podman compose run --rm migrate

13.3 兼容性注意事项

项 说明
deploy.resources.limits podman-compose 部分版本忽略;用 mem_limit/cpus 兼容写法
depends_on.condition podman-compose 支持不完整,用 --no-deps 或启动脚本兜底
container_name 支持
profiles 5.x 的 podman-compose 支持
secrets 支持,落到 podman secret
网络命名 项目名会被加前缀,跨项目访问需显式 name:
build.args 支持,但 --build-arg 传递需注意
# 兼容性更好的写法
services:
  api:
    image: harbor.internal/web/api:1.0.0
    mem_limit: 2g
    cpus: 2.0
    restart: unless-stopped

13.4 开机自启(Compose 项目)

# 生成 systemd 单元(一次性,之后由 systemd 管理)
cat >/etc/systemd/system/webstack.service <<'EOF'
[Unit]
Description=WebStack (podman compose)
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/webstack
Environment=XDG_RUNTIME_DIR=/run/user/0
ExecStart=/usr/bin/podman compose up -d
ExecStop=/usr/bin/podman compose down
TimeoutStartSec=600

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload && systemctl enable --now webstack

更推荐把每个服务转成 Quadlet,逐个管理,依赖和重启策略更清晰。


14. 自动更新与镜像签名

14.1 auto-update

# 1) 容器带更新标记(三种取值)
podman run -d --name web \
  --label io.containers.autoupdate=registry \
  docker.io/library/nginx:1.25
# registry:按 registry 中的 tag 更新
# local   :按本地镜像更新(配合 build 流程)

# 2) 手动检查
podman auto-update --dry-run
podman auto-update

# 3) 定时执行
systemctl enable --now podman-auto-update.timer
systemctl list-timers podman-auto-update.timer
journalctl -u podman-auto-update -n 50

# 4) Quadlet 中开启
# AutoUpdate=registry

# 5) 回滚(auto-update 会保留旧镜像)
podman auto-update --rollback
# 查看上次更新时间与状态
podman inspect web -f '{{.ImageName}} {{index .Labels "io.containers.autoupdate"}}'
podman images --filter label=io.containers.autoupdate=registry

注意:auto-update 只对运行中且带 label 的容器生效;更新是「建新容器 + 删旧容器」,所以容器内状态会丢失。有状态服务(数据库)不要开。

14.2 镜像签名

# 生成密钥
skopeo generate-sigstore-key --output-prefix mykey
# 产出 mykey.private / mykey.pub

# 签名(推送到仓库前后都可)
podman image sign --sign-by mykey@example.com docker://harbor.internal/web/app:1.0.0

# 验证策略 /etc/containers/policy.json
cat >/etc/containers/policy.json <<'EOF'
{
  "default": [{ "type": "reject" }],
  "transports": {
    "docker": {
      "harbor.internal": [
        {
          "type": "signedBy",
          "keyType": "GPGKeys",
          "keyPath": "/etc/pki/containers/mykey.pub"
        }
      ]
    },
    "containers-storage": [{ "type": "insecureAcceptAnything" }]
  }
}
EOF

# 之后拉未签名镜像会失败(这是期望行为)
podman pull harbor.internal/web/app:1.0.0

14.3 漏洞扫描

# 用 trivy 扫镜像
podman run --rm -v /var/run/podman/podman.sock:/var/run/docker.sock \
  aquasec/trivy image harbor.internal/web/app:1.0.0

# 或导出为 oci 归档再扫
podman save --format oci-archive -o app.tar app:1.0.0
podman run --rm -v $PWD:/scan aquasec/trivy image --input /scan/app.tar

15. Podman Machine

15.1 用途

macOS / Windows 上 Podman 需要一台 Linux 虚拟机(内核依赖 namespace/cgroup)。podman machine 管理这台 VM。

# 初始化
podman machine init
podman machine init --cpus 4 --memory 8192 --disk-size 100 vm2
podman machine init --rootful                    # 默认 rootful 模式
podman machine init --image-path ./fedora-coreos.qcow2   # 离线

# 生命周期
podman machine list
podman machine start
podman machine start vm2
podman machine stop
podman machine ssh
podman machine inspect
podman machine rm -f

# 升级 VM 内的 OS(6.0+)
podman machine os update

# 重置(5.0+)
podman machine reset

15.2 在 VM 内操作

podman machine ssh
# 进入后就是普通 Linux
sudo dnf install -y nginx

15.3 与 k8s 集成

# 起一个单节点 k8s(需要 kind/minikube 或 k3s)
podman machine ssh
# 在 VM 内执行 k3s 安装脚本

15.4 常见问题

现象 原因 处理
machine start 卡住 虚拟化未开 / 端口冲突 检查 BIOS 虚拟化、podman machine rm 重建
镜像拉取慢 VM 内 DNS/代理 podman machine ssh 后配 registries.conf
磁盘满 VM 默认 100G 且不自动回收 podman system prune -a,或重建更大的 VM
文件共享 macOS 需显式挂载 --volume 在 podman machine init 时配
Error: vm already exists 名字冲突 podman machine list,改名或删除

16. 与 Docker 的差异与迁移

16.1 命令对照(完整)

操作 Docker Podman
运行 docker run podman run
构建 docker build podman build
拉取/推送 docker pull/push podman pull/push
列表 docker ps/images podman ps/images
日志 docker logs podman logs
执行 docker exec podman exec
检查 docker inspect podman inspect
统计 docker stats podman stats
事件 docker events podman events
系统信息 docker info podman info
清理 docker system prune podman system prune
保存/加载 docker save/load podman save/load
导出/导入 docker export/import podman export/import
登录 docker login podman login
网络列表 docker network ls podman network ls
卷列表 docker volume ls podman volume ls
编排 docker compose podman compose
集群 docker swarm 无(用 k8s / kube play)
生成 k8s 无 podman kube generate
跑 k8s yaml 无 podman kube play
服务化 --restart Quadlet
远程 API 常驻 2375/2376 podman system service

16.2 行为差异(会踩坑的)

差异点 Docker Podman
短名解析 默认 docker.io 按 unqualified-search-registries,可能交互提示
代理环境变量 不自动注入 自动注入宿主代理(可用 --http-proxy=false 关)
--restart 实现 daemon 内建 依赖 podman-restart.service
容器内 /etc/hosts 固定格式 可用 --hosts-file 自定义(5.4+)
--privileged + --device 都生效 5.2 起都生效(此前忽略 --device)
默认网络名 bridge podman
默认 DNS 内嵌 127.0.0.11 aardvark-dns
存储路径 /var/lib/docker /var/lib/containers/storage(rootful)/ ~/.local/share/containers/storage(rootless)
镜像存储格式 graphdriver / containerd store containers/storage
容器父进程 containerd-shim conmon
无 daemon 有 无(podman system service 按需起)

16.3 从 Docker 迁移

# 步骤 1:镜像迁移(最稳的是走 registry)
# 在 Docker 机器上
docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' \
  | xargs -I{} docker tag {} harbor.internal/mirror/{} 2>/dev/null
docker push ...   # 或用 skopeo 直接搬

# 步骤 2:Podman 机器上直接拉
podman pull harbor.internal/mirror/nginx:1.25

# 步骤 3:compose 文件复用(多数场景可直接用)
podman compose -f docker-compose.yml up -d

# 步骤 4:校验
podman ps
podman compose ps
curl localhost:8080

# 步骤 5:服务化(把 compose 转 Quadlet,逐个迁移)
# 无 registry 时的离线迁移:导出 oci 归档
docker save nginx:1.25 -o nginx.tar
podman load -i nginx.tar

16.4 迁移检查清单

检查项 说明
短名镜像 全部改全名,否则脚本可能卡在交互选择
docker.sock 依赖 挂 socket 的工具(watchtower、portainer agent)在 Podman 上要换方案
--restart 语义 需额外 enable podman-restart.service 或改 Quadlet
端口 < 1024 rootless 需调 ip_unprivileged_port_start
卷权限 rootless 下用 --userns=keep-id 或 podman unshare chown
SELinux 加 :Z 标签
网络名 默认网络从 bridge 变 podman,compose 里若硬编码需改
健康检查 --health-* 支持完整,但 HEALTHCHECK 镜像指令在部分旧版本不生效
BuildKit 特性 --mount=type=secret/cache 需 buildah 1.4x+,且语法略有差异
API 客户端 用 podman system service 暴露 socket,路径不同

17. 日志与监控

17.1 日志驱动

驱动 说明 podman logs
journald(默认 rootful) 进 systemd journal 支持
k8s-file(默认 rootless) 写 JSON 文件到 runroot 支持
passthrough 直接透传到 stdout 不支持
none 不记录 不支持
podman run --log-driver journald --name web nginx:1.25
podman logs -f web
podman logs --since 10m web
podman logs --tail 100 web

# journald 下按容器查
journalctl CONTAINER_NAME=web --since today
journalctl -u container-web.service -f          # Quadlet

# k8s-file 的日志路径
podman inspect web -f '{{.HostConfig.LogConfig.Path}}'

17.2 事件

podman events
podman events --filter container=web
podman events --filter event=die
podman events --format json
podman events --since 1h

# 事件写入 journald
journalctl -t podman --since today

17.3 指标

# Podman 无内建 Prometheus exporter,两种做法:
# 1) 用 podman-exporter(社区)
podman run -d --name podman-exporter \
  --privileged -p 9882:9882 \
  -v /run/podman/podman.sock:/run/podman/podman.sock:ro \
  quay.io/navidys/prometheus-podman-exporter
curl -s localhost:9882/metrics | head

# 2) cAdvisor(rootful 场景)
podman run -d --name cadvisor --privileged \
  -p 8081:8080 \
  -v /:/rootfs:ro -v /var/run:/var/run:ro -v /sys:/sys:ro \
  -v /var/lib/containers/storage:/var/lib/containers/storage:ro \
  gcr.io/cadvisor/cadvisor:v0.49.1

17.4 巡检脚本

#!/usr/bin/env bash
set -uo pipefail
echo "===== $(date '+%F %T') podman check ====="
podman info >/dev/null || echo "!! podman 异常"
echo "--- 存储"; podman system df
echo "--- 非运行容器"; podman ps -a --filter status=exited --format '{{.Names}}\t{{.Status}}'
echo "--- unhealthy"; podman ps --filter health=unhealthy --format '{{.Names}}'
echo "--- 卷"; podman volume ls --filter dangling=true
echo "--- 网络"; podman network ls
echo "--- 磁盘"; du -sh "$(podman info --format '{{.Store.GraphRoot}}')"
echo "--- conmon 进程数"; pgrep -c conmon || true

18. 备份与迁移

18.1 备份清单

对象 位置 方式
镜像 storage podman save / registry
卷 ~/.local/share/containers/storage/volumes podman volume export 或 tar
Quadlet 单元 /etc/containers/systemd/、~/.config/containers/systemd/ Git
配置 /etc/containers/、~/.config/containers/ Git
签名策略 policy.json Git
pod 定义 podman kube generate 产出 Git
# 卷批量导出
for v in $(podman volume ls -q); do
  podman volume export "$v" -o "/backup/volumes/$v.tar"
done

# 镜像批量导出
podman images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' \
  | xargs -r podman save -m -o /backup/images.tar

# pod 定义
podman kube generate webpod > /backup/webpod.yaml

18.2 迁移到新机器

# 旧机
podman images --format '{{.Repository}}:{{.Tag}}' > /tmp/images.txt
podman save -m -i /tmp/images.txt -o /backup/images.tar
for v in $(podman volume ls -q); do podman volume export "$v" -o "/backup/$v.tar"; done
tar czf /backup/quadlet.tgz /etc/containers/systemd ~/.config/containers/systemd 2>/dev/null

# 新机
podman load -i /backup/images.tar
podman volume create app-data && podman volume import app-data /backup/app-data.tar
tar xzf /backup/quadlet.tgz -C /
systemctl daemon-reload && systemctl start web.service

18.3 版本升级

# 升级前:备份 storage 与配置
tar czf /backup/podman-storage-$(date +%F).tar.gz \
  $(podman info --format '{{.Store.GraphRoot}}')
cp -a /etc/containers /backup/containers-etc

# RHEL
dnf update -y podman buildah skopeo

# 5.8 → 6.0 特别注意
# - BoltDB 自动迁移 SQLite(首次运行触发)
# - 若迁移失败:podman system migrate --migrate-db
podman system migrate --migrate-db

# 验证
podman info
podman ps -a
podman images
podman run --rm hello-world

19. 卸载与清理

19.1 标准卸载

# 1) 停容器与 pod
podman ps -aq | xargs -r podman stop
podman pod ps -q | xargs -r podman pod rm -f
podman ps -aq | xargs -r podman rm -f

# 2) 停 systemd 相关(Quadlet 生成的服务)
systemctl --user stop 'container-*' 2>/dev/null
systemctl stop 'container-*' 2>/dev/null
systemctl --user disable --now podman.socket podman-restart.service 2>/dev/null
systemctl --user disable --now podman-auto-update.timer 2>/dev/null

# 3) 清数据
podman system prune -a --volumes -f
rm -rf /var/lib/containers
rm -rf ~/.local/share/containers
rm -rf ${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/containers

# 4) 卸载包
dnf remove -y podman podman-docker podman-compose buildah skopeo \
  netavark aardvark-dns containers-common

# Ubuntu
# apt purge -y podman podman-compose buildah skopeo

# 5) 清配置
rm -rf /etc/containers
rm -rf ~/.config/containers
rm -rf ~/.docker 2>/dev/null          # 如果共用过凭据
rm -f /etc/sysctl.d/99-podman.conf
rm -f /etc/modules-load.d/podman.conf

# 6) 清 Quadlet 单元
rm -rf /etc/containers/systemd
rm -rf ~/.config/containers/systemd
systemctl daemon-reload
systemctl --user daemon-reload

# 7) 清网络残留
ip link show | grep -E 'podman|veth'
for i in $(ip -o link show | awk -F': ' '/podman|veth/{print $2}'); do ip link delete "$i"; done

# 8) 验证
which podman || echo "已卸载"
systemctl list-unit-files | grep -i podman
ls /etc/containers 2>/dev/null

19.2 只清数据(重置)

podman system reset          # 删所有镜像/容器/卷/网络/存储
podman system reset -f       # 不确认
# 注意:会删整个 storage 目录,不可恢复

19.3 卸载后残留检查

检查 命令
进程 ps -ef | grep -E 'conmon|podman'
单元 systemctl list-unit-files | grep -i podman
目录 ls /var/lib/containers ~/.local/share/containers
网桥 ip link | grep -E 'podman|veth'
配置 ls /etc/containers
socket ls /run/user/$(id -u)/podman
subuid 残留 grep $USER /etc/subuid /etc/subgid(可保留,无害)

20. 故障速查

20.1 rootless 相关(最高频)

现象 排查 处理
cannot clone: Operation not permitted sysctl kernel.unprivileged_userns_clone 置 1;装 uidmap
newuidmap: not found which newuidmap apt install uidmap / dnf install shadow-utils
cannot set up namespace using "/usr/bin/newuidmap" /etc/subuid 无记录 usermod --add-subuids ...
Error: creating container storage: ... permission denied subuid 区间被占用/冲突 检查 /etc/subuid 是否重复
--memory 不生效 cgroup v2 未委派 Delegate=yes(见 5.3)
podman stats 无数据 cgroup 未委派 同上
退出登录容器就停 未开 linger loginctl enable-linger $USER
绑 80 端口失败 低端口限制 ip_unprivileged_port_start=80
容器内 uid 无用户名 无 /etc/passwd 条目 加 --passwd

20.2 存储

现象 排查 处理
no space left on device podman system df podman system prune -a
overlay: invalid argument(rootless) 内核 < 5.11 改 fuse-overlayfs
layer not known storage 损坏 podman system reset(先备份)
卷权限拒绝 podman unshare ls -ln podman unshare chown
database ... locked 并发操作 等待或 podman system migrate
5.8 升级后启动慢 BoltDB → SQLite 迁移 等首次迁移完成;失败用 --migrate-db

20.3 网络

现象 排查 处理
端口映射不通 podman port、ss -lntp 检查 pasta/slirp4netns 是否安装
容器无 DNS cat /etc/resolv.conf 检查 aardvark-dns 进程
容器间不能按名访问 podman network inspect 同一网络
netavark: ... subnet 网段冲突 --subnet
rootless host 网络异常 podman unshare --rootless-netns ip addr 换 pasta/slirp4netns 实现
pasta 报错 pasta --version 升级 passt 或退回 slirp4netns

20.4 Quadlet 与 systemd

现象 排查 处理
Unit not found ls /etc/containers/systemd 路径/后缀/daemon-reload
服务起但容器退出 journalctl -u x -n 50 前台进程问题
conmon 持续重启 podman logs 应用崩溃
用户级不生效 systemctl --user status XDG_RUNTIME_DIR、linger
改了配置不生效 systemctl cat x.service daemon-reload + restart
Dryrun 报语法错 /usr/libexec/podman/quadlet -dryrun -f x 按提示改键名

20.5 通用

podman info                       # 第一步永远是它
podman system check               # 存储一致性检查
podman --log-level=debug run ...  # 详细日志
podman events --since 10m
journalctl --user -n 100
journalctl -t podman -n 100

21. 附录

21.1 关键路径

项 rootful rootless
存储 /var/lib/containers/storage ~/.local/share/containers/storage
运行态 /run/containers/storage /run/user/<uid>/containers
系统配置 /etc/containers/ ~/.config/containers/
Quadlet(系统) /etc/containers/systemd/ ~/.config/containers/systemd/
凭据 /run/containers/0/auth.json ~/.config/containers/auth.json
socket /run/podman/podman.sock /run/user/<uid>/podman/podman.sock
systemd 单元(生成) /run/systemd/generator/ 同左(user 实例)
subuid /etc/subuid 同
subgid /etc/subgid 同

21.2 常用格式化输出

podman ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
podman ps --format '{{.Names}} {{.Labels.env}}'
podman images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}'
podman volume ls --format '{{.Name}}'
podman network ls --format '{{.Name}} {{.Driver}}'
podman pod ps --format 'table {{.Name}}\t{{.Status}}\t{{.NumContainers}}'
podman stats --no-stream --format '{{.Name}} {{.CPUPerc}} {{.MemUsage}}'
podman system df --format '{{.Type}} {{.Total}} {{.Size}} {{.Reclaimable}}'
podman inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(podman ps -q)

21.3 一键排障

# 容器起不来
podman ps -a | grep <name>
podman logs --tail 100 <name>
podman inspect <name> --format '{{json .State}}' | jq
podman inspect <name> --format '{{json .Config}}' | jq
podman events --since 10m --filter container=<name>

# rootless 环境
podman info --format '{{.Host.Security.Rootless}} {{.Store.GraphRoot}}'
podman unshare cat /proc/self/uid_map
loginctl show-user $USER | grep Linger
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers

# Quadlet 服务
systemctl cat web.service
journalctl -u web.service -n 100 --no-pager
/usr/libexec/podman/quadlet -dryrun -f /etc/containers/systemd/web.container

21.4 与其他手册的配合

  • 宿主机系统(包管理、SELinux、防火墙):CentOS-7-10-常规运维指令手册.md / Ubuntu-常用运维指令手册.md
  • Docker 对照与迁移:Docker-常用运维指令手册.md
  • 集群编排:Kubernetes-常用运维指令手册.md
  • 私有仓库:Harbor-镜像仓库运维指令手册.md
  • 数据库容器化:MySQL-常用运维指令手册.md

一句话总结:Podman 的难点不在命令(和 Docker 几乎一样),而在三处——rootless 的 UID 映射与 cgroup 委派、SELinux 标签、systemd/Quadlet 的服务化方式。这三块顺了,剩下的都是 Docker 经验直接迁移。

Podman 常用运维指令手册 - 知识库 - 起航社区