首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >把应用托管给 systemd:service 单元的重启策略、启动顺序与日志排查

把应用托管给 systemd:service 单元的重启策略、启动顺序与日志排查

原创
作者头像
一只特立独行的兔先生
发布于 2026-11-01 09:28:37
发布于 2026-11-01 09:28:37
1380
举报

背景

用 nohup 或 screen 跑服务,机器重启后就没了,进程崩溃也没人拉起。把应用交给 systemd 管理,可以获得开机自启、崩溃重启、统一日志和资源限制。本文用一个例子说明 service 单元的常用配置,以及最常见的几个坑。

一、一个完整的单元文件

保存为 /etc/systemd/system/myapp.service:

代码语言:ini
复制
[Unit]
Description=My Application
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/myapp
EnvironmentFile=-/etc/myapp/env
ExecStart=/opt/myapp/bin/server --config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=3
LimitNOFILE=65536
TimeoutStopSec=30

[Install]
WantedBy=multi-user.target

加载并启动:

代码语言:bash
复制
systemctl daemon-reload
systemctl enable --now myapp
systemctl status myapp

修改单元文件后必须 daemon-reload,否则 systemd 仍使用旧配置。

二、关键配置项说明

  • User 和 Group:不要用 root 运行应用,按最小权限原则使用专用账号。
  • WorkingDirectory:设置工作目录,避免相对路径出错。
  • EnvironmentFile:从文件读取环境变量,路径前的 - 表示文件不存在时不报错。systemd 不会加载 ~/.bashrc,应用依赖的环境变量要显式提供。
  • ExecStart:必须写绝对路径,且不是通过 shell 解释的,管道和重定向不会生效。需要 shell 特性时写成 /bin/bash -c '...'。
  • Restart:常用取值有 on-failure(异常退出时重启)和 always(无论如何都重启)。手动 systemctl stop 不会触发重启。
  • RestartSec:重启前等待的时间,避免快速循环重启。
  • StartLimitIntervalSec 与 StartLimitBurst:在时间窗口内启动次数超过上限,单元会进入 failed 状态不再重启,防止无限崩溃循环。这两项属于 [Unit] 段。
  • LimitNOFILE:提高文件描述符上限,高并发服务常用。注意在 systemd 下,/etc/security/limits.conf 对服务不生效。
  • TimeoutStopSec:停止时等待进程优雅退出的时间,超时后会被强制杀掉。

三、启动顺序与依赖

After= 只控制顺序,不表示依赖;Wants= 和 Requires= 才表达依赖关系。常见写法:

  • 依赖网络:After=network-online.target 加 Wants=network-online.target。
  • 依赖数据库或其他服务:After=mysql.service 加 Wants=mysql.service(若数据库在本机)。

After=network.target 只表示网络栈已启动,不保证已获得 IP 地址;需要网络可用时用 network-online.target。

即使设置了顺序,也不要假设依赖服务“已经可用”。应用本身应当对数据库、缓存的连接做重试,这比依赖启动顺序更可靠。

四、Type 如何选

  • simple(默认):ExecStart 启动的进程就是主进程,适合前台运行的程序。
  • forking:程序自己 fork 到后台,需要配合 PIDFile。
  • oneshot:执行完就退出,适合一次性任务。
  • notify:程序启动完成后通过 sd_notify 通知 systemd,适合需要精确判断启动完成的服务。

常见错误:程序本身做了守护化(后台运行),却用 simple 类型,systemd 看到主进程退出就认为服务结束,随后清理掉子进程。解决方式是让程序在前台运行,或使用 forking。

五、查看日志与排查

代码语言:bash
复制
# 查看该服务日志,实时跟踪
journalctl -u myapp -f

# 查看本次启动以来的日志
journalctl -u myapp -b

# 按时间过滤
journalctl -u myapp --since "1 hour ago"

# 检查单元文件语法
systemd-analyze verify /etc/systemd/system/myapp.service

应用把日志输出到标准输出和标准错误,journald 会自动收集。需要长期保存时,检查 /etc/systemd/journald.conf 中的持久化和大小限制,磁盘占用可以用 journalctl --disk-usage 查看。

六、常见故障

  1. status 显示 failed,退出码 203/EXEC:ExecStart 路径不存在或没有执行权限。
  2. 退出码 217/USER:User 指定的用户不存在。
  3. 重启太频繁后不再拉起:触发了启动频率限制,用 systemctl reset-failed myapp 清除状态,并先解决崩溃原因。
  4. 改了环境变量不生效:修改 EnvironmentFile 后需要重启服务,修改单元文件还需要 daemon-reload。
  5. 权限问题:以非 root 用户运行时,监听 1024 以下的端口会失败,可以用 AmbientCapabilities=CAP_NET_BIND_SERVICE,或者让反向代理监听 80/443。

七、可选的加固配置

在不影响功能的前提下,可以逐项尝试:NoNewPrivileges=true、ProtectSystem=full、PrivateTmp=true、ProtectHome=true。每开启一项都要测试应用是否仍能正常读写所需路径。可用 systemd-analyze security myapp 查看当前的暴露评分。

小结

  1. 用专用用户、绝对路径、EnvironmentFile 配置运行环境。
  2. Restart=on-failure 加 RestartSec,并理解启动频率限制。
  3. After 管顺序,Wants 管依赖,应用自身仍要做连接重试。
  4. 用 journalctl -u 排查,改完单元文件记得 daemon-reload。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 背景
  • 一、一个完整的单元文件
  • 二、关键配置项说明
  • 三、启动顺序与依赖
  • 四、Type 如何选
  • 五、查看日志与排查
  • 六、常见故障
  • 七、可选的加固配置
  • 小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档