R / Richie全部文章 ↑

Linux · 4 分钟阅读

systemd 服务文件

现代 Linux 服务的"身份证"。三个 section:[Unit] [Service] [Install]。改完一定要 daemon-reload。

文件位置

路径 用途
/etc/systemd/system/<name>.service 自定义(推荐)
/etc/systemd/system/<name>.service.d/*.conf 覆盖配置
/usr/lib/systemd/system/<name>.service 软件包自带

自己的服务放 /etc/systemd/system,升级软件包不会被覆盖。

[Unit]

选项 含义
Description 描述
Documentation 文档 URL
After 列出依赖的目标 / 服务(顺序)
Before 在谁之前启动
Requires 强依赖(失败导致本服务停止)
Wants 弱依赖(失败不影响)
Conflicts 互斥
ConditionPathExists 路径存在才启动

[Service]

Type

类型 行为
simple 默认,ExecStart 起的进程是主进程
exec 同 simple,但确认 ExecStart 真正 fork 完
forking ExecStart 调 fork 后退出;适合传统 daemon
oneshot 一次性任务,配合 RemainAfterExit=yes
notify 启动完用 sd_notify 通知
idle 等其他任务跑完再启动

进程控制

选项 含义
User / Group 运行用户 / 组
WorkingDirectory 工作目录
RootDirectory chroot 目录
Nice 优先级
OOMScoreAdjust OOM 权重(-1000 ~ 1000)
LimitNOFILE 文件描述符上限
Environment 环境变量(也可引 EnvironmentFile)

启动逻辑

[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/app
EnvironmentFile=/etc/default/app
ExecStartPre=/usr/local/bin/pre-check.sh
ExecStart=/usr/bin/app
ExecStartPost=/usr/local/bin/post-start.sh
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/usr/local/bin/stop.sh
TimeoutStopSec=30
Restart=always
RestartSec=5

ExecStart 之前用 + - @ 标记:

  • + 进程获特权
  • - 失败不计入全局失败
  • @ 参数作整体
  • : 走环境变量

进程保护

PrivateTmp=true        # 独立 /tmp
ProtectSystem=strict   # 整个 FS 只读
ProtectHome=true       # /home 不可见
NoNewPrivileges=true   # 不能 setuid
ReadOnlyPaths=/etc     # 指定路径只读

这些是 systemd 推荐的"沙箱"选项。Docker 也吸收了这一套。

[Install]

选项 含义
WantedBy enable 时创建 .wants/ 软链(常用 multi-user.target)
RequiredBy 强依赖
Alias 启用别名
Also 一起 enable 的服务
DefaultInstance 模板实例默认名

完整示例

# /etc/systemd/system/app.service
[Unit]
Description=My App
After=network.target postgresql.service
Requires=postgresql.service
Documentation=https://example.com/docs

[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/app
EnvironmentFile=/etc/default/app
ExecStartPre=/opt/app/bin/migrate.sh
ExecStart=/opt/app/bin/server
ExecStop=/bin/kill -TERM $MAINPID
Restart=always
RestartSec=5
TimeoutStopSec=30

# 沙箱
PrivateTmp=true
ProtectSystem=strict
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now app

一些经验

  • Type 选错是常见坑:fork 完退出得用 forking,前台程序用 simple
  • Restart=always 别乱用,要确保"主动 stop"时不会复活
  • ExecStart 一定要绝对路径
  • 修改后用 systemd-analyze verify app.service 验语法
  • 想跑一次而不留痕:systemd-run --unit=test-app /usr/bin/command

参考