By Gemini

Podman Quadlet: Rclone Mount 中涉及 systemd 依赖问题实践

实践 | Practices 2025年12月11日

1. First | 序

众所周知 Podman 是 Fork-Exec 模型 (每个容器都是 Systemd 的子进程(在使用Quadlet的情况下)),所以它与 Systemd 是天作之合。 而 Docker 是 Client-Server (Daemon) 模型(容器是 Docker 守护进程的子进程),Systemd 只能管理 dockerd 这个守护进程,默认无法感知和控制单个容器的死活

现如今我们有以下 .service 环境

  1. Rclone Mount
  2. Need-RcloneMount1
  3. Need-RcloneMount2
  4. Need-RcloneMount3
🗨️
Rclone Mount 负责挂载云端硬盘到本地
🗨️
Need-RcloneMount1/2/3 依赖于 Rclone Mount 挂载的磁盘读写文件
🗨️
Need-RcloneMount1/2/3 之间没有依赖关系且可以并行启动

1.1. Objectives | 目标

  1. Rclone Mount 容器服务启动,Need-RcloneMount1/2/3 容器服务启动
  2. Rclone Mount 容器服务停止,Need-RcloneMount1/2/3 容器服务停止
  3. Rclone Mount 容器服务重启,Need-RcloneMount1/2/3 容器服务重启

2. Configuration | 配置

依据上文所示这里使用 .container 完成

跳过调试部分这里直接写成品

2.1. Rclone Mount .container

[Unit]
Description=Rclone is a command-line program to manage files on cloud storage | Rclone-Mount
After=network-online.target
# 需求1核心:启动 Rclone 时,尝试拉起这些服务
# 注意:确保这里填写的名称与实际文件名完全一致
Wants=network-online.target Need-RcloneMount1.service Need-RcloneMount2.service Need-RcloneMount3.service
# 显式声明顺序:Rclone 在这些服务之前启动
Before=Need-RcloneMount1.service Need-RcloneMount2.service Need-RcloneMount3.service

[Container]
AutoUpdate=registry
ContainerName=Rclone-Mount
AddCapability=SYS_ADMIN
PodmanArgs=--security-opt apparmor=unconfined
AddDevice=/dev/fuse
Exec=mount xxxxxxx:/xxxxxx /data/mount \
  ......
  --log-file /config/rclone/rclone.log \
  --log-level INFO
Image=docker.io/rclone/rclone
Volume=Rclone-config:/config/rclone
Volume=Rclone-data:/data:shared

[Service]
Restart=always
# 延长等待时间至 300s
TimeoutStartSec=300
# 确保挂载点可用
ExecStartPost=/bin/bash -c "while ! mountpoint -q /Rclone-Mount; do sleep 1; done"
# 确保(服务正常/异常退出亦或者未成功启用时)卸载挂载点
ExecStopPost=/usr/bin/fusermount3 -u /Rclone-Mount

[Install]
WantedBy=default.target

👩‍🏫
Wants= & Before=

在 Systemd 中, 依赖(Dependency) 和 顺序(Ordering) 是两回事。

Wants= 和 Before= 的联合使用,构建了一个 “主动拉起 + 严格排队” 的逻辑。

Systemd 会在内部生成一个任务队列,流程如下:

  1. 触发 (Wants):
    Systemd 收到启动 Rclone 的指令。因为它配置了 Wants=Need-Rclone...,Systemd 立即把 Need-Rclone1, 2, 3 也加入到“待启动列表”中。
    此时,4 个服务都准备启动。

  2. 排序 (Before):
    Systemd 检查这 4 个服务的启动顺序。它发现 Rclone 写了 Before=Need-Rclone...(或者反过来 Need-Rclone 写了 After=Rclone)。
    Systemd 判定:Rclone 必须先走,其他程序红灯等待。

  3. 执行 Rclone:
    Rclone 开始启动。

    • 关键点: ExecStartPost 脚本。Systemd 会一直卡在这里,直到脚本返回成功(也就是挂载点出现)。
  4. 放行 (Release):
    一旦 Rclone 状态变为 Active (Running),Systemd 这里的绿灯亮起。

  5. 执行依赖:
    Need-Rclone1, 2, 3 开始并行启动。

2.1.1. Ex | 拓展

Upholds=

• 官方定义: 配置维持依赖,确保持续性启动。
• 语法: 接受一个或多个以空格分隔的单元名称。
• 行为: 只要本单元处于运行状态,systemd 就会持续监控 Upholds= 列出的单元。如果发现这些单元进入了非激活或失败状态,systemd 会立即尝试重新启动它们。这提供了一种替代服务单元自身 Restart= 设置的机制。
• 互作用: Upholds= 提供了一种跨单元的“保持存活”逻辑,与服务单元的 Restart= 配置相比,它将重启决策的控制权交给了另一个单元。
• 常见误区: 这是一个相对较新的指令,可能被忽视。它与 Wants= 的区别在于 Wants= 是一次性触发,而 Upholds= 是持续性监控和触发。
• 版本: Added in version 249.

结合本文环境分析此参数:

特性 Restart=always (在子服务里) Upholds= (在父服务里)
触发条件 进程崩溃 (非正常退出) 服务处于非 Active 状态
手动 Stop 尊重你的意愿 (不会重启) 无视你的意愿 (强行重启)
适用场景 绝大多数常规服务 关键的辅助进程,必须时刻与主进程共存
控制权 用户掌握 父服务掌握

2.2. Need-RcloneMount1 .container

[Unit]
Description=Need-RcloneMount1
Wants=network-online.target
# 如果 BindsTo= 列出的任何一个单元进入非激活状态(例如被停止、崩溃或设备被拔出),本单元也会被自动停止。
# 当与 After= 结合使用时,行为会更强:它确保了只要本单元处于激活状态,其绑定的单元也必须处于激活状态。如果绑定的单元因条件检查未通过而被跳过,本单元也会被停止
After=network-online.target Rclone-Mount.service
BindsTo=Rclone-Mount.service
# 需求2 & 3 核心:绑定生命周期。当 Rclone 停止或重启时,该动作会传播给此服务
PartOf=Rclone-Mount.service

Need-RcloneMount2/3 .container 内容完全等于 Need-RcloneMount1 不过多赘述

👩‍🏫
After= & BindsTo= & PartOf=
  1. After=Rclone-Mount.service 🚦

    • 含义:顺序控制。
    • 作用:无论发生什么情况(启动或重启),当前服务必须等到 Rclone-Mount 完全启动(Active)之后才能开始启动。
    • 场景:防止容器在挂载点还没准备好时就抢跑。
  2. PartOf=Rclone-Mount.service 🏁

    • 含义:动作传播。
    • 作用:主要针对用户主动的操作。当你对 Rclone 执行 systemctl stop 或 systemctl restart 时,Systemd 会把这个命令自动传导给当前服务。
    • 场景:你重启 Rclone 来更新配置,依赖的容器也会自动跟着重启。
  3. BindsTo=Rclone-Mount.service 🎎

    • 含义:状态绑定。
    • 作用:如果 Rclone 服务进入 active 以外的任何状态(无论是被手动停止,还是意外崩溃 (Crash),或者被 OOM Kill),当前服务会立即、强制停止。
    • 区别:它比 Requires= 强硬得多。Requires 只管“启动时”依赖在不在;而 BindsTo 则是“运行时”每时每刻都在监控依赖的状态。

3. 验证

3.1. systemctl

反向查看 (谁依赖 Rclone-Mount.service 这个服务?)

➜  ~ systemctl list-dependencies Rclone-Mount.service --reverse
Rclone-Mount.service
● ├─Need-RcloneMount1.service
● ├─Need-RcloneMount2.service
● ├─Need-RcloneMount3.service
● └─multi-user.target
➜  ~

验证重启传播 (PartOf 检查)


➜  ~ systemctl show Rclone-Mount.service -p ConsistsOf
ConsistsOf=Need-RcloneMount1.service Need-RcloneMount2.service Need-RcloneMount3.service
➜  ~

3.2. bpftrace execsnoop

18:57:24.239629 26812   24269   sudo systemctl Rclone-Mount.service
18:57:24.245975 26813   26812    root             �E�H�E�HǀP
18:57:24.250041 26815   26814   systemctl restart
......
18:57:24.282244 26817   1       /usr/bin/podman rm -f    �+�M@V
18:57:24.282305 26819   1       18:57:24.282307 26818   1       /usr/bin/podman rm -f    �
                                                                                          ΊU
/usr/bin/podman rm -f    �{}��U
18:57:24.414690 26856   26846   /usr/bin/runc kill 15
18:57:24.420406 26864   26848   /usr/bin/runc kill 15
......
18:57:35.507087 27327   27325   /usr/bin/runc kill 15
......
18:57:35.513300 27337   24982   fusermount3 -u
......
18:57:35.867267 27387   1       /usr/bin/podman run Rclone-Mount --rm --device /xxx/xxx/xxx/xxx:/config/rclone apparmor=unconfined --umask --drive-pacer-burst --vfs-read-chunk-streams --vfs-cache-max-size --log-file
......
18:57:36.046080 27458   27446   rclone mount /data/mount --umask --dir-perms 200 --buffer-size 2G 24h /config/rclone/rclone.log
......
18:57:36.067743 27485   27471   mountpoint -q
......
18:57:36.070751 27488   27471   sleep 1         32
......
18:57:36.739425 27500   27473   fusermount3 -o --
......
18:57:37.072488 27502   27471   mountpoint -q
......
18:57:37.107253 27504   1       /usr/bin/podman run Need-RcloneMount1 --rm --sdnotify=conmon xxxxxx-config:/config:Z io.containers.autoupdate=registry    �k���U    �k���U
18:57:37.107320 27509   1       /usr/bin/podman run Need-RcloneMount2 --rm --sdnotify=conmon /xxx/xxxxx/xxxxxx:/media 127.0.0.1:57382:57382/udp --env  �[8*yU
18:57:37.107343 27508   1       /usr/bin/podman run Need-RcloneMount3 --rm --sdnotify=conmon /xxxxx/xxx/xxxxx:/config io.containers.autoupdate=registry --env --env

3.2.1. 验证 PartOf=

时间戳 进程/动作 原始日志片段 状态
18:57:24.250 Systemd systemctl restart 🚀 收到指令
18:57:24.282 Podman /usr/bin/podman rm -f 🛑 依赖容器被强杀
18:57:35.507 RunC /usr/bin/runc kill 15 🛑 主服务 Rclone 被杀

3.2.2. 验证 Rclone 重建

时间戳 进程/动作 原始日志片段 状态
18:57:35.513 FUSE fusermount3 -u ... 🧹 旧挂载点卸载
18:57:35.867 Podman /usr/bin/podman run Rclone... 🆕 新容器创建
18:57:36.046 Rclone rclone mount /data/mount ... 🔧 挂载命令执行

3.2.3. 验证 ExecStartPost=

时间戳 进程/动作 原始日志片段 状态
18:57:36.067 Check 1 mountpoint -q ❌ 检测失败 (无挂载)
18:57:36.070 Sleep sleep 1 ⏳ 阻塞后续流程
18:57:36.739 Mount fusermount3 -o 🔗 挂载动作发生
18:57:37.072 Check 2 mountpoint -q ✅ 检测成功

3.2.4. 验证 After= & Wants=

时间戳 进程/动作 原始日志片段 状态
18:57:37.107 Podman podman run Need-RcloneMount1... ▶️ 启动 Need-RcloneMount1
18:57:37.107 Podman podman run Need-RcloneMount2... ▶️ 启动 Need-RcloneMount2
18:57:37.107 Podman podman run Need-RcloneMount3... ▶️ 启动 Need-RcloneMount3

Thanks & Copy

System and Service Manager
podman-systemd.unit — Podman documentation
GitHub - rclone/rclone: “rsync for cloud storage” - Google Drive, S3, Dropbox, Backblaze B2, One Drive, Swift, Hubic, Wasabi, Google Cloud Storage, Azure Blob, Azure Files, Yandex Files
“rsync for cloud storage” - Google Drive, S3, Dropbox, Backblaze B2, One Drive, Swift, Hubic, Wasabi, Google Cloud Storage, Azure Blob, Azure Files, Yandex Files - rclone/rclone

标签

xzllll

三分钟热度三分钟收获