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

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 进程退出后保持 active RemainAfterExit=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 说明 适用场景
simple ExecStart 进程即主进程(默认) 前台运行的程序
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
CPUQuota CPU 配额百分比 200%(2 核)
CPUWeight CPU 权重(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

十、扩展阅读