Tao
Tao

Podman Quadlet:一种使用 systemd 运行容器的更简单方法

Podman Quadlet 是一项功能,可让您使用简单的文本文件管理容器。您可以将 Quadlet 视为一个翻译器。您创建一个简单的(以 .container.volume 结尾的)文件来描述您想运行的内容。

当您的系统启动或您运行 systemctl daemon-reload 时,systemd 中一个叫做"生成器"(generator) 的部分会运行。Podman 的生成器 (podman-system-generator) 会找到您的简单文件,并自动将它们翻译成 systemd 能够读取的复杂 .service 文件。

这很强大,因为您不需要自己编写复杂的服务文件。您只需描述容器,Quadlet 就会处理剩下的事情。

在 Quadlet 出现之前,将容器作为服务运行的主要方式是使用 podman generate systemd 命令。这种旧方法有一个大问题:您必须首先手动运行一个容器。然后,您运行命令,基于该正在运行的容器创建一个 .service 文件。您必须将此文件复制到 systemd 文件夹中。这个命令创建的服务文件是"脆弱的",意味着它很容易损坏。

当您更新 Podman(例如,从版本 4 更新到 5)时,命令或最佳实践可能会发生变化。您旧的、静态的 .service 文件不会知道这些变化,导致您的服务在更新后失败。您必须手动重新生成所有服务文件来修复它们。

Quadlet 自动解决了这个问题。因为您的简单 .container 文件在每次运行 systemctl daemon-reload 时都会被"翻译",所以服务文件就像每次都被重建一样。当您更新 Podman 时,翻译器(生成器)也会被更新。在下次重载时,它会自动使用适用于您 Podman 版本的最新、正确的设置来创建新的、正确的服务文件,而您无需执行任何操作。

Quadlet 改变了您管理容器的方式。您不再需要告诉 Podman 如何运行容器(通过一个冗长、复杂的命令),您只需告诉它您想要什么。这是一种"声明式"模型,类似于 Kubernetes 或 Docker Compose 文件。

您编写一个简单的文件:

ini

Image=docker.io/nginxinc/nginx-unprivileged
PublishPort=8080:80

Quadlet 会为您将其翻译成复杂的 systemd 服务文件,其中可能包含如下长命令:

text

ExecStart=/usr/bin/podman run --name=systemd-%N --cidfile=%t/%N.cid --replace --rm --log-driver passthrough --cgroups=split --sdnotify=conmon -d...

这使您的容器定义更容易编写、阅读和管理。

Quadlet 教会您系统的主要服务管理器 (systemd) 如何理解和管理容器,就像它们是常规系统服务一样。您可以使用相同的命令 (systemctl, journalctl) 和依赖规则 (After=, Wants=) 来管理您的容器,就像管理系统上的其他任何东西一样。

Quadlet 流程由 systemd 启动。当您运行 systemctl daemon-reload(作为 root 或使用 --user 标志)时,systemd 会运行它能找到的所有生成器程序,包括 podman-system-generator

该生成器执行以下步骤:

  1. 扫描:它会查找特定文件夹(见 2.2 节)中以 .container, .volume, .network, .pod, .kube, .build, .image.artifact 结尾的文件。
  2. 解析:它读取您的简单文件(例如 myapp.container),记录容器设置(如 [Container])和任何 systemd 设置(如 [Unit][Install])。
  3. 生成:它将您的文件翻译成一个完整的 systemd .service 文件(例如 myapp.service),并将其保存在一个临时的生成器目录中(例如 /run/systemd/generator/~/.config/systemd/user/generator/)。
  4. 加载:systemd 随后会从该临时目录中读取新的 myapp.service 并加载它,就像加载任何其他服务文件一样。

您存放 Quadlet 文件的文件夹非常重要。它决定了容器是以系统的 “root” 用户(rootful)还是以您的普通用户(rootless)身份运行。系统会按顺序检查这些路径;在较高优先级路径中的文件将覆盖在较低优先级路径中的同名文件。

范围 路径 优先级 目的
Rootful (有根模式) /run/containers/systemd/ 1 临时文件,适合测试
/etc/containers/systemd/ 2 系统范围服务的主要位置
/usr/share/containers/systemd/ 3 软件随附的默认服务
Rootless (无根模式) 用户 $XDG_RUNTIME_DIR/containers/systemd/ 1 用户的临时文件,适合测试
$XDG_CONFIG_HOME/containers/systemd/ (或 ~/.config/containers/systemd/) 2 用户自定义服务的主要位置
/etc/containers/systemd/users/$(UID) 3 管理员为特定用户定义的服务
/etc/containers/systemd/users/ 4 管理员为任何用户定义的服务

当您以普通用户(rootless)身份运行容器时,要使其 7x24 小时运行,您必须执行一个关键的设置步骤。默认情况下,当您注销(例如,关闭 SSH 会话)时,systemd 会停止您用户的所有服务。要使您的服务"linger"(驻留)或在您注销后保持运行,您必须运行一次此命令:

bash

sudo loginctl enable-linger <username>

这会告诉 systemd 保持您用户的服务持续运行,即使您没有登录。

以下是使用 Quadlet 将容器部署为 rootless 服务的典型流程:

  1. 安装 Podman:确保您安装了 Podman 4.4 或更高版本。
  2. 启用 Linger (如果是 rootless 模式):运行 sudo loginctl enable-linger $USER,以便您的服务在您注销后仍能运行。
  3. 创建目录:为您的用户 Quadlet 文件创建文件夹:mkdir -p ~/.config/containers/systemd
  4. 创建 Quadlet 文件:编写您的服务文件(例如 nano ~/.config/containers/systemd/myapp.container)。确保包含一个 [Install] 部分,使其在启动时启动。
  5. 重载守护进程:运行 systemctl --user daemon-reload。这是最重要的一步。它告诉 systemd 运行 Podman 生成器,该生成器会找到您的文件并创建真正的 myapp.service
  6. 启动服务:您现在可以启动您的服务:systemctl --user start myapp.service
  7. 开机启动 (自动):您不需要运行 systemctl --user enable。如果您的文件包含 [Install] 部分(如 WantedBy=default.target),则 daemon-reload 步骤(第 5 步)会自动为您启用它。
  8. 管理:您的容器现在是一个原生服务。使用 systemctl --user status myapp.service 检查其状态,并使用 journalctl --user -u myapp.service 查看其日志。

podman generate systemd 命令现已"弃用",这意味着它已过时且不再被推荐。所有新开发都集中在 Quadlet 上。

以下是 Quadlet 更好的原因:

  • 工作流generate systemd 是手动的、多步骤的过程(运行容器、生成文件、复制文件)。Quadlet 是声明式的(创建文件、重载)。
  • 维护性generate systemd 创建的静态文件会在 Podman 更新时损坏。Quadlet 的生成器模型意味着您的服务是"自我修复"的,并且始终使用适用于您的 Podman 版本的正确、最新的设置。
  • 简洁性:Quadlet 文件干净且易于阅读。生成的 .service 文件是"一堵文本墙",充满了难以理解或安全更改的复杂命令。

人们经常问,既然已经有了 Docker Compose(或 podman-compose),为什么还需要 Quadlet。它们是为不同的工作而构建的。

Docker Compose / podman-compose:这些工具非常适合本地开发。它们可以轻松启动 (docker compose up) 和停止 (docker compose down) 一个完整的应用环境以进行测试。但它们不适合运行生产服务。要做到这一点,您必须将 docker compose 命令本身包装在一个 systemd 服务中,这很笨拙。此外,podman-compose 是一个单独的、第三方的工具,并不是 Podman 的主要关注点。

Quadlet:该工具专为将容器作为生产系统服务运行而设计。它的"编排工具"就是 systemd。这种与 systemd 的直接集成是 Quadlet 最大的优势。Quadlet 服务可以使用标准的 systemd 规则(如 After=Requires=Wants=)来创建依赖关系,不仅可以依赖其他容器,还可以依赖主机上的任何其他服务。例如,您可以将一个容器配置为等待 After=nfs-mounts.targetRequires=my-native-database.service。Docker Compose 对主机的服务一无所知,无法做到这一点。这使得 Quadlet 成为单节点设备、边缘设备以及任何需要容器按特定顺序与系统上其他进程一起启动的系统的更好、更可靠的选择。

功能 Quadlet podman generate systemd Docker / podman-compose
主要用途 生产系统服务 (旧) 一次性服务生成 本地开发
模型 声明式 (“做什么”) 命令式 (“怎么做”) 声明式 (“做什么”)
是否保持更新? 是:daemon-reload 时自动更新 否:静态文件在 Podman 更新时损坏 不适用:独立,但不是服务
是否与 systemd 配合? 原生:使用 systemd 处理所有逻辑 部分:创建一个静态 systemd 文件 否:通过单独的进程运行
依赖管理 完整 systemd:After=, Wants= 等 完整 systemd:(如果手动编辑) 隔离的:depends_on (仅在 Compose 内部)
官方支持 是:Podman 核心功能 已弃用:仅修复错误 否:podman-compose 是第三方工具

Quadlet 为不同的工作使用不同的文件扩展名。生成器会读取所有这些文件,以构建最终的 systemd 服务。

这是最常见的文件类型。它定义了如何将单个容器作为服务运行。您可以使用 [Container] 部分来列出 podman run 选项。

一个关键特性是自动依赖。如果您的 .container 文件包含 Network=my-net.networkVolume=my-data.volume:/data 这样的行,生成器会自动将 After=my-net.serviceRequires=my-net.service 添加到最终的服务文件中。

表 2:关键的 [Container] 部分选项

描述
Image= (必需)要运行的容器镜像。
ContainerName= 设置容器名称。默认值:systemd-%N
Exec= 在容器中运行的命令(替换镜像的 CMD)。
PublishPort= 将端口发布到主机(例如 8080:80)。
Volume= 挂载主机路径或命名卷(例如 my-vol.volume:/data:z)。
Network= 附加到自定义网络(例如 my-net.network)。
Pod= 加入由 .pod 文件定义的 Podman pod(例如 my-pod.pod)。
AutoUpdate= 启用自动更新(例如 registry)。
Environment= 设置环境变量(例如 KEY=value)。
EnvironmentFile= 从文件设置环境变量。
Secret= 将 Podman 密钥挂载到容器中。
Label= 在容器上设置标签。
ReadOnly= 使容器的主文件系统只读。
User= 在容器内运行的(数字)UID。
PodmanArgs= “应急出口”,用于将额外参数直接传递给 podman run

.volume 文件定义了一个 Podman 命名卷(一个持久化存储空间)。生成器会创建一个简单的"一次性"服务,如果卷尚不存在,它会运行 podman volume create。这对于需要保存数据的应用非常重要。.container 文件可以要求此服务确保在容器启动之前存储已准备就绪。

表 3:关键的 [Volume] 部分选项

描述
VolumeName= 设置卷的名称。默认值:systemd-%N
Label= 在卷上设置标签。
Driver= 指定卷驱动程序(例如 image)。
Device= 要挂载的设备路径。
Options= 文件系统的挂载选项(例如 o=XYZ)。
User= / Group= 用作卷所有者的主机用户/组(或名称)。
Copy= 如果为 true(默认值),则在首次运行时将内容从镜像路径复制到卷中。

.network 文件定义了一个 Podman 网络。就像 .volume 文件一样,这会创建一个"一次性"服务,如果网络不存在,它会运行 podman network create。这对于多容器应用是必需的,这样它们就可以在自己的私有网络上,并使用它们的容器名称作为主机名相互通信。

表 4:关键的 [Network] 部分选项

描述
Driver= 设置网络驱动程序(例如 bridge)。
Subnet= 网络的 IP 范围(例如 192.168.30.0/24)。
Gateway= 子网的网关(例如 192.168.30.1)。
Label= 在网络上设置标签。
DisableDNS= 禁用网络的 DNS。
Internal= 将网络限制为仅内部通信。
InterfaceName= 指定在主机上创建的网络接口的名称。

在 Podman 5.x 中引入的 .pod 文件允许您创建一个 Podman pod。pod 是一组共享资源(如网络)的容器,类似于 Kubernetes Pod。此文件定义了 pod 的共享设置,例如整个组的端口。然后,各个 .container 文件可以使用 Pod=my-pod.pod 加入该 pod。生成器会自动确保 pod 服务在任何属于它的容器之前启动。

表 5:关键的 [Pod] 部分选项

描述
PodName= 设置 pod 名称。默认值:systemd-%N
PublishPort= 为整个 pod 发布端口(例如 80:80)。
Network= 将 pod 附加到自定义网络。
NetworkAlias= 为 pod 添加一个网络范围的别名。
Label= 在 pod 上设置 OCI 标签。
StopTimeout= 停止 pod 的超时时间(秒)。
ExitPolicy= 容器停止时退出 pod 的策略(例如 continue)。

这种非常强大的文件类型允许 systemd 运行在标准 Kubernetes YAML 文件中定义的应用程序。该服务运行 podman kube play 来创建 YAML 中定义的所有 pod、容器和卷。对于在单台机器上运行复杂应用,这是 podman-compose 的一个很好的替代品,让您可以使用 Kube 风格的文件。这对于开发也很好:您可以在将完全相同的文件部署到真正的 Kubernetes 集群之前,先使用 Quadlet 在本地测试 Kube YAML 文件。

表 6:关键的 [Kube] 部分选项

描述
Yaml= (必需)Kubernetes YAML 文件的路径。
Network= 将 Kube pod 附加到特定的 Podman 网络(例如 my-net.network)。
PublishPort= 覆盖或设置端口映射。
ConfigMap= 从附加文件加载 ConfigMap。
AutoUpdate= 启用容器的自动更新。
LogDriver= 设置 Kube pod 的日志驱动程序。

.build 文件定义了一个使用 podman build 构建容器镜像的服务。这是一个"一次性"服务。.container 文件可以将其镜像设置为 Image=my-app.build。这会自动创建一个依赖关系,强制 systemd 在尝试启动容器之前先运行构建服务。这对于开发或在本地拉取代码并构建镜像的边缘设备非常有用。

表 7:关键的 [Build] 部分选项

描述
Containerfile= Containerfile 或 Dockerfile 的路径。默认值:Dockerfile
Target= 设置 Containerfile 中的目标构建阶段。
BuildArg= 设置构建时变量 (--build-arg)。
IgnoreFile= 指定一个自定义的 .dockerignore 文件。

.image 文件确保从镜像仓库拉取容器镜像。这也会创建一个运行 podman pull 的"一次性"服务。这对于在启动时拉取大型镜像很有用,确保它们在您的主容器服务需要启动时已经下载完毕。

表 8:关键的 [Image] 部分选项

描述
Image= (必需)要拉取的完整镜像名称。
Policy= 拉取策略(例如 always, newer)。
AuthFile= 私有镜像仓库的身份验证文件路径。
Creds= 私有镜像仓库的凭据(例如 username:password)。
TLSVerify= 是否验证 TLS 证书(例如 true, false)。

作为 Quadlet 家族的最新成员,.artifact 文件用于管理 OCI Artifacts (工件) 的拉取。这些是镜像仓库中的项目,不一定是容器镜像,例如 WebAssembly 模块、AI 模型或配置文件。此文件会创建一个服务来使用 podman artifact pull 拉取它们。

表 9:关键的 [Artifact] 部分选项

描述
Artifact= (必需)要拉取的工件全名。
AuthFile= / Creds= 私有镜像仓库的身份验证。
DecryptionKey= 用于解密工件的密钥。
Retry= / RetryDelay= 拉取失败的重试逻辑。
TLSVerify= 是否验证 TLS 证书。

Quadlet 通过两种方式处理服务依赖关系:

  1. 简单方式 (隐式):这通过文件名实现。当您的 .container 文件说 Network=my-net.networkVolume=my-data.volume 时,Quadlet 自动将 Requires=my-net.serviceAfter=my-net.service 添加到生成的服务文件中。
  2. 手动方式 (显式):您可以在文件的 [Unit] 部分使用标准的 systemd 规则。通过添加 Requires=database.serviceAfter=database.service,您可以让您的应用容器等待另一个容器服务完全启动。

此示例以 rootless 用户身份运行单个 Nginx 容器。如果失败,它将重新启动,并在开机时自动启动。

文件:~/.config/containers/systemd/nginx.container

ini

[Unit]
Description=一个简单的 Nginx Web 服务器
# 等待文件系统和网络准备就绪
After=local-fs.target network-online.target
Wants=network-online.target

[Container]
ContainerName=nginx-www
Image=docker.io/nginxinc/nginx-unprivileged:latest
PublishPort=8080:8080
# 将主机目录挂载为网站内容
# 'ro' = 只读, 'z' = 处理 SELinux 标签
Volume=/srv/www:/usr/share/nginx/html:ro,z

# 传递给 systemd:如果失败则重启
Restart=always

[Install]
# 此部分使其在启动时默认启用
WantedBy=default.target

此示例展示了一个包含数据库和 Web 应用的完整应用程序。它使用四个文件来创建网络、卷、数据库容器和 Web 应用容器。它确保在 Web 应用启动前数据库已在运行。

文件 1:wordpress.network

目的:为容器创建一个专用网络,以便按名称进行通信。

ini

[Unit]
Description=用于 WordPress 的 Podman 网络
[Network]
Label=app=wordpress

文件 2:wordpress-db.volume

目的:为数据库文件创建一个持久化命名卷。

ini

[Unit]
Description=用于 WordPress 数据库的卷
[Volume]
Label=app=wordpress

文件 3:wordpress-db.container

目的:运行 MariaDB 数据库。由于命名约定,它自动依赖于 wordpress.networkwordpress-db.volume

ini

[Unit]
Description=WordPress 数据库容器 (MariaDB)

[Container]
ContainerName=wordpress-db
Image=docker.io/library/mariadb:10
Network=wordpress.network
Volume=wordpress-db.volume:/var/lib/mysql:z
# 密钥应通过 EnvironmentFile 或 Podman secrets 传递
Environment=MARIADB_USER=wordpress
Environment=MARIADB_DATABASE=wordpress
Environment=MARIADB_RANDOM_ROOT_PASSWORD=1
Environment=MARIADB_PASSWORD=changeme

[Install]
WantedBy=default.target

文件 4:wordpress-app.container

目的:运行 WordPress 应用程序。此文件演示了对数据库服务的手动依赖。

ini

[Unit]
Description=WordPress 应用容器
# 显式依赖:在 wordpress-db.service (来自 wordpress-db.container) 运行之前,
# 不要启动此容器。
Requires=wordpress-db.service
After=wordpress-db.service

[Container]
ContainerName=wordpress-app
Image=docker.io/library/wordpress:latest
Network=wordpress.network
PublishPort=8000:80
Environment=WORDPRESS_DB_HOST=wordpress-db
Environment=WORDPRESS_DB_USER=wordpress
Environment=WORDPRESS_DB_NAME=wordpress
Environment=WORDPRESS_DB_PASSWORD=changeme

[Install]
WantedBy=default.target

此设置定义了整个应用程序、其资源及其启动顺序,所有这些都由 systemd 管理。

Quadlet 为您提供了一种使用 Podman auto-update(自动更新)功能的简单方法,该功能可以拉取新镜像并自动重启您的服务。

工作原理:自动更新系统有两个部分:

  1. 一个 systemd 计时器 (Timer):Podman 包含 podman-auto-update.timer,它每天运行以检查更新。
  2. 一个标签:更新命令只检查设置了 io.containers.autoupdate 标签的容器。

Quadlet 快捷方式:Quadlet 为您提供了一个简单的键:AutoUpdate=registry,而不是让您记住那个长标签。当生成器在您的 .container 文件中看到这个键时,它会自动在最终的服务文件中添加所需的 Label=io.containers.autoupdate=registry。对于 .kube 文件,它会添加等效的 Kubernetes 注解。

要使用此功能,只需将 AutoUpdate=registry 添加到您的 .container 文件中,然后一次性启用计时器:systemctl --user enable --now podman-auto-update.timer

如果您是从 Docker Compose 或旧的 podman generate systemd 方法迁移过来的,一个名为 podlet 的辅助工具可以提供帮助。

  • podlet compose [docker-compose.yml]:此命令读取一个 docker-compose.yml 文件,并自动为您生成匹配的 Quadlet 文件(.container, .network, .volume)。
  • podlet generate container <name>:此命令会检查您已在运行的容器,并为其生成一个 .container 文件。这是 podman generate systemd 的新替代品。

在 Podman 5.x 中,Quadlet 已成为 Podman 的核心部分,并拥有自己的命令,使管理文件变得更容易:

  • podman quadlet list:列出您系统上的所有 Quadlet 文件。
  • podman quadlet print:显示 Quadlet 文件的内容。
  • podman quadlet install:将 Quadlet 文件安装到正确的系统文件夹中。
  • podman quadlet rm:删除已安装的 Quadlet 文件。

Podman Quadlet 现已成为在 Linux 机器上运行容器即服务的官方推荐方式。它是一种"声明式"(告诉它您想要什么)且可靠的解决方案,完全取代了旧的、“脆弱的” podman generate systemd 方法。

虽然它不能取代 Docker Compose 进行本地开发,但对于在单台主机上运行生产服务而言,它是一个更好的工具。它的"秘密武器"是它直接与 systemd 配合使用,让您的容器可以使用主机的原生依赖管理、日志记录和服务控制。

Podman 5.x 中添加的新文件类型(如 .pod.build.image.artifact)显示了一个明确的方向。Quadlet 正在超越仅仅运行容器的范畴。它正在成为一个用于管理单节点上整个应用生命周期的完整工具:从构建镜像、拉取数据,到运行的多容器应用。这使得 Quadlet 成为边缘计算、嵌入式系统和服务器"一体机"的理想选择。这些系统需要可靠且定义简单,但又不足以需要一个完整的 Kubernetes 集群。Quadlet 完美地填补了这一空白,为在 Linux 上运行容器化服务提供了一种强大的、自我修复的原生方式。

相关内容