使用 User= 或 --user 将无根容器作为 systemd 服务运行的最佳实践?

使用 User= 或 --user 将无根容器作为 systemd 服务运行的最佳实践?

我正在尝试使用 Podman 作为 systemd 服务运行无根容器。我还想以非 root 权限运行服务本身:

a) 作为系统服务,但User=设置为服务用户,或
b) 作为该服务用户的用户服务 ( systemd --user),之前已运行loginctl enable-linger <username>以允许长时间运行的服务。

这个想法是标准化我们未来在生产中进行小规模无守护程序和无根容器部署的方法,但我不完全确定选择这些方法中的哪一种,因为我不知道所有的陷阱。下面是一些背景讨论,其中实际问题以粗体显示。

作为初始实验,我正在运行maptiler/tileserver-gl图像以提供来自本地文件系统源的地图图块。

我注意到的一件事是,使用选项 a 时,单元文件内似乎没有内置方法来访问有关指定为的用户的任何信息User=(请参阅systemd.unit(5) 中的“说明符”;例如,%t指向/run%h指向/root)。这是相关的,因为我最初为tileserver-glwith创建了单元文件podman generate systemd --new--new在每次启动和停止时方便地创建和销毁容器,使用--cidfile=%t/%n.ctr-idinExecStart=来创建(和删除ExecStartPre=)包含容器 ID 的文件。当然,我们的服务用户没有写入权限/run。这不是一个破坏性的事情,因为我可以简单地进行硬编码(/run/user/<uuid>%t使用运行容器的非重新创建方法),但失去“活力”是一种耻辱。有没有办法User=从系统服务文件中获取用户的 UID 并将其插入到ExecStart=我错过的命令中?

如果我的理解正确,显然这两个选项都无法绑定到 1024 以下的 TCP 和 UDP 端口,除非采取其他解决方法(这个看起来很适合我们的情况)。我最初认为User=服务可以在放弃权限之前执行绑定,但显然情况并非如此。根据一些研究,最“systemd-native”的方式是使用套接字单元来实现这一点,但它似乎需要正在运行的服务主动支持它。这两个选项都不能“本地”绑定到没有套接字单元的 sub-1024 端口,甚至在使用这些端口时,也仅在某些情况下,是否正确?

最终,我在这里寻找最佳实践。根据您阅读的来源,有些人强调--user登录会话期间的临时服务,但 OTOH 的存在enable-linger似乎表明对长时间运行的会话具有更广泛的适用性。您认为这两种选择有哪些显着的优缺点?对于这种“服务用户”案例,您认为哪种选择更好,为什么?

答案1

User=目前不支持使用 systemd 指令在 systemd 系统服务中运行无根 Podman 。

功能要求在 Podman GitHub 项目中。

2023 年 11 月 21 日更新:

我想该功能仍然没有得到正式支持,但我使用 , 和 创建了 systemdUser=test系统Type=notify服务Delegate=yes

[Unit]
Wants=network-online.target
After=network-online.target
[email protected]
[email protected]
RequiresMountsFor=/run/user/1000/containers

[Service]
User=test
Environment=PODMAN_SYSTEMD_UNIT=%n
KillMode=mixed
ExecStop=/usr/bin/podman rm -f -i --cidfile=/run/user/1000/%N.cid
ExecStopPost=-/usr/bin/podman rm -f -i --cidfile=/run/user/1000/%N.cid
Delegate=yes
Type=notify
NotifyAccess=all
SyslogIdentifier=%N
ExecStart=/usr/bin/podman run \
     --cidfile=/run/user/1000/%N.cid \
     --cgroups=split \
     --rm \
     --env "NGINX=3;" \
      -d \
     --replace \
     --name systemd-%N \
     --sdnotify=conmon \
     docker.io/library/nginx

我尝试尽可能接近 Quadlet 生成的服务的样子。

测试网络服务器。似乎有效。

$ curl localhost:80 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

在示例中,我还使用了套接字单元来侦听特权端口。详情请参见 https://unix.stackexchange.com/a/762009/9360

我刚刚尝试过这个,所以我仍然需要弄清楚这个解决方案的效果如何。

参考:

示例 3 和示例 4 如下: https://github.com/eriksjolund/podman-nginx-socket-activation/

答案2

以下内容已在 RHEL 8.6 和 9.1、podman 4.0.2 下测试,启用了 SELinux。该服务无需用户登录即可启动。

Linux 用户 svcacct 的用户 ID 为 1000。Shell 脚本launchContainers.shshutdownContainers.sh包含用于启动和停止 pod 的相应 podman 命令。这些脚本可以独立于 systemd 使用和测试。loginctl enable-linger svcacct作为 sudo 用户运行一次。 /etc/systemd/system/ 中的 systemd 单元如下:

[Unit]
Description=Demo Containers

[email protected]
[email protected]

[Service]
Type=oneshot
RemainAfterExit=true

ExecStart=/usr/local/demo/launchContainers.sh
ExecStop=/usr/local/demo/shutdownContainers.sh

User=svcacct
Group=svcacct

[Install]
WantedBy=multi-user.target

答案3

我有相同的用例,老实说,令人惊讶的是,目前还没有更好的支持:它似乎是作为有限权限服务帐户运行服务的长期最佳实践与运行容器的 Podman 最佳实践的完美交集尽可能无根。

考虑到现在有多少应用程序/服务作为容器交付,有充分理由将服务容​​器作为服务用户运行似乎是一种自然的最佳实践。

我的“解决方案”就是将系统服务与 一起使用runuser,我发现这是迄今为止最可编写脚本的解决方案。我仍然必须以用户身份创建容器映像,但同样,这可以使用runuser.我没有花很多时间研究脚本编写,machinectl但据我从我的研究中可以看出,基于machinectl等的替代方案都需要以服务用户身份运行交互式 shell,这会破坏脚本编写能力。

所以基本的想法是

/usr/bin/sudo /usr/sbin/runuser -u serviceUser myCntnrBuildScript containerName
# myCntnrBuildScript does
# _mnt=$(${BUILDAH} mount $_containerImage)
# populate ${_mnt}, e.g., via cp, etc.
# ${BUILDAH} commit $_containerImage $_containerName
# etc., where the various variables are managed in my script
...

# then, in the systemd file,
ExecStart=/usr/sbin/runuser -u serviceUser -- /usr/bin/podman run --cidfile=%t/user/$(id -u serviceUser)/__cidfile__ ...
...
ExecStop=/usr/sbin/runuser -u cds-ipa -- /usr/bin/podman stop --ignore --cidfile=%t/user/$(id -u serviceUser)/__cidfile__
# in practice, `$(id -u serviceUser)` does not appear in the systemd file,
# which is generated programmatically: `$(id -u serviceUser)` appears in
# the script that generates the systemd file.

说实话,当我第一次踏上这条道路时,使用--cidfile=%t/user/$(id -u serviceUser)/__cidfile__确实是一种“公认的智慧”方法来实现这一目标。现在我了解了这些部分如何更好地协同工作,我正在考虑用%t/user/$(id -u serviceUser)更适合我的用例的东西替换,因为我可以使用ExecStartPre语句在 a 上创建一个 serviceUser 可读目录tmpfs用于此目的,并ExecStopPost使用语句来清理它,如果有必要,但该%t/user/方法有效,所以如果它没有损坏......

相关内容