前言

为什么要用 podman 代替 docker?podman 主要有以下优点:

  • 支持免 root 启动容器
  • 无守护进程
  • 支持 k8s 样式的 yaml 配置文件

docker 现在也可以使用 -userns-remap 参数来免 root 运行守护进程了。

免 root 启动容器能够提升系统的安全性。虽然容器化技术能够较好的抑制容器内的恶意软件,但容器逃逸技术层出不穷,不使用 root 启动容器能够在容器逃逸后依旧保持一定的安全性。同时,免 root 启动容器使用户权限管理更简单了。

但是,免 root 启动容器有一些限制,举例如下:
– 需要设置用户的 subuid/subgid (大部分 linux 发行版会在 adduser 的时候自动设置这一项目)
– 需要使用 cgroups v2 (因为要用到 cgroups v2 才有的资源限制)
– 需要额外安装 slirp4netns 软件包(用于提供网络)
– slirp4netns 的最大吞吐量约为 11.2 Gbps,而 root 下的 bridge 模式最大吞吐量可达 40.1 Gbps.
– 无法绑定小于 1024 的端口
– 无法与其他使用 root 启动的容器共享镜像
– 文件系统需要使用 overlayfs (Linux 5.11+) 或 fuse-overlayfs(Linux 4.18+),fuse 版本的性能相对较低。

详细限制可以查看官方文档

在 docker 中,所有的容器都是通过 docker 的守护进程来启动的。这个守护进程会与 containerd 通信,再调用 runc,最终启动目标进程。所有的 container 都是守护进程的子进程,守护进程因此可以管理各个 container。

与 docker 使用守护进程管理 container 不同,podman 更希望使用已有的 systemd 管理各个 container 的启停。在 podman 中,podman 会首先拉起 conmon,conmon 会拉起对应的 container,然后监控其状态 (即 Contanier Monitor) 。

从 docker 迁移到 podman

podman 的命令与 docker 的命令基本一致,你甚至可以直接使用 alias 将 podman 作为 docker 的别名。完整的命令兼容列表可以查看这个文档

不过,podman 本体并没有构建镜像的能力。若需要构建镜像,你需要使用 buildah。buildah 本身也支持 dockerfile 文件,你可以直接使用 buildah bud 命令作为 docker build 命令的替代。

需要注意的是,podman 并不将 Docker Hub 作为镜像的缺省源。即在仅指定路径 “centos:8” 时,podman 并不会自动的将其转换为 “docker.io/centos:8”.

从 docker-compose 迁移到 podman

概念

podman 中有一些特有的概念,部分概念与 docker-compose 不一致,参见下表

概念 Podman Docker Compose
container 定义了一个容器的配置 容器本身
service 定义了 pod 要如何对外映射端口 定义了容器的配置,对端口做映射
pod 定义了多个容器的运行组
compose 定义了多个 service 的运行组

综上所述,我们可以简单的将 compose 看作 podman 的 pod 和 service 的集合体。

podman 和 docker-compose 的网络模型也略有不同。同一个 pod 下的所有 container 共享同一个网络 namespace,它们之间可以直接通过 localhost 互相访问。而 docker-compose 的不同 service 是不同的网络 namespace,需要通过对方的名称才能互相访问。
从网络的角度来说,podman 的 pod 概念也可以对应 docker-compose 的 service。

方案选择

现在有三种编排方案可以选择:

  • docker-compose: podman 自 v3.0 起提供了兼容的 API,docker-compose 可以直接使用 podman 作为后端了。需要使用以下命令启用服务:
    systemctl enable --now podman.socket
  • podman-compose: 在配置文件兼容的情况下,实现了 docker-compose 功能的子集。其相较于 docker-compose 更轻量,会直接调用 podman 命令,对原生 podman 的功能(如 rootless)也有更好的支持。
  • podman k8s yaml: podman 原生的编排方案,是 k8s 语法的一个子集,可以很方便地迁移到 k8s。

docker-compose 和 podman-compose 都是第三方的解决方案,需要额外安装软件才能实现。在这里,我们使用 podman 原生的 k8s yaml 支持来编排服务。

ref: podman 与 docker-compose, podman 与 k8s

文件格式

要对文件格式迁移,podman 官方建议首先替换 docker-compose 的 backend,然后使用 docker-compose 启动容器。

接下来,使用以下命令导出 pod:

$ sudo podman generate kube -s -f [export_yaml] [container1_id] [container2_id] ...

-s 参数指示需要生成服务(Service)用于导出端口,-f 参数指示保存到文件。

不过如果你已经很熟悉 k8s yaml 的格式的话,也可以手动迁移配置文件。

服务管理

Docker-compose 使用了守护进程管理各个容器的启动和重启等操作。podman 没有守护进程,我们可以使用系统自带的服务管理工具 systemd 管理各个 pod 的启停。

使用 podman generate systemd -f --new [podname] 即可自动生成对应的服务文件;将这一服务文件放到 systemd 文件目录下,即可使用 systemd 管理此 pod。在这个过程中,Podman 根据 container 和 pod 的 ID,生成了对应的命令,使得 systemd 能够正确的调用 podman 启动需要的资源。

ref: 使用 systemd 将 pod 运行为服务

更新

正如传统的 docker 一样,在 podman 中如果需要更新一个已有 container 的 image,则需要首先停止并移除这个 container,然后再重新启用一个新的 container。如果我们在之前生成了 systemd 文件,此时我们会需要重新再次生成一次新的 systemd 文件,以更新此文件中对应的资源 ID。
这项工作听上去就非常的麻烦,所以 Podman 提供了自动更新功能,用于一键更新所有 container 的镜像版本。

运行 podman auto-update 即可更新所有正在运行的 container。
这项功能有以下的限制:

  • 需要是使用 –new 参数导出的 systemd service 文件启动的 container
  • 需要设定 io.containers.autoupdate 标签,其值为 registry 表示升级到 registry 的版本,local 则表示升级到本地已有的版本
  • 镜像引用不能是ID,必须是正常的url,否则无法正确新建 container

自动更新功能还提供了自动回滚的技术。如果一个服务在更新后无法正常启动,则 podman 会尝试将其回滚到之前能够正常启动的版本。Systemd 会根据进程的状态,更新对应 service 的状态;对于一些应用(如数据库),并不是进程在跑了就表示自身工作正常,此时 systemd 提供了一个 sd_notify 方法,用于通知 systemd 更新自身的状态。在默认情况下,conmon 会监控 container 的状态,再利用 sd_notify 机制更新服务的状态。如果需要让 container 里的进程自行使用 sd_notify 更新,可以使用 --sdnotify=container 参数设定,让 conmon 提供 sd_notify 代理服务。

ref: 自动更新与回滚

k8s yaml 格式的支持

详情可以参考官方手册

简单来说,Podman支持以下四种类型的资源
Pod
Deployments
PersistentVolumeClaim
ConfigMap

对于 Pod 和 Deployment,Podman 只支持以下三种
hostPath:映射主机文件
– 支持 default (empty), DirectoryOrCreate, Directory, FileOrCreate, File, Socket, CharDevice, BlockDevice 设备类型
– 若值中含有斜杠,则视为文件路径;否则视为名字
emptyDir: 提供一个空白文件夹,并在资源删除时删除
– persistentVolumeClaim

当然,由于 podman 只能在本地运行,所以 deployment 的所有的 replica 都会运行在本地。

对于 PersistentVolumeClaim,Podman 只需要名字(name)这一参数即可创建对应的资源。你也可以指定以下注解(annotation)来设定参数:

  • volume.podman.io/driver
  • volume.podman.io/device
  • volume.podman.io/type
  • volume.podman.io/uid
  • volume.podman.io/gid
  • volume.podman.io/mount-options

服务管理与自动更新

使用 k8s yaml 生成的 pod 不能通过 --new 参数生成 systemd 服务文件。若要使用 systemd 管理 k8s yaml 的 pod,一个简单的方法是使用以下命令:

$ escaped=$(systemd-escape ~/workload.yaml)
$ systemctl --user start [email protected]$escaped.service
$ systemctl --user is-active [email protected]$escaped.service

自动更新默认是不启用的,需要设定下列 annotation 控制更新的逻辑:
– io.containers.autoupdate: “registry|local” 控制所有 container 的更新逻辑
– io.containers.autoupdate/$container: “registry|local” 控制指定 container 的更新逻辑
– io.containers.sdnotify: “conmon|container” 控制所有 container 的 sdnotify 逻辑
– io.containers.sdnotify/$container: “conmon|container” 控制指定 container 的 sdnotify 逻辑

结语

podman 当前处于较为活跃的开发状态,上文所提到的一些功能需要在较新的 podman 版本下才能使用。同时,部分功能也需要依赖较高版本的内核。在写下本文的时候,podman 的最新版本 4.2.0 尚未进入各大发行版的默认仓库,主流的 LTS 内核也尚停留在 5.10 版本,这就意味着许多新功能(如 systemd 管理 k8s 样式的 pod)无法使用。

podman 的文档相对于 docker 可以说是少的可怜。官网仅给出了一些简单的示例,同时也给了非常详尽的命令行参考。但是,对于特定场景和特定功能的应用,你就需要去 redhat 的网站上找对应的博客。这些博客可以说的官方文档的一部分,但是它们却从未出现在官方网站上。

同时,podman 相对于 docker 的优势不大。实际上,podman 相对于 docker 的优点仅限于无守护进程、支持 k8s 样式的 yaml 文件以及自动更新回滚功能。

无守护进程听上去虽然很不错,但是它的代价是需要与 systemd 配合使用,这稍微增加了配置的复杂性。使用 systemd 管理各项服务确实是一个不错的选择,它能够提供统一的服务管理体验。然而如果是使用 k8s 的 yaml 文件创建的服务,其服务名称会变得非常长且丑,这非常影响使用体验。

在 k8s 已经成为容器编排的事实性标准的今天,使用 k8s 样式的文件可以很好的从 podman 迁移到 k8s,这无疑是一种较大的优势。但对于不需要往 k8s 迁移的应用来说,这其实并没有什么实际上的用处。

在当前状态下,对于使用 docker 作为对外服务的服务器来说,可以等待 podman 进入各大发行版后迁移。其 k8s 文件兼容特性和自动更新回滚功能对于服务器来说是非常有用的。而对以上优势没有感觉的个人来说,其实可以继续使用 docker 和 docker-compose,毕竟其文档充足而且使用简单。

分类: Linux

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注