从 Docker 到 Podman

我烦 Docker 很久了。倒不如说我烦 “屁大点事都要容器化” 很久了,但在不得不用的时候,与其用自成一体的 Docker,不如用同样遵守 OCI 的 Podman。

由于 Docker 和 Podman 基本能够兼容,这里是一些个人碰到可能比较有意思的用法。我也不知道会不会继续写,什么时候会写。

与 systemd 结合

podman-systemd 已被弃用,请参考使用 Quadlet 的新方法

现在使用 podman generate systemd,会提示 DEPRECATED command: It is recommended to use Quadlets for running containers and pods under systemd.

Red Hat 文档

Docker 有 dockerd,能自主控制容器的启动与停止,Podman 没有。相比 Docker,Podman 的特点之一就是与 systemd 的融合,毕竟两个玩意全都是 Red Hat 自家的,他们大概也不想再做一个 daemon。

解决办法就是,Podman 容器可以使用 systemd 单元来控制启停等功能。举个例子,如果是 root 用户的容器,可以直接运行下面的命令,生成一个 systemd 单元:

bash
1
podman generate systemd --new <container_name> > /etc/systemd/system/<service_name>.service

记得替换你自己的容器名称和单元名称。虽然前后两个名字不统一又不是不能用,但个人强烈建议统一一下,可能能免去一些玄学问题。然后就可以用平常管理 systemd 单元的方法来管理它了。


Podman 推荐的用 systemd 管理容器的方式是 Quadlet。一个 Quadlet 形似 systemd unit,但又神似 docker-compose YAML。相比于已经弃用的 podman-systemd ,Quadlet 表现能力更强,更容易编辑。看一眼 redis.container 示例1就明白了:

ini
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[Unit]
Description=Redis container

[Container]
Image=docker.io/redis
PublishPort=6379:6379
User=999

[Service]
Restart=always

[Install]
WantedBy=local.target

详细的文件结构在 Podman 的文档 中有说明。除上面的 .container 示例外,还有.kube.network.volume 文件分别描述基于 K8S 的服务、容器网络设备和卷。

编辑完成后,要将上面的文件放进下面的目录之一2

  • $HOME/.config/containers/systemd/(rootless container)
  • /usr/share/containers/systemd/
  • /etc/containers/systemd/

然后运行 systemd daemon-reload,就可以自动生成对应的 systemd unit。比如在我的设备上,刚刚的 redis.container 生成的 systemd unit 就在 /run/systemd/generator/redis.service

ini
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Automatically generated by /usr/lib/systemd/system-generators/podman-system-generator
#
[Unit]
Description=Redis container
SourcePath=/etc/containers/systemd/redis.container
RequiresMountsFor=%t/containers

[X-Container]
Image=docker.io/redis
PublishPort=6379:6379
User=999

[Service]
Restart=always
Environment=PODMAN_SYSTEMD_UNIT=%n
KillMode=mixed
ExecStop=/usr/bin/podman rm -f -i --cidfile=%t/%N.cid
ExecStopPost=-/usr/bin/podman rm -f -i --cidfile=%t/%N.cid
Delegate=yes
Type=notify
NotifyAccess=all
SyslogIdentifier=%N
ExecStart=/usr/bin/podman run --name=systemd-%N --cidfile=%t/%N.cid --replace --rm --cgroups=split --sdnotify=conmon -d --user 999 --publish 6379:6379 docker.io/redis

[Install]
WantedBy=local.target

当然,放出来的目的只是展示会生成一个怎样的文件,实际有需要时都是编辑 Quadlet 的。

从其他格式生成

坏消息是似乎无法直接为一个已经存在的容器生成 Quadlet 了;但也有好消息,只要能搞来 podman run 命令(或 docker-compose YAML),就可以使用 podlet 工具 生成 Quadlet。比如:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ podlet podman run --name redis -p 6379:6379 --restart always --user 999 docker.io/redis
# redis.container
[Container]
Image=docker.io/redis
ContainerName=redis
PublishPort=6379:6379
User=999

[Service]
Restart=always

开机启动

截至编写本文时,由于 Quadlet 生成的 systemd unit 是自动生成、可能改变的,所以无法 enable3(会提示 Failed to enable unit: Unit is transient or generated)。

解决办法是在 Quadlet 中添加任何 [Install] 栏的内容,比如之前的 redis.container,或者参考官方文档的做法:

ini
1
2
[Install]
WantedBy=multi-user.target default.target

然后就可以自动开机启动了。

自动更新镜像

Red Hat 文档

手动重新找到同样的参数更新,不仅麻烦还不安全。Podman 自带了一个 auto-update 工具,可以自动更新镜像,但依赖 systemd unit。

适用于 podman-systemd 的方法(已弃用)

先创建个新容器,至于用 run 还是 create 其实都没什么关系。但不要忘了指定 io.containers.autoupdate label。属性可以为 registry 或者 local

bash
1
podman create --label io.containers.autoupdate=registry --name <container_name> <image_name>

然后还要创建一下 systemd 单元,方法如上,这里再次建议单元名为容器友好名称,或者那个 64 位的随机 hex 值。


在上文容器 Quadlet 中的 [Container] 一栏中,添加一行 AutoUpdate=,后面的值从下面的选项中选一个:

  • registry:更新的时候会 pull 一下
  • local:有需要的时候得手动 pull,如果本地镜像更新,用本地的替换

启用 systemd 单元后,建议先试一下:

bash
1
podman auto-update --dry-run

看名字也知道不会真的更新,只不过看看是不是配置对了。真正更新的时候把 --dry-run 去掉就行。

这玩意看起来更像是 apt update && apt upgrade 或者 pacman -Syu 之类的工具。如果更新失败,它也会自动进行回滚。

许可证:CC BY-SA 4.0
最后更新于 Oct 15, 2023 12:59 +0800