Podman 套接字激活

套接字激活的概念机制是通过systemd创建套接字(例如TCP、UDP或Unix套接字)来实现的。当客户端连接到套接字时,systemd会立即启动为该套接字配置的systemd服务。新启动的程序会继承套接字的文件描述符,从而能够接受传入连接(即执行系统调用accept())。此描述对应于systemd套接字配置的默认值Accept=no,该设置允许服务接受套接字连接。

套接字激活的概念机制是通过systemd创建套接字(例如TCP、UDP或Unix套接字)来实现的。当客户端连接到套接字时,systemd会立即启动为该套接字配置的systemd服务。新启动的程序会继承套接字的文件描述符,从而能够接受传入连接(即执行系统调用accept())。此描述对应于systemd套接字配置的默认值Accept=no,该设置允许服务接受套接字连接。

Podman支持两种形式的套接字激活:

  • API服务的套接字激活

  • 容器的套接字激活

API服务的套接字激活

架构如下所示:

Podman 套接字激活

在 Fedora 系统中,文件 /usr/lib/systemd/user/podman.socket 定义了适用于无特权用户的 Podman API 套接字:

$ cat /usr/lib/systemd/user/podman.socket
[Unit]
Description=Podman API Socket
Documentation=man:podman-system-service(1)

[Socket]
ListenStream=%t/podman/podman.sock
SocketMode=0660

[Install]
WantedBy=sockets.target

该套接字配置为 Unix 套接字,可通过以下方式启动:

$ systemctl --user start podman.socket
$ ls $XDG_RUNTIME_DIR/podman/podman.sock
/run/user/1000/podman/podman.sock
$

该接口后续可供需要Docker兼容API的工具(例如 docker-compose)使用:

$ export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
$ docker-compose up

当 docker-compose 或任何其他客户端连接到 UNIX 套接字 $XDG_RUNTIME_DIR/podman/podman.sock 时,podman.service 服务将被启动。请参阅文件 /usr/lib/systemd/user/podman.service 中的服务定义。

容器的套接字激活

自版本 3.4.0 起,Podman 支持容器的套接字激活,即向容器传递一个套接字激活的套接字。得益于 Podman 的 fork/exec 模型,该套接字将首先被 conmon 继承,随后由 OCI 运行时继承,最终由容器继承,如下图所示:

Podman 套接字激活

此类套接字激活机制可用于由容器单元文件生成的 systemd 服务(参见 podman-systemd.unit(5))(注:quadlet 需使用 cgroup v2)或通过命令podman generate systemd 创建的服务。容器本身也必须支持套接字激活。并非所有软件守护进程都支持套接字激活,但该技术正日益普及。例如 Apache HTTP 服务器、MariaDB、DBUS、PipeWire、Gunicorn、CUPS 均支持套接字激活。

示例:systemd 服务中通过套接字激活的回显服务器容器

此示例演示如何在 systemd 用户服务中运行套接字激活的回显服务器 socket-activate-echo。需使用 Podman 4.4.0 及以上版本。

为普通用户启用残留进程:

$ loginctl enable-linger $USER

该命令对您启用的 systemd 用户单元具有以下效果:

  • 重启后单元会自动启动

  • 注销后单元不会自动停止

创建目录:

$ mkdir -p ~/.config/systemd/user
$ mkdir -p ~/.config/containers/systemd

创建文件 ~/.config/containers/systemd/echo.container,内容如下:

[Unit]
Description=Example echo service
Requires=echo.socket
After=echo.socket

[Container]
Image=ghcr.io/eriksjolund/socket-activate-echo
Network=none

[Install]
WantedBy=default.target

该文件遵循 podman-systemd.unit 中描述的语法。

[Install] 部分为可选项。若删除最后两行,重启后 echo.service将不会自动启动。取而代之的是,当首个客户端连接套接字时 echo.service 才会启动。

Network=none 配置行可选。该设置通过禁用容器网络连接增强安全性。但容器仍可向互联网提供服务,因为 Network=none 对已激活的套接字无效。

套接字激活服务还需配置 systemd 套接字单元。请创建文件 ~/.config/systemd/user/echo.socket,用于定义容器应使用的套接字。

[Unit]
Description=Example echo socket

[Socket]
ListenStream=127.0.0.1:3000
ListenDatagram=127.0.0.1:3000
ListenStream=[::1]:3000
ListenDatagram=[::1]:3000
ListenStream=%h/echo_stream_sock

# VMADDR_CID_ANY (-1U) = 2^32 -1 = 4294967295
# See "man vsock"
ListenStream=vsock:4294967295:3000

[Install]
WantedBy=sockets.target

%h 是 systemd 的指定符,展开后表示用户的 home 目录。

编辑完单元文件后,systemd 需要重新加载其配置。

$ systemctl --user daemon-reload

在重新加载配置时,systemd 会通过执行单元生成器 /usr/lib/systemd/system-generators/podman-system-generator,从文件 ~/.config/containers/systemd/echo.container 生成单元 echo.service。

可选操作:查看生成的 echo.service 文件,可了解将要执行的 podman run 命令。

$ systemctl --user cat echo.service

配置 systemd,使其在重启后自动启动 echo.socket。

$ systemctl --user enable echo.socket

提前拉取容器镜像:

$ podman pull ghcr.io/eriksjolund/socket-activate-echo

启动套接字单元:

$ systemctl --user start echo.socket

使用程序socat测试回显服务器:

$ echo hello | socat -t 30 - tcp4:127.0.0.1:3000
hello
$ echo hello | socat -t 30 - tcp6:[::1]:3000
hello
$ echo hello | socat -t 30 - udp4:127.0.0.1:3000
hello
$ echo hello | socat -t 30 - udp6:[::1]:3000
hello
$ echo hello | socat -t 30 - unix:$HOME/echo_stream_sock
hello
$ echo hello | socat -t 30 - VSOCK-CONNECT:1:3000
hello

选项 -t 30 将 socat 的超时时间配置为 30 秒,该超时时间适用于 socat 从套接字读取数据并等待文件结束标志 (EOF) 的场景。由于容器镜像已完成拉取,如此长的超时时间实则并无必要。

回显服务器运行正常。当接收到文本 “hello” 后,它会返回 “hello” 作为响应。

示例:使用 systemd-socket-activate 通过套接字激活 Apache HTTP 服务器

要测试套接字激活功能,除了创建 systemd 服务外,还可以使用命令行工具 systemd-socket-activate。

现在为 Apache HTTP 服务器构建一个容器镜像,配置其在8080端口支持套接字激活。

创建新目录 ctr,并在 ctr/ 目录下创建文件 Containerfile,内容如下:

FROM docker.io/library/fedora
RUN dnf -y update && dnf install -y httpd && dnf clean all
RUN sed -i "s/Listen 80/Listen 127.0.0.1:8080/g" /etc/httpd/conf/httpd.conf
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]

构建容器镜像:

$ podman build -t socket-activate-httpd ctr

在一个 shell 中,启动 systemd-socket-activate。

$ systemd-socket-activate -l 8080 podman run --rm --network=none localhost/socket-activate-httpd

TCP 端口号 8080 作为选项传递给 systemd-socket-activate。podman run 命令的 --publish (-p) 选项未被使用。

在另一个终端中,从 localhost:8080 获取网页:

$ curl -s localhost:8080 | head -6
<!doctype html>
<html>
  <head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>Test Page for the HTTP Server on Fedora</title>
$

使用 --network=none 禁用网络

如果容器仅需通过套接字激活的套接字进行通信,可通过向 podman run 传递 --network=none 参数禁用网络功能。此举能提升安全性,因为容器将以更低的权限运行。

通过 socket-activated 套接字实现原生网络性能

使用无 root 权限 Podman 时,网络流量通常通过 slirp4netns 传输。这会带来性能损失。幸运的是,通过套接字激活套接字进行的通信无需经过slirp4netns,因此其性能表现与主机上的常规网络相同。

启动 socket-activated 服务

首次建立连接时会出现延迟,因为容器需要启动。为最大限度减少此延迟,建议在调用 podman run 时添加 --pull=never 参数,并提前拉取容器镜像。此外,服务无需等待首个客户端连接触发启动,也可通过系统命令显式启动(systemctl --user start echo.service)。

停止 socket-activated 服务

某些服务会运行一条命令(由systemd指令ExecStart配置),该命令在闲置一段时间后退出。根据服务的重启配置(systemd指令Restart),此时服务可能被停止。例如podman.service在闲置一段时间后会停止运行。当下次客户端连接到套接字时,该服务将重新启动。

  

人永远是要学习的。死的时候,才是毕业的时候。 —— 萧楚女
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号