首页 / 内容指南 / 当前文章

systemd 的 Condition、Assert、ExecCondition、ConditionPathExists 和 ConditionEnvironment 有什么区别?

发布于 2026-08-26 · Content Fleet 编辑部

systemd 的 Condition、Assert、ExecCondition、ConditionPathExists 和 ConditionEnvironment 有什么区别?

快速对照

ConditionPathExists=ConditionEnvironment= 不是与 Condition 并列的新机制,而是 Condition…= 家族中的具体判断类型。相同判断通常也有对应的 Assert…= 形式,例如 AssertPathExists=

Condition:不满足代表“现在不适用”

Condition…= 用于描述一个 Unit 是否适合在当前环境启动。systemd 在该 Unit 被请求启动时评估条件。若条件不满足,Unit 不会进入正常激活流程,但这通常不被视为启动作业失败。它适合表达可选能力,例如只在虚拟机中运行代理、只在某个架构启用优化服务,或仅当可选配置存在时启动辅助任务。

这种“跳过也成功”的语义非常重要。另一个 Unit 即便通过 Requires= 拉入它,单纯的条件不满足也不会像真正启动失败那样自动让依赖方失败。若业务要求依赖对象必须保持 active,需要重新评估依赖模型,例如结合 BindsTo= 与顺序关系,而不是误以为 Requires= 会把条件跳过变成硬失败。

Condition 只在启动请求时检查,不会持续监视环境。某个文件在服务启动后被删除,不会因为 ConditionPathExists= 自动停止服务;文件稍后出现,也不会自动触发启动。需要监听路径变化时应考虑 .path Unit,而不是把 Condition 当作文件监控器。

Assert:不满足代表“启动要求遭到破坏”

Assert…= 使用与对应 Condition 类似的检查方式,但失败结果不同。断言不满足会导致 Unit 的启动作业失败。它适合表达本来必须成立、否则应让运维系统明确报警的先决条件,例如关键挂载点必须存在、指定路径必须可写,或者系统架构必须属于受支持范围。

Condition 与 Assert 的选择可以用一句话判断:条件为假时,如果“这个 Unit 在本机不适用”是正常状态,用 Condition;如果“部署或主机状态不符合要求”是故障,用 Assert。

不要为了让仪表盘更“干净”而把所有 Assert 改成 Condition。这样虽然减少 failed 状态,却可能把错误部署隐藏为正常跳过。相反,把可选组件写成 Assert 会让不具备该功能的主机持续报警。

ExecCondition:运行命令后按退出状态分流

ExecCondition= 写在 Service 的 [Service] 段,语法与 ExecStart= 类似,并在 ExecStartPre=ExecStart= 之前运行。可以配置多行,systemd 按顺序串行执行。它适合内置 Condition 无法表达、必须由程序检查的逻辑,例如校验配置语法、读取本地数据库元数据或执行一个快速的许可检查。

其退出状态分为三组:

  • 退出码 0,或命中 SuccessExitStatus=:检查通过,继续下一条命令;
  • 退出码 1–254:停止后续启动命令,Unit 不因这次检查被标记为 failed;
  • 退出码 255,或者超时、被信号终止等异常结束:Unit 启动失败。

这使 ExecCondition 同时具有命令执行与条件分流能力,但它不等同于 Unit 级 Condition。ExecCondition 已经进入 Service 的激活转换过程;非零或异常退出时会执行相应的 ExecStopPost= 清理逻辑。Unit 级 Condition 则在激活前阻止启动,不会产生完全相同的状态转换与 SuccessAction= 行为。

检查程序必须短小、确定、可重复执行。不要在 ExecCondition 中启动长期后台进程,也不要把重要初始化写进去;其命令可能执行后又因返回 1–254 而跳过主服务。需要修改系统状态的准备工作通常属于 ExecStartPre= 或专用 oneshot Unit,并应明确失败策略。

ConditionPathExists:判断路径是否存在

ConditionPathExists=/path 在 Unit 启动请求时检查指定路径。路径存在时通过,不存在时不通过。给参数加 ! 可以反转,例如:

[Unit]
ConditionPathExists=!/etc/myapp/disabled

这表示禁用标记不存在时才启动。它只判断路径是否存在,不保证该路径是普通文件、目录、可读文件或内容有效。更精确时应选择 ConditionPathIsDirectory=ConditionPathIsReadWrite=ConditionFileNotEmpty= 等对应检查。

路径检查不替代依赖和挂载顺序。如果路径由某个 Mount Unit 或准备服务创建,还需要使用 After=RequiresMountsFor= 或适当依赖来保证检查发生在正确时间。否则在并行启动中,条件可能因路径尚未出现而跳过。

ConditionFileNotEmpty:存在还不够,文件必须非空

ConditionFileNotEmpty= 用于判断普通文件存在且大小非零的场景,比 ConditionPathExists= 更严格。例如只有在凭据包或生成的配置文件已写入内容时才运行一次导入任务。

但“非空”仍不等于“内容有效”。一个只有换行符的配置文件也可能通过文件大小检查。若必须验证语法、签名、JSON 结构或证书有效期,应该使用专门校验程序配合 ExecCondition,或者由配置生成流程提供原子且可验证的就绪标志。

生产配置写入时最好先写临时文件、校验并原子重命名,避免 Condition 在半写入状态下看到一个非空但未完成的文件。

ConditionEnvironment:检查的是管理器环境

ConditionEnvironment= 可以判断 systemd 管理器环境中某变量是否存在,或是否等于指定值。它不是读取 Service 通过 Environment=EnvironmentFile= 将来传给子进程的环境,因为 Condition 在启动程序前由管理器评估。

例如:

[Unit]
ConditionEnvironment=DEPLOYMENT_TIER=staging

系统管理器的环境与交互式 Shell 环境也不是同一回事。在终端里执行 export DEPLOYMENT_TIER=staging,通常不会自动改变 PID 1 已持有的环境。因此用它做部署开关前,必须明确变量怎样进入对应 system 或 user manager,并用 systemctl show-environment 等方式验证实际来源。

如果条件依赖的是服务自身 EnvironmentFile= 中的值,应让启动程序或 ExecCondition 显式读取并验证该文件,不要假设 ConditionEnvironment 会看到它。

其他常用 Condition 类型

  • ConditionPathIsDirectory=:路径必须是目录;
  • ConditionPathIsReadWrite=:底层文件系统应可读写;
  • ConditionDirectoryNotEmpty=:目录存在且不是空目录;
  • ConditionArchitecture=:匹配运行架构;
  • ConditionVirtualization=:匹配虚拟化或容器环境;
  • ConditionHost=:匹配主机名或 machine ID;
  • ConditionKernelCommandLine=:匹配内核命令行;
  • ConditionACPower=:根据交流电源状态判断;
  • ConditionFirstBoot=:用于首次启动相关逻辑;
  • ConditionUser=ConditionGroup=:匹配运行管理器的用户或组语境。

各指令的引入版本不同。面向多种 Linux 发行版部署时,应先核对目标机 systemd --version 和该版本手册;未知指令可能被忽略并记录警告,不能假设所有新条件在旧系统上都能生效。

多个条件如何组合

默认情况下,多个普通 Condition 必须全部通过,逻辑类似 AND。前缀 ! 只反转该条判断结果。以竖线 | 为前缀可以把条件标记为 trigger 条件:存在 trigger 条件时,至少一个 trigger 条件为真,同时所有未标记 trigger 的普通条件仍必须为真。

例如:

[Unit]
ConditionArchitecture=x86-64
ConditionPathExists=|/etc/myapp/site-a.conf
ConditionPathExists=|/etc/myapp/site-b.conf

它表示架构必须是 x86-64,并且两个配置路径至少存在一个。不能把 | 理解成“与上一行做 OR”的通用表达式,也不能任意写括号。复杂布尔逻辑应拆成更清晰的 Unit,或交给经过测试的 ExecCondition 程序。

将同一 Condition 指令赋空值可以重置此前累积的该类条件。这在 drop-in 覆盖供应商 Unit 时尤其重要:重复赋值通常是追加,不是自动替换。修改后应查看 systemctl cat 合并结果,避免以为旧条件已经消失。

Condition 与依赖顺序必须分开设计

Condition 负责“检查什么”,After=Before= 负责“何时检查”,Wants=Requires= 和其他依赖负责“启动时带上谁”。它们是不同维度。

假设 app.service 只有在 /mnt/data/config.json 存在时启动,而该路径来自 mnt-data.mount。只写 ConditionPathExists= 会存在检查过早的风险。应使用 RequiresMountsFor=/mnt/data/config.json 或显式挂载依赖和顺序,让路径先准备好,再评估条件。

若条件对象会动态出现,.path Unit 可以监听路径并触发 Service。若要求某个准备任务成功后才运行主服务,可以创建独立 oneshot Service,并用 Requires=After= 建立可观察的失败链,而不是在 Condition 中隐藏复杂编排。

验证与排障方法

修改 Unit 后先运行 systemd-analyze verify 检查语法和未知指令,再执行 systemctl daemon-reload 让管理器重新读取文件。systemctl cat name.service 用于确认主文件和所有 drop-in 的最终组合。

可以使用 systemd-analyze condition 'ConditionPathExists=/path' 测试条件表达式;新版本还支持针对 Unit 检查条件,具体参数以目标机手册为准。启动后查看:

systemctl status myapp.service
systemctl show myapp.service -p ActiveState -p SubState -p Result -p ConditionResult -p AssertResult
journalctl -u myapp.service -b

不要只看 systemctl start 是否返回 0。Condition 跳过时,命令可能成功,但 Unit 仍是 inactive;这恰恰是其设计语义。自动化验收应同时检查 ActiveStateSubState、条件结果和日志,并明确“跳过是否可接受”。

常见误区

把 Condition 当作持续健康检查

Condition 只在启动请求时评估。运行期间健康检查应由 watchdog、监控探针或服务自身完成。

用 Condition 隐藏关键配置缺失

关键配置缺失属于部署错误时,应使用 Assert 或明确失败的校验流程,而不是让服务安静跳过。

认为 EnvironmentFile 会影响 ConditionEnvironment

EnvironmentFile= 用于构造服务进程环境,ConditionEnvironment 检查管理器环境,评估阶段和来源不同。

在 ExecCondition 中用复杂 Shell 管道

systemd 的命令行不是完整 Shell 语法。复杂逻辑应放入版本化、可测试的脚本或程序,并返回约定退出码。

只验证 Unit 文件,不验证运行时条件

systemd-analyze verify 能发现许多语法问题,但无法证明生产路径、管理器环境和挂载时序满足条件。仍需在目标环境触发并检查实际结果。

FAQ

1. ConditionPathExists 不满足时,systemctl start 为什么仍可能返回成功?

因为 Condition 失败表示 Unit 在当前环境不适用,systemd 跳过激活而不把该条件视为启动故障。应同时查看 Unit 状态和 ConditionResult

2. 想让缺少配置文件时明确失败,应该用什么?

可使用 AssertPathExists=;若还需要检查内容格式,则用能明确返回失败状态的 ExecCondition 或独立准备服务。

3. ExecCondition 返回 1 会把服务标记 failed 吗?

通常不会。1–254 会跳过后续启动;255、超时、信号终止等异常才会导致失败。0或命中 SuccessExitStatus= 才继续。

4. ExecCondition 和 ExecStartPre 有什么核心区别?

ExecStartPre 非零退出通常是启动失败;ExecCondition 专门把1–254解释为不失败的跳过,并保留255或异常退出作为失败。

5. ConditionPathExists 能等待文件出现吗?

不能。它只在启动时即时检查。需要等待或监听路径时,应使用 .path Unit、正确依赖顺序或专门的准备流程。

6. 多个 ConditionPathExists 默认是 AND 还是 OR?

默认全部必须通过。给相关条目前缀 | 可形成 trigger 组,要求至少一个 trigger 通过,同时普通条件仍须全部通过。

7. 为什么终端 export 的变量没有通过 ConditionEnvironment?

因为系统管理器不会自动继承之后打开的交互式 Shell 环境。应检查对应 manager 的环境,而不是只看当前终端。

8. 条件跳过后文件出现,服务会自动启动吗?

不会。需要新的启动触发;若希望文件出现即启动,应配置 .path Unit 或其他事件驱动机制。

参考来源