systemd 教程 / Unit 文件基础
Unit 文件基础
一、Unit 文件格式
systemd Unit 文件采用类似 INI 的格式,由多个段(section)组成:
# 注释以 # 开头
[Unit]
# Unit 段:通用元数据
[Service]
# 服务特定配置(仅适用于 .service 类型)
[Install]
# 安装指令(用于 enable/disable 操作)
💡 提示:Unit 文件的注释和空行会被忽略。行尾的反斜杠 \ 用于续行。
二、[Unit] 段详解
[Unit] 段是所有 Unit 类型通用的元数据段。
2.1 核心指令
| 指令 | 说明 | 示例 |
|---|---|---|
Description | Unit 描述(显示在 status 中) | Description=OpenSSH server |
Documentation | 文档链接 | Documentation=man:sshd(8) |
After | 本 Unit 在哪些 Unit 之后启动 | After=network.target |
Before | 本 Unit 在哪些 Unit 之前启动 | Before=nginx.service |
Requires | 强依赖:依赖项失败则本 Unit 也失败 | Requires=network.target |
Wants | 弱依赖:依赖项失败不影响本 Unit | Wants=sshd.service |
Conflicts | 冲突:不能与列出的 Unit 同时运行 | Conflicts=iptables.service |
BindsTo | 比 Requires 更强:依赖项停止则本 Unit 也停止 | BindsTo=docker.socket |
PartOf | 级联操作:依赖项 restart/stop 时联动 | PartOf=nginx.service |
Condition... | 启动条件检查 | ConditionPathExists=/etc/my.conf |
Assert... | 断言检查(失败则进入 failed 状态) | AssertPathIsDirectory=/data |
2.2 依赖关系详解
Requires ──── 强依赖(失败则本服务也失败)
│
├── Requisite ── 启动前检查(未运行则失败,不自动启动)
│
└── BindsTo ──── 最强绑定(停止则本服务也停止)
Wants ──────── 弱依赖(失败不影响本服务)
After/Before ── 顺序依赖(仅控制启动顺序,不控制依赖)
实际示例:
[Unit]
Description=My Web Application
Documentation=https://myapp.io/docs
After=network.target postgresql.service
Requires=postgresql.service
Wants=redis.service
Conflicts=apache.service
这段配置的含义:
- 在网络和 PostgreSQL 之后启动
- 必须有 PostgreSQL(没有则启动失败)
- 希望有 Redis(没有也可以正常运行)
- 与 Apache 冲突(不能同时运行)
⚠️ 注意:After 只控制启动顺序,不建立依赖关系。Requires 只建立依赖关系,不控制顺序。通常需要 Requires + After 组合使用。
2.3 依赖关系的实际影响
| 指令 | 自动启动? | 失败影响 | 停止联动 |
|---|---|---|---|
Requires | ❌ 需配合 After | 本服务也失败 | ❌ |
Requires + After | ✅ 自动启动 | 本服务也失败 | ❌ |
BindsTo | ❌ 需配合 After | 本服务也失败 | ✅ 依赖停止则停止 |
Wants | ✅ 自动尝试启动 | 无影响 | ❌ |
PartOf | ❌ | 无影响 | ✅ 依赖 restart 则联动 |
三、条件检查(Condition)
Condition 指令用于在启动前检查系统状态,不满足条件时 Unit 默认跳过(不进入 failed 状态):
| 条件指令 | 说明 |
|---|---|
ConditionPathExists=/path | 文件/目录存在 |
ConditionPathIsDirectory=/path | 路径是目录 |
ConditionPathIsSymbolicLink=/path | 路径是符号链接 |
ConditionFileNotEmpty=/path | 文件存在且非空 |
ConditionKernelCommandLine=opt | 内核命令行包含参数 |
ConditionVirtualization= | 虚拟化环境(vm/container/physical) |
ConditionHost= | 主机名匹配 |
ConditionACPower= | 是否连接电源 |
ConditionMemory= | 最小内存(如 4G) |
ConditionCPUs= | 最少 CPU 数量 |
ConditionSecurity= | 安全模块(selinux/apparmor) |
示例:
[Unit]
Description=GPU Compute Service
# 仅在物理机上运行,不适用于容器
ConditionVirtualization=physical
# 需要至少 16GB 内存
ConditionMemory=16G
# 需要存在 CUDA 设备
ConditionPathExists=/dev/nvidia0
💡 提示:Condition 检查失败时 Unit 会被静默跳过。如果需要检查失败时进入 failed 状态,使用 Assert 前缀(如 AssertPathExists=)。
四、Unit 文件语法检查
# 验证单个 Unit 文件语法
systemd-analyze verify /etc/systemd/system/myapp.service
# 验证所有已知 Unit 文件
systemd-analyze verify
# 检查 Unit 配置的安全性(安全评分 0-10)
systemd-analyze security myapp.service
systemd-analyze security 输出示例:
NAME DESCRIPTION EXPOSURE
✗ PrivateNetwork= Service has access to the host's network 0.5
✗ User=/DynamicUser= Service runs as root user 0.4
✗ DeviceAllow= Service has no device ACL 0.2
✗ MemoryDenyWriteExecute= Service may create writable executable memory mappings 0.2
✗ RestrictAddressFamilies= Service may allocate sockets of all address families 0.2
...
→ Overall exposure level for myapp.service: 9.6 UNSAFE 😨
五、模板单元(Template Unit)
5.1 基本概念
模板单元使用 @ 符号实现一个 Unit 文件管理多个实例:
# 模板文件名格式
# /etc/systemd/system/[email protected]
# 实例化
systemctl start [email protected]
systemctl start [email protected]
5.2 模板示例
创建模板文件 /etc/systemd/system/[email protected]:
[Unit]
Description=Web Application Instance %i
After=network.target
[Service]
Type=simple
User=www-data
ExecStart=/opt/webapp/server --port %i --config /etc/webapp/%i.conf
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
使用方式:
# 启动不同端口的实例
systemctl start [email protected]
systemctl start [email protected]
# 查看状态
systemctl status [email protected]
# 设为开机自启
systemctl enable [email protected]
5.3 模板替换符
| 符号 | 含义 |
|---|---|
%i | 实例名称(小写) |
%I | 实例名称(未转义) |
%f | 实例名称(文件系统路径格式) |
%p | Unit 名称前缀(@ 之前的部分) |
%P | Unit 名称前缀(未转义) |
%h | 用户主目录(用户级 Unit) |
%u | 当前用户名 |
六、Drop-in 覆盖机制
6.1 为什么需要 Drop-in
直接编辑 /usr/lib/systemd/system/ 中的文件会在软件包更新时被覆盖。Drop-in 机制允许在不修改原始文件的情况下覆盖配置。
6.2 Drop-in 目录结构
/etc/systemd/system/
├── nginx.service # 完全覆盖(不推荐)
└── nginx.service.d/
├── override.conf # Drop-in 文件(推荐)
└── custom-logging.conf # 可以有多个 Drop-in
6.3 创建 Drop-in 文件
# 方法一:使用 systemctl edit(推荐)
systemctl edit nginx.service
这会创建目录 /etc/systemd/system/nginx.service.d/ 并打开编辑器,输入:
[Service]
# 覆盖启动命令的参数
Environment="NGINX_OPTS=--debug"
# 追加额外的指令
ExecStartPost=/usr/local/bin/notify-deploy.sh
# 方法二:手动创建
mkdir -p /etc/systemd/system/nginx.service.d/
cat > /etc/systemd/system/nginx.service.d/override.conf << 'EOF'
[Service]
Environment="NGINX_OPTS=--debug"
EOF
# 重载配置
systemctl daemon-reload
# 验证 Drop-in 已生效
systemctl cat nginx.service
6.4 覆盖规则
| 动作 | 语法 |
|---|---|
| 覆盖已有值 | 直接写同名指令(值替换) |
| 追加列表值 | ExecStart= 清空后重写,或使用 ExecStartPre= 等追加 |
| 清空列表 | ExecStart=(空值) |
| 设置为空值 | Environment= |
⚠️ 注意:ExecStart 等列表指令不支持简单的追加,需要先清空再重写。例如要修改启动命令:
[Service]
ExecStart=
ExecStart=/usr/bin/nginx -c /etc/nginx/custom.conf
6.5 查看最终生效配置
# 显示最终合并后的配置
systemctl show nginx.service
# 显示特定属性
systemctl show nginx.service -p ExecStart
# 查看配置来源(包含 Drop-in)
systemctl cat nginx.service
七、Unit 文件重载
每次修改 Unit 文件或 Drop-in 后,必须通知 systemd 重新加载:
# 重新加载所有 Unit 文件(推荐)
systemctl daemon-reload
# 重新加载后,如果需要,重启服务
systemctl restart myapp.service
💡 提示:daemon-reload 只重新加载 Unit 文件定义,不会重启正在运行的服务。修改配置后需要手动 restart 或 reload 服务。
⚠️ 注意:daemon-reload 会重新评估所有 Unit 的依赖关系,可能影响正在等待其他 Unit 的启动队列。生产环境中建议在维护窗口操作。
八、systemctl edit 命令详解
# 编辑(创建 Drop-in 覆盖)
systemctl edit nginx.service
# 编辑(完全覆盖整个文件,原始文件仍保留)
systemctl edit --full nginx.service
# 创建新的 Unit 文件
systemctl edit --force --full myapp.service
# 查看编辑结果
systemctl cat nginx.service
| 选项 | 说明 |
|---|---|
--full | 编辑完整文件而非 Drop-in |
--force | 文件不存在时创建新文件 |
--runtime | 编辑运行时路径(重启后丢失) |
--stdin | 从 stdin 读取配置 |
示例:从 stdin 创建 Unit
cat << 'EOF' | systemctl edit --force --full myapp.service --stdin
[Unit]
Description=My Application
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
九、生产场景
场景 1:自定义 Nginx 启动参数
# 不修改原始文件,通过 Drop-in 添加模块
systemctl edit nginx.service
[Service]
ExecStart=
ExecStart=/usr/sbin/nginx -g 'daemon off; master_process on;' -c /etc/nginx/nginx-custom.conf
LimitNOFILE=65535
场景 2:为服务添加环境变量
# 创建环境文件
cat > /etc/myapp/env << 'EOF'
DATABASE_URL=postgresql://user:pass@localhost/myapp
REDIS_URL=redis://localhost:6379
LOG_LEVEL=info
EOF
# 创建 Drop-in
systemctl edit myapp.service
[Service]
EnvironmentFile=/etc/myapp/env
场景 3:限制服务的重启频率
systemctl edit myapp.service
[Service]
# 10 秒内最多重启 3 次,超过则进入 failed 状态
StartLimitBurst=3
StartLimitIntervalSec=10
Restart=on-failure
RestartSec=5
十、命令速查表
| 操作 | 命令 |
|---|---|
| 查看 Unit 配置 | systemctl cat <unit> |
| 编辑 Unit | systemctl edit <unit> |
| 完全编辑 | systemctl edit --full <unit> |
| 重载 Unit 文件 | systemctl daemon-reload |
| 语法检查 | systemd-analyze verify |
| 安全分析 | systemd-analyze security <unit> |
| 查看 Unit 属性 | systemctl show <unit> |
| 查看依赖 | systemctl list-dependencies <unit> |