玩转 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'
保存后,运行:
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):
[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:
sudo systemctl edit myapp.service
# 写入 /etc/systemd/system/myapp.service.d/override.conf:
[Service]
Environment="APP_ENV=prod"
EnvironmentFile=/etc/default/myapp
重载并重启:
sudo systemctl daemon-reload
sudo systemctl restart myapp
场景 B:替换 ExecStart(需要先清除原 ExecStart)
如果主文件有 ExecStart,在 drop-in 中直接写另一个 ExecStart= 会被追加而非替换。要先清空原来的,再写新值:
[Service]
ExecStart=
ExecStart=/usr/local/bin/myapp --serve
空行 ExecStart= 是清除旧值的标准方法(适用于累加型字段)。
添加后置命令:使用 ExecStartPost= 执行启动后的操作,如日志记录
[Service]
ExecStartPost=/bin/echo "Nginx started at $(date)" >> /var/log/nginx-start.log
场景 C:模板实例化(多个配置文件的守护进程)
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):
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
若不满足,回滚方法:
如果用了 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
实战案例:从需求到 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:用 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
- 添加配置(覆盖
ExecStart启动参数):
[Service]
# 原系统默认 ExecStart=/usr/sbin/sshd -D $OPTIONS
# 修改为端口 2222,禁止 root 登录
ExecStart=
ExecStart=/usr/sbin/sshd -D -p 2222 -o PermitRootLogin=no
- 生效配置
sudo systemctl daemon-reload
sudo systemctl restart sshd.service
高级覆盖技巧
动态属性覆盖(无需重载)
运行时动态修改服务属性:
# 临时调整服务CPU权重(立即生效)
sudo systemctl set-property nginx.service CPUWeight=300
# 永久调整内存限制
sudo systemctl set-property --runtime nginx.service MemoryMax=2G
查看生效配置:
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
调试与验证技巧
配置合并查看
# 显示最终生效配置
systemd-analyze cat-unit nginx.service
覆盖差异对比
# 比较原始配置与合并后配置
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 官方文档:https://systemd.io/
Arch Wiki:https://wiki.archlinux.org/title/systemd
Man 页:man systemd.service
欢迎在评论区分享你的 Systemd 配置经验,或提出问题。你的反馈是我继续创作的动力!
浙公网安备 33010602011771号