systemd 服务管理:从开机自启到故障自愈的实战笔记

举报
yd_223785715 发表于 2026/11/13 02:31:26 2026/11/13
【摘要】 前言刚接触 Linux 服务器时,很多人启动一个后台程序就用 nohup ... &,断开终端就靠运气。程序一崩,没人拉它起来;机器一重启,服务全靠手动补。systemd 就是解决这类问题的标准答案:它是绝大多数现代 Linux 发行版的初始化系统兼服务管理器,管开机、管进程、管日志、管依赖。本文用一次真实的部署经历,把 systemd 服务管理最常用的部分讲清楚。 一、一个最小可用的 s...

前言

刚接触 Linux 服务器时,很多人启动一个后台程序就用 nohup ... &,断开终端就靠运气。程序一崩,没人拉它起来;机器一重启,服务全靠手动补。systemd 就是解决这类问题的标准答案:它是绝大多数现代 Linux 发行版的初始化系统兼服务管理器,管开机、管进程、管日志、管依赖。本文用一次真实的部署经历,把 systemd 服务管理最常用的部分讲清楚。

一、一个最小可用的 service 文件

假设我们写了一个 Python 写的 Web 服务,启动命令是 python3 /opt/myapp/app.py,希望它开机自启、崩溃自动重启。先写一个 unit 文件:

[Unit]
Description=My Web App
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

把文件保存为 /etc/systemd/system/myapp.service,然后执行:

sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

三条命令分工明确:daemon-reload 让 systemd 重新读取 unit 文件;enable 建立开机自启的软链接;start 立即启动。enable 只是管开机,不管现在;start 只管现在,不管开机——两个动作经常被混淆。

二、[Unit]:描述服务和它的依赖

[Unit] 段回答两个问题:这是什么服务,它依赖谁。

  • Description:服务的文字说明,systemctl status 里会显示。
  • After=network.target:表示在本服务启动之前,先启动网络。注意 After 只决定顺序,不决定依赖关系;如果还需要“网络没起来就不启动我”,要再加 Requires=network.target 或 Wants=network.target。Requires 是强依赖,依赖失败则本服务也失败;Wants 是弱依赖,依赖失败不影响本服务。
  • 常见的依赖目标还有 multi-user.target(多用户命令行模式)、local-fs.target(本地文件系统就绪)。写 Web 服务时,After=network.target 几乎是标配。

三、[Service]:进程怎么跑、崩了怎么办

这是最核心的一段,几个关键字段要理解透。

Type:服务是什么启动形态

  • simple(默认):ExecStart 启动的进程就是主进程,systemd 认为命令执行即启动完成。适合前台运行的程序。
  • forking:程序启动后会 fork 出子进程、父进程退出,适合传统 daemon(比如老式 Nginx 的启动方式)。需要配合 PIDFile 告诉 systemd 主进程的 PID 文件位置。
  • oneshot:执行一次就退出的任务,比如初始化脚本。配合 RemainAfterExit=yes 可以让它在执行完后仍显示为 active 状态。
  • notify:程序启动完成后会主动通知 systemd(通过 sd_notify),适合需要较长初始化时间的服务。

新手最常见的坑,就是把一个会 fork 的程序写成 simple,或者把前台程序写成 forking,结果 systemctl status 显示启动失败,日志里却一切正常。先确认程序的运行形态,再选 Type。

Restart:崩溃自愈

Restart=on-failure 表示进程非正常退出时自动重启;Restart=always 表示无论什么原因退出都重启;RestartSec=5 表示等 5 秒再重启,避免瞬间反复拉起把 CPU 打满。还有一个容易被忽略的 StartLimitBurst 和 StartLimitIntervalSec,它们限制单位时间内的重启次数,防止程序一启动就崩、崩了又启动的死循环。

运行身份与环境

  • User / Group:指定服务以哪个用户身份运行。不要用 root 跑应用服务,新建一个专用用户是基本安全习惯。
  • WorkingDirectory:进程的工作目录,相对路径、日志文件、配置文件都以它为基准。
  • Environment:设置环境变量,例如 Environment=APP_ENV=production。需要多个变量时可以写多行,或者用 EnvironmentFile=/opt/myapp/.env 从文件加载。
  • ExecStartPre / ExecStartPost:主命令执行前后的钩子,比如启动前检查配置文件、启动后发一条通知。

四、日常运维:五个高频命令

systemctl status myapp      # 看状态、看最近日志、看是否开机自启
systemctl start myapp       # 启动
systemctl stop myapp        # 停止
systemctl restart myapp     # 重启
systemctl reload myapp      # 重载配置(仅当服务支持 reload)

status 的输出值得细读:Active: active (running) 是正常运行;Loaded: enabled 说明已设置开机自启;下面几行是最近的日志,排障时先看这里,再决定要不要去翻完整日志。

查看完整日志用 journalctl:

journalctl -u myapp -f        # 实时跟随本服务的日志
journalctl -u myapp --since "1 hour ago"   # 最近一小时的日志
journalctl -u myapp -p err     # 只看错误级别以上的日志

systemd 的日志由 journald 统一收集,不用再到处找日志文件,这是它比 nohup 方式方便得多的地方。

五、排障清单:服务起不来的常见原因

  1. 改了 unit 文件没 daemon-reload:systemd 缓存了旧配置,改完必须先 daemon-reload 再 restart。
  2. ExecStart 路径写错:必须写绝对路径,python3 这种相对命令要写成 /usr/bin/python3。可以用 which python3 确认。
  3. 权限问题:User 指定的用户读不到代码目录或写不了日志目录,先用 ls -l 检查权限。
  4. 端口被占用:ss -tlnp 看看端口谁在用,旧进程没杀干净是常见原因。
  5. 依赖服务没起来:After 写错目标,或者数据库服务本身启动失败,先查依赖服务的状态。

六、一个真实的教训

有一次我把一个服务的 Type 写成 simple,但程序内部会 fork 两次才进入主循环。systemctl start 总是报超时失败,日志里程序其实跑得好好的。改成 Type=forking 并加上 PIDFile 后,一切正常。教训是:unit 文件要按程序的真实行为写,而不是按想象写。不确定时,先用 ps -ef 看看进程树,或者直接问程序的文档。

结语

systemd 的学习曲线并不陡:先掌握一个最小 service 文件,再理解 [Unit]、[Service]、[Install] 三段的分工,最后把 status、journalctl 变成肌肉记忆,就能覆盖日常 90% 的服务管理场景。把服务交给 systemd,就是把“程序崩了谁来拉起来”这个问题,从人肉值守变成系统兜底。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。