李守中
该站已迁往根域名 https://lishouzhong.com
需要注意,迁移后的文章的 url 可能会发生变化。
域名 https://note.lishouzhong.com 下的内容将不再更新,但已有内容会永久保留。

Podman 相关

Table of Contents

1. 容器 Volume

1.1. SELinux 在 volume 上的坑

不开 SELinux 的情况下,执行 podman run ... -v /local/path:/container/path ... 容器可正常挂载 volume 并使用。

在 SELinux 下,用户创建的文件或文件夹的默认 SELinux Type 为 user_home_t,而 SELinux 里面对于容器的规则会让容器无法读写 SELinux Type 为 user_home_t 的文件。

命令改成 podman run ... -v /local/path:/container/path:Z ... 则系统会将挂载点的 SELinux Type 改为 container_file_t:s0...,比如 container_file_t:s0:c191,c797, 表示这个 volume 只由一个容器独占。

也能将命令改成 podman run ... -v /local/path:/container/path:z ... 系统会将挂载点的 SELinux Type 改为 container_file_t:s0 表示这个 volume 可以给多个容器分享。

注意: -v 参数最后的大写 Z 表示这个卷由一个容器独占,小写 z 表示卷可以在多个容器间共享。

2. 容器网络

2.1. 执行 firewall-cmd --reload 之后容器断网

sudo firewall-cmd --reload 之后防火墙会根据配置文件重新生成流量过滤规则,但是 podman 容器的流量过滤规规则没有被 firewalld 保存在文件中,所以此时容器的规则就全部丢失了。

同时,防火墙 reload 之后,podman 并不会重新为容器添加过滤规则,这就导致了所有依赖 firewalld 的容器都断网了。podman 仅仅只是在容器启动的时候,在 firewall 中为这个容器添加了一次规则,就没有然后了。

bridge 网络模式的容器的流量转发靠的是 firewalld,而 网络模式为 host 的容器的网络流量由于不走 firewalld 转发 ,所以使用 host 网络模式的容器并不受影响。

这个问题 2020 年就出现了,这里 https://github.com/containers/podman/issues/5431 也讨论了这个问题。结论是目前无解。

在为 pod container 开放端口时,不能用:

sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=dns --permanent
sudo firewall-cmd --reload

应该用:

sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=http
sudo firewall-cmd --zone=public --add-service=dns --permanent
sudo firewall-cmd --zone=public --add-service=dns

这是 bug 的表现:

[root@rhel8a zones]# sudo firewall-cmd --zone=trusted --list-all
trusted (active)
  target: ACCEPT
  icmp-block-inversion: no
  interfaces:
  sources: 10.88.0.5/32 10.88.0.6/32
  services:
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
[root@rhel8a zones]# podman inspect vlmcsd | grep IPA
            "IPAddress": "10.88.0.6",
                    "IPAddress": "10.88.0.6",
                    "IPAMConfig": null,
[root@rhel8a zones]# sudo firewall-cmd --reload
success
[root@rhel8a zones]# sudo firewall-cmd --zone=trusted --list-all
trusted
  target: ACCEPT
  icmp-block-inversion: no
  interfaces:
  sources:
  services:
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

2.2. root 容器使用 bridge 网络模式打开的端口会绕开 firewalld

root 用户的,网络模式为 bridge 的容器启动之后,相应端口直接就是打开的,不需要操作 firewalld。

root 用户的,网络模式为 host 的容器启动之后,需要正常打开端口。

而普通用户的容器的网络模式不论是 bridge 还是 host,容器启动之后,都需要在 firewalld 中打开对应端口。

2.3. rootless 容器不能使用低于 1024 的端口号

在 Linux 上,非特权用户无法打开低于端口号 1024 的端口,这个限制同样适用于 Podman。

因此,默认情况下,rootless 容器无法暴露低于端口号 1024 的端口。

要允许所有非特权应用程序绑定到低于1024的端口,可以使用以下命令解除此限制: sysctl net.ipv4.ip_unprivileged_port_start=0

要永久解除此限制,运行: sysctl -w net.ipv4.ip_unprivileged_port_start=0

net.ipv4.ip_unprivileged_port_start 指定端口开始的端口都是 unprivileged port,例子里表示从端口号 0 及 0 之后的端口都是 unprivileged port,即,没有特权端口。

或者,可以用防火墙的端口转发功能,将流向特权端口的流量转发到非特权端口上。

3. ID 映射 (userns)

在 rootless 容器中,podman 自动管理 ID 映射的选项是 --userns 它有如下可选值:

  • "" 默认空值,等于 --userns=host. 。将当前用户的 UID 映射为容器内 root 用户的 UID
  • keep-id 将当前用户映射到容器内。即,在容器内创建与容器外用户具有相同 UID 的用户
  • keep-id:uid=<uid>,gid=<gid> 将当前用户映射到容器内的 <uid>:<gid> 账户
  • auto 当前用户的 UID 不被映射到容器内,容器内 root 用户的 UID 会被赋一个没有被占用的值。从 /etc/{subuid,subgid} 选 ID 时从没有被占用的部分选 1024 个或更多的 ID 给容器使用 (其他选项默认使用 /etc/{subid,subgid} 指定的所有范围,导致不同容器使用的 ID 范围可能重合)
  • nomap 当前用户的 UID 不被映射到容器内,将 /etc/subuid 内写明可用的,第一个 UID 作为容器内 root 用户的 UID

用户也可以用 --uidmap <start-number-in-container>:<start-number-in-host>:<range> 自己管理到容器的 ID 映射,用法 --uidmap 0:100000:5000 表示,将 host 上 100000 开始的 5000 个 ID 映射到容器中,并且这些 ID 在容器内从 0 开始 (100000 对应容器内 0, 100001 对应 1 ... 104999 对应 5000)。选项 --gidmap 的功能与 --uidmap 类似。它们与 --userns 冲突。

在容器内执行 cat /proc/self/{uid_map,gid_map} 可以查看容器中的 ID 映射规则,输出格式也为 <start-number-in-container>:<start-number-in-host>:<range>。

4. systemd 管理容器

和 docker 不同, podman run --restart ( unless-stopped | always ) ... 之后,如果机器重启,容器会被系统停止,并且容器不随机器一同重启。

如果使用 systemd 来管理容器启停,那么 podman run ... --restart ... 参数绝对不能用。

4.1. 系统级别的容器

若要让容器随机器一同重启,需要自已写一个 .service 文件放在 /etc/systemd/system 下。

.service 文件的内容可以用 podman generate systemd --name <container_name> 生成。

下面的命令可以将输出重定向到 /etc/systemd/system 下,一步到位:

podman generate systemd \
    --name <container_name> \
    > /etc/systemd/system/container-<container_name>.service

在命令中给定 --new 参数表示每次重启时从镜象生成新的容器,比如:

podman generate systemd --new \
    --name <container_name> \
    > /etc/systemd/system/container-<container_name>.service

也可以在 podman generate systemd --files --name <container_name> 生成 .service 文件后,执行 mv container-<container_name>.service /etc/systemd/system/container-<container_name>.service 把文件放在 /etc/systemd/system 下。

注意: 如果开了 SELinux,在移动 .service 文件后还要用 restorecon -v /etc/systemd/system/container-<container_name>.service 重新给文件正确的权限。

然后执行 systemctl enable --now <container_name>.service 即可让指定的容器开机启动。

4.2. 用户级别的容器

4.2.1. 分配用户管理器

只需要执行 loginctl enable-linger [USER…] 启用 / 禁止用户逗留 ( 相当于保持登录状态 )。

如果指定了用户名或 UID,那么系统将会在启动时自动为这些用户派生出用户管理器,并且在用户登出后继续保持运行。这样就可以允许未登录的用户在后台运行持续时间很长的服务。

注意: root 用户也不例外。不配置 enable-linger 的情况下,不登陆是不会被分配用户管理器的。

如果没有指定任何参数,那么将作用于当前调用者的用户。

执行 loginctl disable-linger [USER…] 取消操作。

4.2.2. 用户级的 .service 文件

制作 .service 文件的方法参考系统级容器 .service 文件的生成方法。

然后放一个归属普通用户的 vlmcsd 容器的例子:

#!/bin/bash

podman run \
    -d \
    --name vlmcsd \
    --network bridge \
    -p 1688:1688 \
    docker.io/mikolatero/vlmcsd:latest
    # --restart unless-stopped \

############
#
# podman generate systemd --new \
#     --name vlmcsd \
#     > $HOME/.config/systemd/user/container-vlmcsd.service
#
############

mkdir -p $HOME/.config/systemd/user

podman generate systemd --new --files --name vlmcsd

mv container-vlmcsd.service $HOME/.config/systemd/user/container-vlmcsd.service

restorecon -v $HOME/.config/systemd/user/container-vlmcsd.service

systemctl --user enable container-vlmcsd

sudo firewall-cmd --add-port=1688/tcp --permanent
sudo firewall-cmd --add-port=1688/tcp

4.3. podman 生成 systemd 文件中的坑

如果用 systemd 管理容器启停,那么 podman run ... --restart ... 这个参数绝对不能用。如果没有加这个参数, podman generate systemd 生成的 .service 文件中,服务启动时执行的命令如下:

...
ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --sdnotify=conmon --cgroups=no-conmon --rm --replace -d --name adguardhome ......
...

注意里面有一个 podman run ... --rm ... 参数,参数 --rm--restart 冲突。如果在首次生成容器时的命令用 podman run ... --restart ... ,那么,由 podman generate systemd 得到的 .service 文件中的启动命令会变成 podman run ... --rm ... --restart ... 。这个命令是无法启动容器的。

sudo journalctl -n 100 可以看到日志中有类似这样的报错:

...
Error: the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"
...


Last Update: 2023-12-03 Sun 17:42

Generated by: Emacs 28.2 (Org mode 9.5.5)   Contact: lsz.sino@outlook.com

若正文中无特殊说明,本站内容遵循: 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议