⏰ 第04章 · Type/依赖/WantedBy
Type=simple/After/WantedBy
Type、依赖与 WantedBy¶
前几章你会看到 Type=simple、After=mysql.service、WantedBy=multi-user.target 这三个短语反复出现。这一章把它们彻底讲透:systemd 怎么判断"启动完成"、怎么排启动顺序、怎么表达依赖关系、以及"开机自启"到底是怎么通过 WantedBy 实现的。
Type:systemd 怎么判断"启动完成"¶
Type= 告诉 systemd:服务到什么程度算"成功启动",只有它认定启动完成,才会接着启动排在 After= 后面的服务。取值如下:
| Type | 启动完成判定 | 适用场景 |
|---|---|---|
simple |
主命令一 fork 即认为完成 | 前台常驻进程(node、python、大多数 web 服务)最常见 |
exec |
主命令成功 exec 才算完成 | 同 simple,但在 exec 失败时能报更准的错误 |
forking |
主进程 fork 出守护子进程后才算完成 | 传统 do-while 型守护进程(传给 pidfile) |
oneshot |
命令执行完一次性任务即完成 | ExecStart 跑完就结束的脚本,如初始化、备份 |
notify |
程序显式发 READY=1 通知 |
支持 sd_notify 的服务 |
dbus |
在 DBus 上注册了名字才完成 | DBus 服务 |
idle |
无明显 I/O 时启动,避免抢资源 | 少量用途 |
易错 一个最常见的问题:用了
Type=forking却没配PIDFile=,导致 systemd 找不到子进程 PID,status里出现 "no main process" 或误判active其实已死。对node server.js、python app.py这类前台进程,请用Type=simple,不要用forking。
[Service]
Type=simple
ExecStart=/usr/local/bin/node server.js
提示 判别口诀:前台直接跑的一律
simple;会自己fork变成后台守护进程的老式程序才用forking;跑完就退的脚本用oneshot。
After / Before:启动顺序,而非依赖¶
After=a.service 表示"等 a 启动完成,再启动我"。注意它管的是顺序,不是"必须有 a"。
[Unit]
After=network.target mysql.service
这台机器上 bangdream.service 的 After=network.target mysql.service,意思是:先等网络就绪、先等 MySQL 起来,再启动 API。——因为 API 启动时要连数据库,数据库没好它必然报错。
Before= 则相反:在某个 unit 之前先启动自己。After/Before 常成对使用规划一棵启动树。
Wants / Requires / BindsTo:真正的依赖¶
顺序之外,还有三类依赖:
| 字段 | 强度 | 含义 |
|---|---|---|
Wants= |
弱 | 尽量拉起依赖;依赖失败不阻止自己启动 |
Requires= |
强 | 依赖没起动,自己也不起;依赖失败自己会停 |
BindsTo= |
最强 | 依赖停止,自己跟着停(绑定关系) |
[Unit]
Requires=mysql.service ; 强依赖
Wants=network.target ; 弱依赖
Wants 和 Requires 都隐含着启动顺序,但可被 After= 显式覆盖。通常写法是":用 After 管顺序,用 Wants 管弱依赖"。因为 Requires 太激进——依赖一挂,自己也被拉着停,容易连锁反应。生产上更常见:
[Unit]
After=network.target mysql.service ; 排顺序
Wants=mysql.service ; 尽量带上,但不强绑
易错 别把
After当成可以省略Wants的理由:After=mysql只排顺序,不保证 MySQL 真的会启动。若 MySQL 没在启动列表里,你的服务会"等着也可能白等"。多数情况下要After+Wants配合用。
WantedBy:开机自启的实现原理¶
[Install] WantedBy=multi-user.target 是"开机自启"的开关。它的机制很朴素:systemctl enable 时会在 /etc/systemd/system/<target>.wants/ 目录里,为你的服务创建一个符号链接。
systemctl enable bangdream.service
ls -l /etc/systemd/system/multi-user.target.wants/bangdream.service
# lrwxrwxrwx ... /etc/systemd/system/multi-user.target.wants/bangdream.service -> /etc/systemd/system/bangdream.service
multi-user.target 这个"目标"本身定义了一堆 Wants= 依赖,当开机进入它时,systemd 就依 .wants 目录里的链接,把每个目标服务拉起来。
易错 为什么有时
enable报错?因为 unit 文件里没有[Install]段,或WantedBy写错了 target 名。没有[Install],systemd 不知道把它挂到哪,enable就会失败。这是新手写 unit 时最常见的坑——只写了[Service],忘了[Install]。
systemctl list-dependencies multi-user.target # 看这个 target 会拉起哪些服务
systemctl get-default # 看默认启动 target(通常就是 multi-user)
xray.service 的额外:权限与资源限制¶
xray.service 是这台机器上最讲究的一个,除了基础三段,还加了权限收紧设置:
[Service]
User=nobody
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE ; 只授予两类能力
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true ; 禁止提权
ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.json
Restart=on-failure
RestartPreventExitStatus=23 ; 退出码 23 不触发重启
LimitNPROC=10000
LimitNOFILE=1000000
RuntimeDirectory=xray
RuntimeDirectoryMode=0755
User=nobody用最低权限用户跑,即使被攻破也影响有限;CapabilityBoundingSet只给它两条网络类能力(管理网卡、绑定低端口),其余能力全没收;NoNewPrivileges=true防止它setuid提权;LimitNPROC/LimitNOFILE放开进程/文件描述符上限,因为反代要扛大量连接;RuntimeDirectory让它能在/run/xray建临时目录。
这背后是同一个思想:程序需要的权限尽量小,崩溃的重启策略尽量智能。on-failure 配合 RestartPreventExitStatus=23,避免"明知不该重启的退出码"也进入死循环。
一个完整的依赖链案例¶
假设你的服务需要"先 MySQL、再 Redis、最后自己",且都弱依赖:
[Unit]
Description=My stacked service
After=network.target mysql.service redis.service
Wants=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/my_app
Restart=on-failure
[Install]
WantedBy=multi-user.target
启动顺序:network → mysql → redis → my_app。任何一个在 After 里排在前面、又没能起来,mys_app 的启动顺序会被延后(弱依赖会继续,强依赖则等待失败)。
systemctl list-dependencies my_app.service # 看这条服务的依赖树
易错点¶
After是顺序不是依赖:想"必须有它",要Requires/BindsTo,想"尽量带上"用Wants。- 忘写
[Install]:enable会报错,开机自启失效。一定要有[Install] WantedBy=multi-user.target。 - 前台程序用
simple:forking不给PIDFile会误判状态,新手坑。 Requires太激进:依赖一挂自己跟着停,容易连锁;默认用Wants。enable后记得systemctl start:enable只配自启不立即启动,两步都要做。
相关命令¶
systemctl list-dependencies(依赖树)、systemctl get-default/set-default(默认 target)、systemctl cat(看单元定义)、business 判断 systemctl show -p After/Requires/WantedBy。