Skip to content

필사 모드: Docker 到 Podman — 无守护进程容器引擎迁移完全指南

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言 — 命令相同,哲学不同

Podman 给人的第一印象是"把 docker 换成 podman 就能用的工具"。确实,alias docker=podman 之后大多数命令都能照常运行,官方也把 Docker CLI 兼容性当作目标。但表面之下的架构几乎是相反的方向 — 而迁移中遇到的所有差异(配置文件位置、GPU 设置、compose、权限问题)都是从这个架构差异中流淌出来的。

本文从根源上梳理这些差异。另外提一句,本文中的命令以 Podman 5.x / Docker Engine 27+ 为准。

内部工作原理 — 守护进程 vs fork-exec

Docker 的结构是客户端-服务器模式。 敲下 docker run 时,CLI 会生成一个 REST 请求发给 dockerd 守护进程,守护进程再委托给 containerd,containerd 又委托给 runc 来创建容器。所有容器最终的父进程都是这个守护进程。

Docker:  docker CLI ──REST──▶ dockerd ──▶ containerd ──▶ runc ──▶ 容器
Podman:  podman CLI ──fork/exec──▶ conmon ──▶ crun(runc) ──▶ 容器

Podman 没有守护进程。 podman run 就像普通进程一样,通过 fork-exec 直接创建容器。每个容器都会挂上一个叫 conmon 的小型监视进程来管理 stdio 和退出码,实际的创建工作则由 OCI 运行时 crun(用 C 编写,比 runc 更轻更快)完成。这种结构带来的含义:

  • 没有单点故障。 dockerd 一旦挂掉(或升级),所有容器的管理都会停摆;而 Podman 从一开始就没有中心进程。
  • 没有"root 守护进程套接字"这种攻击面。 /var/run/docker.sock 事实上等同于 root 权限本身,针对它的攻击层出不穷。Podman 中这个套接字根本不存在(兼容套接字只在需要时以用户权限开启)。
  • 与 systemd 自然结合。 由于容器就是普通的子进程,可以直接用 systemd 单元来管理。最新 Podman 推荐的方式是 Quadlet — 写一个 .container 文件就会生成 systemd 服务(podman generate systemd 已经过时了)。

Rootless — 默认值的差异

两个引擎都支持 rootless 模式,但重心不同。Docker 默认是 rootful,rootless 是可选项;Podman 则默认就是 rootless 体验(在 Fedora/RHEL 上,安装完成后直接用普通用户就能用)。

rootless 的原理是 user namespace。容器内的 root(UID 0)在外部会映射为你自己账号的 UID,容器内其他 UID 则映射到 /etc/subuid/etc/subgid 中分配的从属 UID 段。就算容器"越狱",拿到手的也只是普通用户的权限。

迁移时需要了解的 rootless 限制:

  • 无法绑定 1024 以下端口(默认情况)— 可以调低 sysctl net.ipv4.ip_unprivileged_port_start,或者改用高位端口。
  • 网络走用户空间协议栈(pasta/slirp4netns)— 对大多数负载来说已经足够,但如果需要极致的网络性能,可以考虑 rootful。
  • 存储位置不同 — 参见下面的配置章节。这是按 docker system df 的直觉去找磁盘占用时最容易困惑的地方。
  • 启动开销略有增加,但对长期运行的服务来说可以忽略不计。

配置文件 — 什么在哪里

如果说 Docker 的配置是以守护进程为中心(daemon.json),那么 Podman 就是以库为中心的分散式配置。系统全局配置在 /etc/containers/,按用户的配置在 ~/.config/containers/,后者优先级更高。

Docker                              Podman
──────────────────────────────      ─────────────────────────────────────────
/etc/docker/daemon.json             /etc/containers/containers.conf   (引擎·运行时选项)
                                    ~/.config/containers/containers.conf
(镜像仓库镜像等也在 daemon.json)     /etc/containers/registries.conf   (镜像仓库·镜像源·屏蔽)
                                    /etc/containers/storage.conf      (存储驱动·路径)
                                    /etc/containers/policy.json       (镜像签名策略)
~/.docker/config.json (认证)        ~/.config/containers/auth.json    (镜像仓库认证)

镜像·容器存储位置
/var/lib/docker                     rootful:  /var/lib/containers/storage
                                    rootless: ~/.local/share/containers/storage

迁移时最常需要动手的有两处:一是在 registries.conf 中指定 unqualified 镜像的搜索仓库(Docker 会自动把 nginx 解析为 docker.io,而 Podman 默认会询问具体是哪个仓库,或者遵循配置 — 加上 unqualified-search-registries = ["docker.io"] 就能和 Docker 保持一致);二是把公司内部的镜像源/认证信息迁移到 registries.conf + auth.json 中。

GPU — CDI 才是正解

在 Podman 中使用 NVIDIA GPU 的标准路径是 CDI(Container Device Interface)。CDI 是一个厂商中立的规范,描述"要把这个设备放进容器,需要哪些设备节点、库、环境变量",比 Docker 的 --gpus 这种运行时钩子方式更透明、可移植性也更高(Kubernetes 的 GPU Operator 内部也是用 CDI 实现的)。

# 1) 安装 nvidia-container-toolkit 后生成 CDI 规范
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml

# 2) 确认生成的设备名称
nvidia-ctk cdi list
#   nvidia.com/gpu=0, nvidia.com/gpu=all, (若为 MIG) nvidia.com/gpu=0:0 ...

# 3) 在容器中使用 GPU
podman run --rm --device nvidia.com/gpu=all \
  nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
  • 对应 Docker 的 docker run --gpus all 的是 --device nvidia.com/gpu=all(最新版 Podman 也接受 --gpus 作为兼容标志,但 CDI 写法才是正式规范)。
  • 在 rootless 下也能用 GPU。 GPU 驱动运行在内核空间,所以容器的权限级别和性能无关。如果能读取系统全局的 /etc/cdi/nvidia.yaml 就直接用,读取不了的话,就在用户空间生成规范,再把那个目录指定给 Podman。
  • 驱动更新后如果 GPU 识别不到,十有八九是忘了 重新生成 CDI 规范(nvidia-ctk cdi generate)。

Compose — compose.yaml 能不能直接照用?

先说结论:大多数情况下可以直接照用,而且有两种方法

方法 1 — 把 Docker Compose 二进制连接到 Podman 套接字(推荐)。 Podman 可以提供一个兼容 Docker API 的套接字。开启这个套接字后,标准的 docker compose 就能照常运行,只是后端换成了 Podman — 不需要重写 compose.yaml。

# 启用用户套接字 (rootless)
systemctl --user enable --now podman.socket

# 让 docker compose 指向 Podman 套接字
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock

docker compose up -d   # 后端是 Podman

由于用的就是 Go 实现的官方 Compose 规范实现,兼容性最高。podman compose 命令本身也是一个包装器,会调用外部的 compose 提供者(如果装了的话就是 docker-compose)。

方法 2 — podman-compose。 这是用 Python 重新实现的一个独立项目。优点是纯粹 — 不经过守护进程/套接字,直接调用 podman 命令 — 但在 Compose 规范的一些边角(部分网络选项、profiles、高级 depends_on 条件等)上,可能会和官方实现出现细微差异。简单的技术栈用它足够,复杂的 compose 文件则方法 1 更安全。

迁移时与 compose 相关的注意事项:

  • 在没有守护进程的 Podman 中,restart: always 并不意味着"开机自动启动" — 开机自动化改用 Quadlet/systemd 才是 Podman 式的正解。
  • 如果面向生产环境,也可以考虑用 podman kube play(直接运行 Kubernetes YAML)代替 compose。这样能把本地和 Kubernetes 的清单统一起来。

迁移陷阱合集 — 提前知道只需 30 分钟,不知道就得半天

  • SELinux 卷标签:在 Fedora/RHEL 上,如果卷挂载出现 Permission denied,十有八九是 SELinux 的问题。加上 -v ./data:/data:Z(独占)或 :z(共享)标签即可。从 Ubuntu 迁移过来的 compose 文件最常踩这个坑。
  • unqualified 镜像名:如果没有指定上面配置章节里的 unqualified-search-registriespodman pull nginx 的行为会和 Docker 不一样。如果是 CI 脚本,最稳妥的做法是直接写 docker.io/library/nginx 这样的完整路径。
  • rootless 存储空间:镜像会堆积在主目录(~/.local/share/containers)里。如果服务器的主目录空间较小,请迁移 storage.conf 里的 graphroot
  • 依赖 Docker 套接字的工具(Testcontainers、部分 CI):用方法 1 的 Podman 套接字 + DOCKER_HOST 大多能解决。
  • podman-docker:这是一个把 docker 命令转接到 podman 的垫片包。对脚本兼容很有用,但一定要告诉团队成员"这台服务器上的 docker 其实是 podman"。
  • 可以渐进式迁移:两个引擎的存储是分离的,能在同一台机器上共存。按服务逐个迁移即可。

版本与社区 — 谁在开发,讨论在哪里

版本感(截至 2026 年): Podman 处于 5.x 世代,从 4.x 升级到 5.0 的过程中,默认网络切换为 pasta,CDI 支持也趋于成熟。它深度集成在 RHEL 9/10 中,Fedora 也已经连续多个版本以 Podman 为默认容器工具。Docker Engine 处于 27+ 世代,依然拥有最大的生态系统和文档量。

治理与社区:

  • Docker — 由 Docker, Inc. 主导,采用商业产品 + 开源(moby/moby)的双重结构。Docker Desktop 在超过一定规模的企业中需要付费订阅(引擎本身是开源的)。它最大的资产是海量的教程和 Stack Overflow 积累。
  • Podman — 是由 Red Hat 主导的 containers GitHub 组织成员之一,与 Buildah(镜像构建)、Skopeo(镜像检查/复制)同属一个家族。全部采用 Apache-2.0 开源协议,Podman Desktop 也是免费的。讨论主要活跃在 GitHub(containers/podman)、Podman 邮件列表和 Matrix 频道上,在 RHEL 系企业环境中的采用尤其强劲。

该选哪个的实用标准:如果团队使用 RHEL/Fedora 系、需要 rootless 安全性,或偏好基于 systemd 的运维方式,Podman 是自然的选择。如果依赖大量 Docker Desktop 相关工具,或生态文档量很重要,Docker 依然是稳妥的选项。而且两者都建立在同一个 OCI 标准之上,镜像不管在哪一边构建,都能在另一边运行 — 这正是标准存在的福气。

结语

从 Docker 迁移到 Podman,不是命令的替换,而是 运维模型的替换 — 从守护进程到 fork-exec,从 rootful 到 rootless,从 daemon.json 到 containers.conf 家族,从 --gpus 到 CDI,从依赖 compose 守护进程到套接字兼容或 systemd/Quadlet。幸运的是,托 OCI 标准的福,镜像和 compose.yaml 这类产物几乎能原样迁移,陷阱也收敛为上面整理的那几点(SELinux 标签、unqualified 镜像、存储路径)。如果想动手打牢容器基础,也可以用一用本站的容器实验室Linux 终端

参考资料

현재 단락 (1/66)

Podman 给人的第一印象是"把 docker 换成 podman 就能用的工具"。确实,`alias docker=podman` 之后大多数命令都能照常运行,官方也把 Docker CLI 兼容...

작성 글자: 0원문 글자: 6,419작성 단락: 0/66