我烦 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 单元:
podman generate systemd --new <container_name> > /etc/systemd/system/<service_name>.service
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
示例就明白了:
[Unit]
Description=Redis container
[Container]
Image=docker.io/redis
PublishPort=6379:6379
User=999
[Service]
Restart=always
[Install]
WantedBy=local.target
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 的服务、容器网络设备和卷。
编辑完成后,要将上面的文件放进下面的目录之一:
$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
:
# 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
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。比如:
$ 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
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 是自动生成、可能改变的,所以无法 enable(会提示 Failed to enable unit: Unit is transient or generated
)。
解决办法是在 Quadlet 中添加任何 [Install]
栏的内容,比如之前的 redis.container
,或者参考官方文档的做法:
[Install]
WantedBy=multi-user.target default.target
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
。
podman create --label io.containers.autoupdate=registry --name <container_name> <image_name>
1
|
podman create --label io.containers.autoupdate=registry --name <container_name> <image_name>
|
然后还要创建一下 systemd 单元,方法如上,这里再次建议单元名为容器友好名称,或者那个 64 位的随机 hex 值。
在上文容器 Quadlet 中的 [Container]
一栏中,添加一行 AutoUpdate=
,后面的值从下面的选项中选一个:
registry
:更新的时候会 pull 一下
local
:有需要的时候得手动 pull,如果本地镜像更新,用本地的替换
启用 systemd 单元后,建议先试一下:
podman auto-update --dry-run
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 创建一个链,而这个链的名字常常因为各种原因已经使用,于是会出现以下错误信息:
Error: starting container xxxxx: netavark: code: 1, msg: iptables: Chain already exists.
1
|
Error: starting container xxxxx: netavark: code: 1, msg: iptables: Chain already exists.
|
这时需要在 /etc/containers/containers.conf
里,将后端替换为 Nftables:
[network]
firewall_driver = "nftables"
[network]
firewall_driver = "nftables"
如果版本比较老,可以改成 "none"
。