玩转 Systemd Unit 文件:进阶技巧与服务覆盖实战
简介
作为进阶系列的第一篇,本文将聚焦 Unit
文件的进阶配置,特别是覆盖机制(Drop-in
),这些内容在生产环境中非常实用,能帮助你优化服务并提升系统稳定性。如果对 Systemd
基础还不熟悉,建议先阅读基础教程。
Systemd
的 Unit
文件是服务管理的核心,通常位于 /lib/systemd/system/
(系统默认)或 /etc/systemd/system/
(自定义)。进阶配置允许你不修改原文件,而是通过覆盖方式扩展功能,避免升级时覆盖问题。接下来,我们一步步深入。
为什么不要直接修改 /usr/lib / /lib
下的 unit 文件
发行版把 package
自带的 unit
文件放在 /usr/lib/systemd/system
或 /lib/systemd/system
(不同发行版路径可能不同),这些文件在包升级时可能被覆盖。正确做法是把修改放在 /etc/systemd/system
(或者用 drop-in
),这样系统升级不会覆盖你的改动。系统会按优先级选取最终生效的配置(管理员目录优先)。
unit 文件的查找与优先级
systemd
会从多个目录加载 unit
文件与 drop-in
,优先级通常是:
/etc/systemd/system
(管理员/本地覆盖,最高)/run/systemd/system
(运行时生成的unit
)/usr/lib/systemd/system
或/lib/systemd/system
(发行版自带,最低)
drop-in
(<unit>.d/*.conf
)也会被加载并按目录顺序合并,/etc
下的 drop-in
优先于 /run
、再优于 /usr/lib
。这意味着可以只写小段配置覆盖原设置而不必复制整个文件。
Drop-in 的工作原理
Drop-in
文件位于 /etc/systemd/system/<unit-name>.d/
(例如 /etc/systemd/system/nginx.service.d/
)。
每个 Drop-in
文件(如 override.conf
)可以覆盖特定节(如 [Service]
)的指令。
Systemd
会合并原始 Unit
和所有 Drop-in
文件,按字母顺序加载(最后加载的优先)。
覆盖(Override)与 Drop-in 的实战
推荐的两种修改方式(按优先级)
最推荐(安全):用 systemctl edit <unit>
,它会在 /etc/systemd/system/<unit>.d/override.conf
创建或编辑 drop-in
。
sudo systemctl edit nginx.service
编辑器会打开,你写入例如:
[Service]
Environment="ENV=production"
ExecStartPre=/usr/bin/echo 'starting nginx'
2
3
保存后,运行:
sudo systemctl daemon-reload
sudo systemctl restart nginx
2
这样不会改动发行版文件,且便于回滚或审计。
需要完整替换:如果确实要完全替换一个 unit
(例如深度修改 ExecStart
的语义),可把它整文件复制到 /etc/systemd/system/
,编辑后 daemon-reload
。但更常见也更安全的还是用 drop-in。
覆盖行为要点
drop-in
中只写你要改变或添加的节/键;systemd
会把主文件和所有 drop-in
合并来生成最终配置。
同一个 unit
的多个 drop-in
(不同文件名)会按字典序合并,后面的可覆盖前面的。
模板单元(@ 单元)与实例化
什么是模板单元
模板单元用于同一套 unit
定义不同实例。例如 mydaemon@.service
,你可以 systemctl start mydaemon@work.service
和 systemctl start mydaemon@home.service
各自成为独立实例。
示例 mydaemon@.service
(放 /etc/systemd/system/mydaemon@.service
):
[Unit]
Description=My daemon instance %i
[Service]
ExecStart=/usr/local/bin/mydaemon --config /etc/mydaemon/%i.conf
Restart=on-failure
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
这里 %i
会被替换为实例名(work
/ home
等),通常用于文件名或参数。用 template
可以让多个实例共享相同 unit
定义。
常见定制场景与实例
场景 A:只需设环境变量或修改一个参数
使用 drop-in:
sudo systemctl edit myapp.service
写入 /etc/systemd/system/myapp.service.d/override.conf
:
[Service]
Environment="APP_ENV=prod"
EnvironmentFile=/etc/default/myapp
2
3
重载并重启:
sudo systemctl daemon-reload
sudo systemctl restart myapp
2
场景 B:替换 ExecStart(需要先清除原 ExecStart)
如果主文件有 ExecStart
,在 drop-in
中直接写另一个 ExecStart=
会被追加而非替换。要先清空原来的,再写新值:
[Service]
ExecStart=
ExecStart=/usr/local/bin/myapp --serve
2
3
空行 ExecStart=
是清除旧值的标准方法(适用于累加型字段)。
添加后置命令:使用 ExecStartPost=
执行启动后的操作,如日志记录
[Service]
ExecStartPost=/bin/echo "Nginx started at $(date)" >> /var/log/nginx-start.log
2
场景 C:模板实例化(多个配置文件的守护进程)
sudo systemctl start mydaemon@work.service
sudo systemctl enable mydaemon@work.service
2
enable
/ disable
/ mask
/ revert
等操作说明
systemctl enable <unit>
:创建启动时的symlink
(一般放在/etc/systemd/system/<target>.wants/
),使其随 target 启动。systemctl disable <unit>
:删除这些symlink
。systemctl mask <unit>
:把unit
链接到/dev/null
,用于阻止启动(包括依赖触发)——用于强制禁止服务。systemctl unmask <unit>
:解除 mask。systemctl revert <unit>
:撤销所有在/etc/systemd/system
的override
(包括恢复被复制的完整 unit),把 unit 恢复到下层(发行版)版本(具体行为会处理drop-in
和复制覆盖)。
调试、验证与回滚流程
编辑(用 drop-in)
sudo systemctl edit foo.service
查看合并后配置
systemctl cat foo.service
该命令会显示原始 unit
文件以及所有 drop-in
,可确认合并结果。
重载 systemd 配置(在复制/新增完整 unit 或生成器输出时必须)
sudo systemctl daemon-reload
重启并观察
sudo systemctl restart foo.service
sudo systemctl status foo.service
sudo journalctl -u foo.service -n 200 --no-pager
2
3
若不满足,回滚方法
如果用了 systemctl edit(drop-in)
,可用 sudo systemctl edit --full foo.service
或直接删除 /etc/systemd/system/foo.service.d/override.conf
,然后 daemon-reload
。
如果复制了完整 unit
到 /etc/systemd/system
,删除该文件并 daemon-reload
。
另外,可用 systemctl revert foo.service
(视 systemd
版本)尝试恢复到下层默认。
实用命令速查
# 编辑 drop-in(推荐)
sudo systemctl edit foo.service
# 查看合并后的 unit(主文件 + drop-ins)
systemctl cat foo.service
# 列出 unit 的依赖树
systemctl list-dependencies foo.service
# 重载 systemd 配置(新增/修改完整 unit 时必要)
sudo systemctl daemon-reload
# 启动/重启/查看状态
sudo systemctl start foo
sudo systemctl restart foo
sudo systemctl status foo
# 查看日志
sudo journalctl -u foo -f
# 探查当前有效 unit 文件来源
systemctl status foo.service # 输出里会显示 Loaded: 路径和 drop-in
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
实战案例:从需求到 Unit 文件定制
案例 1:定制 Python 应用服务(完整 Unit 文件)
需求:部署一个 Python API
服务,需设置启动前环境检查、后台运行、异常重启、日志输出到文件,且随系统启动。
创建 /etc/systemd/system/api.service
:
[Unit]
Description=Python API Service
Documentation=https://example.com/api-docs
# 依赖网络和数据库
After=network-online.target mysql.service
Wants=mysql.service # 弱依赖数据库(数据库未启动仅警告)
# 仅在配置文件存在时启动
ConditionPathExists=/opt/api/config.yaml
[Service]
# 服务运行用户(避免用 root)
User=appuser
Group=appuser
# 启动命令(前台运行,Type=simple)
ExecStart=/usr/bin/python3 /opt/api/main.py
# 启动前检查 Python 环境
ExecStartPre=/usr/bin/python3 -c "import sys; sys.exit(0)"
# 启动前创建日志目录
ExecStartPre=-/bin/mkdir -p /var/log/api
# 标准输出和错误重定向到日志文件
StandardOutput=append:/var/log/api/access.log
StandardError=append:/var/log/api/error.log
# 异常退出时重启,间隔 3 秒
Restart=on-failure
RestartSec=3
# 资源限制:最多 512MB 内存,20% CPU
MemoryLimit=512M
CPUQuota=20%
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
案例 2:用 drop-in 调整 SSH 服务端口和登录限制
需求:默认 SSH
服务端口为 22,需修改为 2222,且禁止 root
直接登录(不修改系统默认 sshd.service
)。
创建 drop-in
目录和配置:
# 可手动编辑文件,不用 edit 也行
sudo mkdir -p /etc/systemd/system/sshd.service.d
sudo vim /etc/systemd/system/sshd.service.d/port.conf
2
3
添加配置(覆盖 ExecStart
启动参数):
[Service]
# 原系统默认 ExecStart=/usr/sbin/sshd -D $OPTIONS
# 修改为端口 2222,禁止 root 登录
ExecStart=
ExecStart=/usr/sbin/sshd -D -p 2222 -o PermitRootLogin=no
2
3
4
5
生效配置
sudo systemctl daemon-reload
sudo systemctl restart sshd.service
2
高级覆盖技巧
动态属性覆盖(无需重载)
运行时动态修改服务属性:
# 临时调整服务CPU权重(立即生效)
sudo systemctl set-property nginx.service CPUWeight=300
# 永久调整内存限制
sudo systemctl set-property --runtime nginx.service MemoryMax=2G
2
3
4
5
查看生效配置:
systemctl show nginx.service | grep -E 'CPUWeight|MemoryMax'
条件化覆盖
根据环境变量动态调整配置:
# /etc/systemd/system/redis.service.d/10-env-override.conf
[Service]
EnvironmentFile=-/etc/default/redis
ExecStart=/usr/bin/redis-server /etc/redis/%i.conf $REDIS_OPTIONS
2
3
4
调试与验证技巧
配置合并查看
# 显示最终生效配置
systemd-analyze cat-unit nginx.service
2
覆盖差异对比
# 比较原始配置与合并后配置
diff <(systemd-analyze cat-unit nginx.service) \
<(cat /usr/lib/systemd/system/nginx.service)
2
3
最佳实践与常见问题
最佳实践
- 优先使用
drop-in
片段:除非必须完全重写服务逻辑,否则尽量用drop-in
增量修改,减少升级时的配置冲突。 - 避免直接修改系统
Unit
文件:/usr/lib/systemd/system/
下的文件由包管理维护,修改会被升级覆盖。 - 命名规范:
drop-in
目录严格遵循<服务名>.service.d
,配置文件以.conf
结尾(如custom.conf
)。 - 及时重载配置:修改
Unit
文件或drop-in
后,必须执行systemctl daemon-reload
使配置生效。 - 备份配置:自定义
Unit
文件或drop-in
片段建议纳入版本控制(如Git
),便于迁移和回滚。
常见问题与排查
配置不生效?
- 检查是否执行
systemctl daemon-reload
。 - 用
systemctl cat <服务名>
确认配置是否被正确合并。 - 查看服务日志排查错误:
journalctl -u <服务名> -e
。
- 检查是否执行
drop-in
片段未覆盖参数?- 确认
drop-in
目录和文件命名正确(如nginx.service.d/custom.conf
)。 - 部分参数(如
Description
)在[Unit]
区块,需在drop-in
中显式重写该区块的参数。
- 确认
服务启动失败,提示 “依赖未满足”?
- 检查
[Unit]
区块的Requires/After
参数,确保依赖服务存在且能正常启动(可用systemctl is-active <依赖服务>
检查)。
- 检查
结尾:小结与预告
通过掌握 drop-in/override
、模板单元、优先级与回滚流程,可以在不影响系统升级的前提下对服务进行灵活定制。