Linux systemd 服务管理入门:Unit 文件编写与故障排查

说明: 本文为配置思路与示例整理,不代表作者已在自己的服务器上逐项验证全部命令。执行涉及公网暴露、账户权限、数据删除或服务重启的操作前,请先备份,并结合官方文档与实际环境核验。

引言

在 Linux 服务器运维中,systemd 是绝大多数发行版(Ubuntu 16.04+、Debian 8+、CentOS 7+)的初始化系统和服务管理器。无论你是部署 Docker、Nginx、自定义应用还是定时任务,都离不开 systemd。然而,不少开发者在实际使用中常常遇到服务启动失败、配置不生效、启动顺序混乱等问题。

本文将从零开始,手把手教你编写 Unit 文件、管理服务,并分享几个高频踩坑场景的排查思路。


一、systemd 基础概念

systemd 管理的每一个资源都称为一个 Unit,常见的 Unit 类型:

类型 后缀 用途
Service .service 系统服务(最常用)
Socket .socket 套接字激活
Timer .timer 定时任务(替代 cron)
Mount .mount 文件系统挂载
Path .path 路径监控

Unit 文件的存放位置:

  • 系统级/etc/systemd/system/(管理员手动创建,优先级最高)
  • 运行时/run/systemd/system/(运行时生成,重启丢失)
  • 默认/usr/lib/systemd/system/(软件包自带)

💡 优先级:/etc/systemd/system/ > /run/systemd/system/ > /usr/lib/systemd/system/


二、编写一个 Service Unit 文件

假设我们有一个用 Node.js 编写的 API 服务,入口文件是 /opt/myapp/server.js

2.1 创建 Unit 文件

BASH
sudo vim /etc/systemd/system/myapp.service

写入以下内容:

INI
[Unit]
Description=My Application API Server
Documentation=https://example.com/docs
After=network.target postgresql.service
Wants=postgresql.service

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=3

# 环境变量
Environment=NODE_ENV=production
Environment=PORT=3000

# 安全加固
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/myapp/data

# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

[Install]
WantedBy=multi-user.target

2.2 关键字段解释

[Unit] 部分

  • After=network.target:在网络就绪之后启动
  • Wants=postgresql.service:软依赖,PostgreSQL 启动失败不会阻止本服务启动
  • Requires=postgresql.service:硬依赖,PostgreSQL 启动失败则本服务也不启动

[Service] 部分

  • Type=simple:ExecStart 启动的进程就是主进程
  • Restart=on-failure:仅在非正常退出时重启(exit code 非 0 或被信号杀掉)
  • RestartSec=5:重启间隔 5 秒
  • StartLimitBurst=3:60 秒内最多重启 3 次,防止无限重启

[Install] 部分

  • WantedBy=multi-user.target:系统进入多用户模式时启动

三、服务管理常用命令

BASH
# 重新加载 Unit 文件(修改配置后必须执行)
sudo systemctl daemon-reload

# 启动服务
sudo systemctl start myapp

# 查看状态
sudo systemctl status myapp

# 设置开机自启
sudo systemctl enable myapp

# 同时启动并设置自启
sudo systemctl enable --now myapp

# 停止服务
sudo systemctl stop myapp

# 禁用开机自启
sudo systemctl disable myapp

# 查看服务日志
sudo journalctl -u myapp -f          # 实时跟踪
sudo journalctl -u myapp --since today  # 今天的日志
sudo journalctl -u myapp -n 50        # 最近 50 行

四、常见坑与排查技巧

坑 1:Unit 文件语法错误导致服务"消失"

症状:执行 systemctl start xxx 报 Unit not found,但文件明明存在。

原因:Unit 文件中有语法错误(如缺少 [Service] 段、缩进用了 Tab 而非空格、中括号拼写错误等),systemd 直接忽略了这个文件。

排查

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

# 查看 systemd 是否识别了这个 Unit
systemctl list-unit-files | grep myapp

# 查看 systemd 解析错误
journalctl -u systemd --no-pager | grep -i "myapp\|error"

修复:仔细检查 Unit 文件语法,特别注意:

  • 缩进必须用空格,不能用 Tab
  • 每个 Section 标题 [Unit][Service][Install] 必须正确
  • 路径和命令中的空格要用引号包裹

坑 2:同一服务存在两个 Unit 文件导致冲突

症状:服务行为异常,修改配置后不生效,或者出现"启动了但不是我想要的版本"。

原因:同一个服务在不同目录下有两个 Unit 文件。比如你在 /etc/systemd/system/ 放了一个,系统包又在 /usr/lib/systemd/system/ 带了一个。虽然优先级机制会选 /etc/ 下的,但如果文件名不同(如 myapp.service vs my-app.service),两个会同时存在。

排查

BASH
# 查找所有同名或类似的 Unit 文件
find /etc/systemd /usr/lib/systemd /run/systemd -name "myapp*" 2>/dev/null

# 查看当前生效的 Unit 文件路径
systemctl show myapp -p FragmentPath

修复

BASH
# 删除不需要的 Unit 文件
sudo rm /usr/lib/systemd/system/myapp.service

# 重新加载
sudo systemctl daemon-reload

⚠️ 建议:自定义服务统一放在 /etc/systemd/system/ 下,包管理器带的放在 /usr/lib/systemd/system/,两者不要混用。


坑 3:ExecStart 路径错误导致服务启动失败

症状systemctl status myapp 显示 code=exited, status=203/EXEC

原因:ExecStart 指定的可执行文件路径错误,或者文件没有执行权限。

排查

BASH
# 查看详细错误
systemctl status myapp -l

# 确认文件是否存在且有执行权限
ls -la /opt/myapp/server.js
file /opt/myapp/server.js

# 确认解释器路径正确
which node
# 或
/usr/bin/node --version

修复

BASH
# 确保文件有执行权限
chmod +x /opt/myapp/server.js

# 如果用的是绝对路径的解释器,确保路径正确
# 推荐:用 which 确认后再写入 ExecStart

坑 4:环境变量未传递导致应用报错

症状:手动运行正常,通过 systemd 启动就报错(如找不到数据库连接串)。

原因:systemd 启动的服务不会继承 shell 的环境变量(~/.bashrc/etc/environment 等都不加载)。

排查

BASH
# 在服务的 ExecStart 前加一个调试命令
# 临时修改 Unit 文件的 ExecStart 为:
ExecStart=/bin/bash -c 'env > /tmp/myapp-env.log && exec /usr/bin/node server.js'

# 查看 systemd 传递的环境变量
cat /tmp/myapp-env.log

修复:在 Unit 文件中显式声明环境变量:

INI
# 方法1:直接在 Unit 文件中设置
Environment=DATABASE_URL=postgres://user:pass@localhost:5432/mydb
Environment=NODE_ENV=production

# 方法2:加载外部 .env 文件
EnvironmentFile=/opt/myapp/.env

五、调试与监控

5.1 使用 journalctl 实时监控

BASH
# 实时跟踪服务日志
journalctl -u myapp -f

# 只看错误级别
journalctl -u myapp -p err

# 按时间范围查询
journalctl -u myapp --since "2026-05-06 10:00" --until "2026-05-06 12:00"

5.2 分析启动耗时

BASH
# 查看系统启动时间分析
systemd-analyze

# 查看各服务启动耗时排序
systemd-analyze blame

# 生成服务启动时序图(输出 SVG 文件)
systemd-analyze plot > boot-time.svg

5.3 验证 Unit 文件语法

BASH
# 验证单个文件
systemd-analyze verify /etc/systemd/system/myapp.service

# 验证所有系统 Unit
systemd-analyze verify --recursive --man=no /etc/systemd/system/

六、建议总结

  1. 命名规范:服务名用小写字母和连字符,如 my-app.service
  2. 统一目录:自定义服务放 /etc/systemd/system/,不要和软件包的混放
  3. 修改后 reload:每次修改 Unit 文件后必须执行 systemctl daemon-reload
  4. 用 journalctl 而非 tail:systemd 管理的服务日志统一用 journalctl 查看
  5. 安全加固:善用 NoNewPrivilegesProtectSystemProtectHome 等安全选项
  6. 合理设置重启策略:避免死循环重启,设置 StartLimitBurstRestartSec
  7. 使用 EnvironmentFile:敏感信息不要硬编码在 Unit 文件中,用 .env 文件管理

结语

systemd 功能强大但学习曲线不低。掌握了 Unit 文件的编写和调试技巧后,你可以轻松管理任何自定义服务。遇到问题时,记住三板斧:systemctl status 看状态 → journalctl -u 看日志 → systemd-analyze verify 验证语法

希望这篇文章能帮你在 Linux 运维中少踩坑。如果有其他 systemd 相关的问题,欢迎在评论区交流!

评论