systemd
是现代 Linux 发行版中默认的初始化系统(init system)和服务管理器。相比传统的 SysVinit
,它拥有并发启动、依赖关系解析、服务自恢复等强大特性,因此成为了企业级部署和开发者首选的服务管理框架。
本文将由浅入深,从基本概念讲起,逐步深入到实际配置、调试与优化,帮助你全面掌握 systemd
服务配置技巧。
一、什么是 systemd?
systemd
是一套系统与服务管理工具,主要负责:
- 系统引导流程(init)
- 守护进程管理(daemon)
- 日志收集(journal)
- 挂载点和自动挂载(mount/automount)
- 定时任务(timer)
- 网络管理(通过
networkd
)
在实际项目中,我们主要用 systemd
来配置和管理后台服务,也就是我们常说的 .service
单元。
二、systemd Unit 文件结构解析
每一个 systemd 管理的服务,都有一个对应的 Unit 文件,后缀为 .service
,位于 /etc/systemd/system/
或 /lib/systemd/system/
。
一个典型的服务 unit 文件结构如下:
[Unit]
Description=My Custom Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/python3 app.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
一个完整的 systemd unit 文件 可能包含如下结构段(根据 unit 类型而定):
段名 | 适用类型 | 说明 |
---|---|---|
[Unit] | 所有类型 | 描述 unit 的元信息与依赖 |
[Service] | .service | 服务定义本体 |
[Socket] | .socket | 套接字激活设置 |
[Timer] | .timer | 定时任务调度 |
[Install] | 所有类型 | 定义该 unit 如何安装到目标(target)中 |
[Path] | .path | 文件或目录监控触发器 |
[Mount] | .mount | 文件系统挂载定义 |
[Swap] | .swap | Swap 分区管理 |
[Slice] | .slice | CGroup 切片资源限制 |
[Scope] | .scope | 对临时进程组的描述(不常手写) |
三、常见配置选项详解
PS. 如何查找所有可用字段与含义?使用 man
命令是最权威的方式:
man systemd.unit
man systemd.service
man systemd.timer
man systemd.exec
man systemd.directives
其中 systemd.directives 是所有可用字段的索引总表,你可以通过关键词查找字段适用于哪些 unit 类型。
[Unit] 段配置项大全
配置项 | 说明 |
---|---|
Description= | 对该 unit 的描述,用于 systemctl status 显示 |
Documentation= | 指定帮助文档链接 |
Requires= | 启动该服务时,必须同时启动的依赖 |
Wants= | 弱依赖,如果依赖项失败,当前服务仍会继续 |
Before= / After= | 启动顺序关系,不代表依赖,只是时间顺序 |
Conflicts= | 定义与当前 unit 冲突的其他 unit,不能共存 |
Condition...= | 启动前的判断条件,如 ConditionPathExists= 、ConditionUser= 等 |
Assert...= | 与 Condition 类似,但失败时会导致硬错误(启动失败) |
[Service] 段配置项详解(含可选值)
配置项 | 说明 |
---|---|
Type= | 服务运行类型,有以下选项: - simple :默认,ExecStart 启动的进程保持前台运行- forking :后台进程(如传统的守护进程)- oneshot :执行一次性命令,通常用于初始化脚本- notify :服务会发送 readiness 信号(需实现 systemd 通信)- idle :延迟到所有任务处理完再启动 |
ExecStart= | 启动命令,必须指定 |
ExecStop= / ExecReload= | 停止 / 重载命令(可选) |
Restart= | 自动重启策略: - no (默认):不重启- on-success :成功退出时重启- on-failure :非 0 退出时重启- always :无论如何都重启- on-abnormal :信号或 core dump 时重启 |
RestartSec= | 重启前延迟秒数 |
RemainAfterExit= | 适用于 oneshot 类型,服务执行后是否保持“激活”状态 |
TimeoutStartSec= / TimeoutStopSec= | 启动/停止的超时时间 |
User= / Group= | 服务运行用户和组 |
WorkingDirectory= | 工作目录 |
Environment= / EnvironmentFile= | 设置环境变量 |
StandardOutput= / StandardError= | 设置日志输出: journal , syslog , null , tty 等 |
LimitNOFILE= | 文件描述符限制 |
CapabilityBoundingSet= | 限制服务拥有的 Linux capabilities |
[Install] 段配置项(决定 enable 方式)
配置项 | 说明 |
---|---|
WantedBy= | 该 unit 会被自动添加到此 target 的 wants 目录中(enable 时) |
RequiredBy= | 会被添加到其他 unit 的 Requires= 依赖中 |
Also= | 指定其他 unit 文件也一起启用 |
Alias= | 提供 unit 文件的别名,供 systemctl 调用 |
[Timer] 配置项
配置项 | 示例值 | 含义 |
---|---|---|
OnCalendar= | daily , Mon *-*-* 05:00:00 , *-*-01 00:00:00 | 类似 cron 表达式的时间格式 |
OnBootSec= | 5min | 开机后 N 秒启动 |
OnUnitActiveSec= | 10min | 上次激活后多久再次启动 |
Persistent= | true/false | 是否补偿 missed 事件(如关机期间) |
[Socket] 配置项
配置项 | 示例值 | 含义 |
---|---|---|
ListenStream= | 8080 , /run/myapp.sock | TCP 或 Unix socket |
SocketMode= | 0660 | socket 权限 |
Accept= | yes/no | 是否接受多个连接(多实例) |
[Path] 配置项
配置项 | 示例值 | 说明 |
---|---|---|
PathExists= | /srv/myapp/config.yaml | 监控文件是否存在 |
PathChanged= | /srv/myapp/data/ | 监控内容变化 |
Unit= | myapp.service | 被触发的 unit 名称 |
[Mount] 配置项
与 /etc/fstab 类似,用于挂载磁盘:
配置项 | 示例值 | 说明 |
---|---|---|
What= | /dev/sdb1 | 挂载源设备 |
Where= | /mnt/data | 挂载目标路径 |
Type= | ext4 | 文件系统类型 |
Options= | defaults,noatime | 挂载选项 |
四、创建并启用一个自定义服务
假设你有一个脚本 /srv/myapp/run.sh
,希望开机自启并自动重启,我们可以按以下步骤操作:
1. 创建 Unit 文件
sudo nano /etc/systemd/system/myapp.service
写入如下内容:
[Unit]
Description=MyApp Daemon
After=network.target
[Service]
Type=simple
ExecStart=/srv/myapp/run.sh
WorkingDirectory=/srv/myapp
Restart=on-failure
RestartSec=2
User=www-data
[Install]
WantedBy=multi-user.target
2. 重新加载 systemd 配置
sudo systemctl daemon-reexec # 重载 systemd 本身(极少用)
sudo systemctl daemon-reload # 重载所有 unit 文件(常用)
3. 启动服务并设置开机启动
sudo systemctl start myapp.service
sudo systemctl enable myapp.service
4. 查看服务状态与日志
sudo systemctl status myapp
sudo journalctl -u myapp -f
五、调试技巧与问题排查
1. 服务失败但没有报错?
- 检查
ExecStart
路径是否正确,是否有执行权限。 - 使用
journalctl -xe
获取详细错误信息。
2. 修改 unit 文件后没生效?
别忘了执行:
sudo systemctl daemon-reload
再重启服务:
sudo systemctl restart myapp
3. 服务运行不了但手动命令可以?
可能是缺少环境变量或依赖。使用以下方式添加环境:
Environment="PATH=/usr/local/bin:/usr/bin"
或者写入 .env
文件后使用:
EnvironmentFile=/srv/myapp/.env
六、进阶用法:依赖管理与 Timer 定时任务
1. 服务依赖(Before / After)
如果你希望你的服务在某个服务启动后运行:
[Unit]
After=nginx.service
Requires=nginx.service
After
是启动顺序,Requires
是依赖绑定。
2. 定时任务(Timer)
替代 crontab 的现代方式。示例:
创建 /etc/systemd/system/cleanup.service
:
[Unit]
Description=Daily Cleanup
[Service]
Type=oneshot
ExecStart=/srv/scripts/cleanup.sh
然后创建对应的 Timer:
[Unit]
Description=Run Cleanup Daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
启用并启动 Timer:
sudo systemctl enable --now cleanup.timer
七、最佳实践总结
操作 | 建议 |
---|---|
文件存放路径 | 建议放在 /etc/systemd/system/ ,便于管理与持久化 |
用户权限 | 尽量使用非 root 用户运行服务 |
重启策略 | 推荐使用 on-failure 或 always ,结合 RestartSec |
日志收集 | 使用 journalctl 替代传统 log 文件,方便统一查看 |
自动 reload | 修改后不要忘记 daemon-reload |
八、参考命令速查表
# 启动 / 停止 / 重启
sudo systemctl start xxx.service
sudo systemctl stop xxx.service
sudo systemctl restart xxx.service
# 查看服务状态
sudo systemctl status xxx.service
# 设置服务开机启动 / 禁用
sudo systemctl enable xxx.service
sudo systemctl disable xxx.service
# 查看实时日志
sudo journalctl -u xxx.service -f
# 列出所有服务
systemctl list-units --type=service
总结
无论是部署 Web 应用、运行 Python 脚本、还是控制后台服务进程,systemd
都能提供稳定而强大的守护能力。掌握它,不仅可以简化服务器管理工作流,还能显著提升系统可靠性。
在容器化、微服务流行的今天,systemd
仍然在边缘节点、物理机、IoT、传统云服务器中占据一席之地。它不是过时的工具,而是基础设施工程师的必备技能。