强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

systemd 教程 / Service Unit 详解

Service Unit 详解

一、[Service] 段核心参数

1.1 启动参数

参数说明示例
Type服务进程类型Type=simple
ExecStart启动命令ExecStart=/usr/bin/nginx
ExecStartPre启动前执行ExecStartPre=/usr/sbin/nginx -t
ExecStartPost启动后执行ExecStartPost=/bin/sleep 1
ExecReload重载命令ExecReload=/bin/kill -HUP $MAINPID
ExecStop停止命令ExecStop=/bin/kill -QUIT $MAINPID
ExecStopPost停止后执行ExecStopPost=/usr/bin/cleanup.sh
RemainAfterExit进程退出后保持 activeRemainAfterExit=yes

完整示例:

[Unit]
Description=My Web Application
After=network.target postgresql.service

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
EnvironmentFile=/etc/myapp/env

ExecStartPre=/opt/myapp/scripts/pre-start.sh
ExecStart=/usr/bin/node /opt/myapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID

Restart=on-failure
RestartSec=5
TimeoutStartSec=30
TimeoutStopSec=30

[Install]
WantedBy=multi-user.target

二、Type 类型详解

Type说明适用场景
simpleExecStart 进程即主进程(默认)前台运行的程序
forking进程 fork 到后台传统 daemon(如 Nginx daemon 模式)
oneshot执行完毕后退出初始化脚本、一次性任务
dbus获取 D-Bus 名称后就绪D-Bus 服务
notify发送 sd_notify() 后就绪支持 notify 协议的服务
idle等待其他任务完成后启动控制台输出服务

2.1 simple(最常用)

[Service]
Type=simple
ExecStart=/usr/bin/myapp --foreground

💡 提示:如果程序在前台运行,就用 simple。这是最常见的类型。

2.2 forking

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStart=/usr/sbin/nginx

⚠️ 注意forking 类型强烈建议设置 PIDFile,否则 systemd 可能无法正确追踪主进程。

2.3 oneshot

[Service]
Type=oneshot
ExecStart=/usr/local/bin/initialize-db.sh
ExecStart=/usr/local/bin/seed-data.sh
RemainAfterExit=yes

oneshot 可以指定多个 ExecStart,按顺序执行。RemainAfterExit=yes 使任务完成后 Unit 仍保持 active。

2.4 notify

[Service]
Type=notify
ExecStart=/usr/bin/myapp
NotifyAccess=main

服务就绪时调用 sd_notify(0, "READY=1"),适合启动时间较长的服务:

// C 示例
#include <systemd/sd-daemon.h>
int main() {
    init_database();
    load_config();
    sd_notify(0, "READY=1"); // 通知 systemd 已就绪
    // 主循环...
}

三、Restart 策略

3.1 Restart 参数值

触发条件
no不自动重启(默认)
on-success退出码为 0
on-failure非零退出码、被信号杀死、超时、看门狗
on-abnormal被信号杀死、超时、看门狗
on-abort收到未捕获的信号
on-watchdog看门狗超时
always无论何种原因都重启

3.2 Restart 与退出方式对照

Restart 值正常退出(0)异常退出(非0)被信号杀死超时
no
on-success
on-failure
on-abnormal
always

3.3 重启频率限制

[Service]
Restart=on-failure
RestartSec=5
StartLimitBurst=3       # 10 秒内最多重启 3 次
StartLimitIntervalSec=10
参数说明默认值
RestartSec重启前等待时间100ms
StartLimitBurst时间窗口内最大重启次数5
StartLimitIntervalSec时间窗口10s

💡 提示:合理的 RestartSec(建议 5-10 秒)可避免服务崩溃时的 CPU 风暴。


四、环境变量

[Service]
# 行内设置
Environment=MY_VAR1=value1 MY_VAR2=value2
Environment="LANG=en_US.UTF-8"

# 外部文件(- 前缀表示文件不存在时不报错)
EnvironmentFile=/etc/myapp/env
EnvironmentFile=-/etc/myapp/env.local

环境文件格式(/etc/myapp/env):

# 注释行
DATABASE_URL=postgresql://user:pass@localhost/myapp
REDIS_URL=redis://localhost:6379
LOG_LEVEL=info

⚠️ 注意- 前缀表示文件不存在时不报错。环境变量优先级:命令行变量 > EnvironmentFile > Environment


五、用户与权限

[Service]
User=www-data
Group=www-data
SupplementaryGroups=www-data docker
WorkingDirectory=/opt/myapp
UMask=0027

六、资源限制

6.1 传统限制(ulimit)

[Service]
LimitNOFILE=65535      # 最大打开文件描述符
LimitNPROC=4096        # 最大进程数
LimitMEMLOCK=infinity  # 最大锁定内存
LimitCORE=infinity     # 核心转储文件大小

6.2 CGroup 资源控制(systemd v232+)

[Service]
MemoryMax=2G           # 硬内存限制
MemoryHigh=1G          # 软限制(超过后节流)
CPUQuota=200%          # CPU 配额(200% = 2 核)
CPUWeight=100          # CPU 权重
TasksMax=512           # 最大任务数
参数说明示例
MemoryMax硬内存限制2G512M
MemoryHigh软限制(超过后 swap 节流)1G
CPUQuotaCPU 配额百分比200%(2 核)
CPUWeightCPU 权重(10-10000)100
TasksMax最大任务数512

七、安全加固

[Service]
# 文件系统保护
ProtectSystem=strict        # /usr 只读
ProtectHome=yes             # /home 不可见
PrivateTmp=yes              # 临时目录私有化
ReadWritePaths=/var/lib/myapp  # 可写路径(配合 strict)
PrivateDevices=yes          # 设备访问私有化

# 权限控制
NoNewPrivileges=yes         # 禁止提权
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# 系统保护
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes

💡 提示:使用 systemd-analyze security myapp.service 检查安全评分(0-10,越低越安全)。


八、PID 文件与看门狗

8.1 PID 文件

[Service]
Type=forking
PIDFile=/run/myapp.pid
ExecStart=/usr/bin/myapp --daemon

⚠️ 注意:PID 文件必须在主进程 fork 完成后由主进程自己写入。

8.2 看门狗(Watchdog)

[Service]
Type=notify
WatchdogSec=30

服务必须定期发送心跳(sd_notify(0, "WATCHDOG=1")),超时未收到则标记失败。

⚠️ 注意:需要程序代码中集成 sd_notify() 调用,没有代码支持时不要启用。


九、生产场景

场景 1:Node.js 应用服务

[Unit]
Description=Node.js API Server
After=network.target redis.service
Requires=redis.service

[Service]
Type=simple
User=nodeapp
Group=nodeapp
WorkingDirectory=/opt/api-server
EnvironmentFile=/etc/api-server/env
Environment=NODE_ENV=production

ExecStart=/usr/bin/node --max-old-space-size=2048 /opt/api-server/dist/server.js
Restart=on-failure
RestartSec=5
StartLimitBurst=3
StartLimitIntervalSec=30

MemoryMax=3G
CPUQuota=200%
LimitNOFILE=65535

NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/log/api-server /var/lib/api-server

[Install]
WantedBy=multi-user.target

场景 2:一次性备份脚本

[Unit]
Description=Database Backup
After=postgresql.service

[Service]
Type=oneshot
User=postgres
ExecStart=/usr/local/bin/backup-db.sh
TimeoutStartSec=3600
StandardOutput=journal
StandardError=journal
SyslogIdentifier=db-backup

十、扩展阅读