从 Docker 到 Podman

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

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

与systemd结合

PROTECTED_0 已被弃用,请参考使用 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),就可以使用 PROTECTED_1 工具 生成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。

适用于 PROTECTED_0 的方法(已弃用)

先创建个新容器,至于用 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 之类的工具。如果更新失败,它也会自动进行回滚。

Netavark和Nftables

Netavark是Podman依赖的网络组件,后者需要前者创建虚拟网络等。而Nftables是iptables的替代品,依托Netfilter,用于创建防火墙规则或重定向等操作。

Netavark的默认后端是iptables。当Podman涉及到创建Pod等与虚拟网络有关的操作时,iptables-nft会操作Nftables创建一个链,而这个链的名字常常因为各种原因已经使用,于是会出现以下错误信息:

plaintext
1
Error: starting container xxxxx: netavark: code: 1, msg: iptables: Chain already exists.

这时需要在 /etc/containers/containers.conf 里,将后端替换为Nftables4

conf
[network]
firewall_driver = "nftables"

如果版本比较老,可以改成 "none"