简介
作为进阶系列的第一篇,本文将聚焦 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
。
shell
sudo systemctl edit nginx.service
编辑器会打开,你写入例如:
ini
[Service]
Environment="ENV=production"
ExecStartPre=/usr/bin/echo 'starting nginx'
保存后,运行:
shell
sudo systemctl daemon-reload
sudo systemctl restart nginx
这样不会改动发行版文件,且便于回滚或审计。
- 需要完整替换:如果确实要完全替换一个
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
):
ini
[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
这里 %i
会被替换为实例名(work / home
等),通常用于文件名或参数。用 template
可以让多个实例共享相同 unit
定义。
常见定制场景与实例
场景 A:只需设环境变量或修改一个参数
使用 drop-in:
shell
sudo systemctl edit myapp.service
# 写入 /etc/systemd/system/myapp.service.d/override.conf:
[Service]
Environment="APP_ENV=prod"
EnvironmentFile=/etc/default/myapp
重载并重启:
shell
sudo systemctl daemon-reload
sudo systemctl restart myapp
场景 B:替换 ExecStart(需要先清除原 ExecStart)
如果主文件有 ExecStart
,在 drop-in
中直接写另一个 ExecStart=
会被追加而非替换。要先清空原来的,再写新值:
ini
[Service]
ExecStart=
ExecStart=/usr/local/bin/myapp --serve
空行 ExecStart=
是清除旧值的标准方法(适用于累加型字段)。
添加后置命令:使用 ExecStartPost= 执行启动后的操作,如日志记录
ini
[Service]
ExecStartPost=/bin/echo "Nginx started at $(date)" >> /var/log/nginx-start.log
场景 C:模板实例化(多个配置文件的守护进程)
shell
sudo systemctl start mydaemon@work.service
sudo systemctl enable mydaemon@work.service
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):
shell
sudo systemctl edit foo.service
查看合并后配置:
shell
systemctl cat foo.service
该命令会显示原始 unit
文件以及所有 drop-in
,可确认合并结果。
重载 systemd 配置(在复制/新增完整 unit 或生成器输出时必须):
shell
sudo systemctl daemon-reload
重启并观察:
shell
sudo systemctl restart foo.service
sudo systemctl status foo.service
sudo journalctl -u foo.service -n 200 --no-pager
若不满足,回滚方法:
如果用了 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
版本)尝试恢复到下层默认。
实用命令速查
shell
# 编辑 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
实战案例:从需求到 Unit 文件定制
案例 1:定制 Python 应用服务(完整 Unit 文件)
需求:部署一个 Python API
服务,需设置启动前环境检查、后台运行、异常重启、日志输出到文件,且随系统启动。
创建 /etc/systemd/system/api.service
:
ini
[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:用 drop-in 调整 SSH 服务端口和登录限制
需求:默认 SSH
服务端口为 22,需修改为 2222,且禁止 root
直接登录(不修改系统默认 sshd.service
)。
- 创建
drop-in
目录和配置:
shell
可手动编辑文件,不用 edit 也行
sudo mkdir -p /etc/systemd/system/sshd.service.d
sudo vim /etc/systemd/system/sshd.service.d/port.conf
- 添加配置(覆盖
ExecStart
启动参数):
ini
[Service]
# 原系统默认 ExecStart=/usr/sbin/sshd -D $OPTIONS
# 修改为端口 2222,禁止 root 登录
ExecStart=
ExecStart=/usr/sbin/sshd -D -p 2222 -o PermitRootLogin=no
- 生效配置
shell
sudo systemctl daemon-reload
sudo systemctl restart sshd.service
高级覆盖技巧
动态属性覆盖(无需重载)
运行时动态修改服务属性:
shell
# 临时调整服务CPU权重(立即生效)
sudo systemctl set-property nginx.service CPUWeight=300
# 永久调整内存限制
sudo systemctl set-property --runtime nginx.service MemoryMax=2G
查看生效配置:
shell
systemctl show nginx.service | grep -E 'CPUWeight|MemoryMax'
条件化覆盖
根据环境变量动态调整配置:
ini
# /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
调试与验证技巧
配置合并查看
shell
# 显示最终生效配置
systemd-analyze cat-unit nginx.service
覆盖差异对比
shell
# 比较原始配置与合并后配置
diff <(systemd-analyze cat-unit nginx.service) \
<(cat /usr/lib/systemd/system/nginx.service)
最佳实践与常见问题
最佳实践
-
优先使用
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
、模板单元、优先级与回滚流程,可以在不影响系统升级的前提下对服务进行灵活定制。
下篇文章将聚焦 "cgroups v2
与资源控制实战",敬请期待!
参考资源:
Systemd 官方文档:systemd.io/ Arch Wiki:wiki.archlinux.org/title/syste... Man 页:man systemd.service
欢迎在评论区分享你的 Systemd
配置经验,或提出问题。你的反馈是我继续创作的动力!