我烦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单元:
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),就可以使用 PROTECTED_1 工具 生成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。
适用于 PROTECTED_0 的方法(已弃用)
先创建个新容器,至于用 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"。