Administrator
Administrator
发布于 2026-10-29 / 1 阅读
0
0

systemd 服务启动失败怎么查:从 status 到 journalctl 的完整练习

适合读者:会使用 ls、cd 和 sudo,准备独立排查 Linux 服务的初级运维人员。本文以 systemd 为前提,实验使用单独的用户服务;报错和输出均为教学示例,具体以机器上的日志为准。

systemd 服务排查路线:状态、实际配置、执行环境、日志、验证

1. 服务显示 failed,先保留什么信息

某次修改配置后,网站没有启动。执行 systemctl start 只看到一句“启动失败”,这时需要同时回答三个问题:systemd 启动了哪个程序、程序以什么环境运行、程序为什么退出。

先把下面的 your-app.service 替换成真实服务名,只做查看:

systemctl status your-app.service --no-pager -l
sudo journalctl -u your-app.service -b -n 100 --no-pager
systemctl cat your-app.service

status 给出当前状态和最近日志;journalctl -u 聚焦指定服务,-b 限定本次开机;cat 会显示单元文件及相关覆盖文件。服务可能有 drop-in 覆盖配置,只读某个磁盘文件容易漏掉它们。日志筛选方式见 journalctl 官方手册。

记录报错时间和退出原因后,再决定是否修改或重启。命令行最后一句“Job failed”通常只是结果,真正原因往往在它前面。

2. 先读懂状态,不把所有失败混在一起

现象 说明 下一步
Unit ... could not be found 管理器没有找到该单元 检查名称、文件位置和是否需要 daemon-reload
status=203/EXEC systemd 在执行程序这一步失败 查路径、执行权限、解释器、文件格式和安全策略
status=200/CHDIR 无法切换到工作目录 查 WorkingDirectory 是否存在、是否可进入
status=1/FAILURE 程序已经运行,但以非零码退出 看程序自己的错误输出
Start request repeated too quickly 短时间反复启动触发了限制 先找最早一次失败,再考虑重置失败计数
active (exited) 进程已退出,但该单元仍被视为活动 结合 Type、RemainAfterExit 和服务用途判断

退出码的定义可查 systemd.exec。203/EXEC 只能定位到“执行阶段”,无法单凭这个数字断言一定是少了执行权限。

3. 为什么手动运行成功,交给 systemd 就失败

交互式终端和服务启动环境可能不一样。最常见的差异是:

  • 用户不同:终端里是自己,系统服务可能配置了 User=app。
  • 当前目录不同:程序使用相对路径读取 config.json,实际却从另一个目录启动。
  • 环境变量不同:终端的虚拟环境、代理和 PATH 不一定存在于服务中。
  • 启动命令不同:手动执行带了参数,单元文件却漏了。

可以查看 systemd 已加载的关键属性:

systemctl show your-app.service \
  -p FragmentPath -p DropInPaths -p User -p Group \
  -p WorkingDirectory -p ExecStart -p Result -p ExecMainStatus

系统服务需要提权才能修改;下面的练习改用用户服务,因此后续命令都带 --user。两种管理器不能混着查:systemctl status 和 systemctl --user status 查的不是同一个位置。

4. 实验:复现一个“找不到配置文件”的失败

准备一台已有 Python 3、支持 systemd 用户管理器的 Linux 测试机。先确认环境:

command -v python3
systemctl --user is-system-running
mkdir -p "$HOME/.local/share/ops-service-lab"
mkdir -p "$HOME/.config/systemd/user"

如果 systemctl --user 提示无法连接用户总线,需要先使用正常的登录会话或排查用户管理器;此时还没有运行到本实验的应用。

使用编辑器把下面内容保存为 ~/.local/share/ops-service-lab/app.py:

import json
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path

config = json.loads(Path("config.json").read_text(encoding="utf-8"))

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = b"ok\n" if self.path == "/healthz" else b"not found\n"
        self.send_response(200 if self.path == "/healthz" else 404)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

print("starting demo", flush=True)
HTTPServer(("127.0.0.1", int(config["port"])), Handler).serve_forever()

再保存 ~/.config/systemd/user/ops-demo.service:

[Unit]
Description=Operations learning demo

[Service]
Type=simple
WorkingDirectory=%h/.local/share/ops-service-lab
ExecStart=/usr/bin/python3 -u %h/.local/share/ops-service-lab/app.py
Restart=no
StandardOutput=journal
StandardError=journal

示例假设 Python 位于 /usr/bin/python3,应与前面 command -v 的结果核对。%h 是用户家目录的 systemd 说明符,程序保持前台运行。本实验没有设置自动重启,方便观察第一次失败。

现在故意不创建 config.json,执行:

systemctl --user daemon-reload
systemctl --user start ops-demo.service
systemctl --user status ops-demo.service --no-pager -l
journalctl --user -u ops-demo.service -b -n 50 --no-pager

预期可以在日志里找到 FileNotFoundError,并指向 config.json。这条信息比“服务 failed”具体得多:Python 能执行,应用在读取配置阶段失败。

如果实际出现 Address already in use,说明你遇到的是端口冲突;如果是 203/EXEC,则要先查 Python 路径。按真实证据进入相应分支,不要强行套用示例结论。

5. 修复:让配置位置和工作目录对应起来

在 ~/.local/share/ops-service-lab/config.json 中保存:

{
  "port": 18180
}

单元文件里的 WorkingDirectory 正好是这个目录,程序读取的相对路径便能对应到该文件。检查后重新启动:

systemctl --user restart ops-demo.service
systemctl --user is-active ops-demo.service
curl --max-time 5 -i http://127.0.0.1:18180/healthz

预期服务为 active,HTTP 状态为 200,正文为 ok。这里“启动成功”和“接口能用”是两次不同的验证:进程活着并不自动保证业务功能正常。

这个程序必须在服务启动时读到有效 JSON。端口冲突、JSON 语法错误等仍会让它退出,可以继续用相同路线查看日志。

6. daemon-reload、reload、restart 分别在改什么

命令 主要用途
systemctl daemon-reload 让 systemd 重新读取单元定义及依赖信息
systemctl reload 服务名 请求服务重载自己的配置,前提是该服务支持
systemctl restart 服务名 停止后重新启动服务进程
systemctl reset-failed 服务名 清除失败状态及相关启动限制计数,不修复程序

比如,改了 ExecStart=,通常需要 daemon-reload 后再重启服务。只改应用的 JSON 配置并不需要重新读取单元定义;本例程序也没有实现热加载,因此要重启进程才能读取新配置。命令语义见 systemctl 官方手册。

生产环境是否可以重启,需要结合连接中断、主从切换和变更窗口判断。排查阶段先把原因定位到具体文件、用户或端口,修复动作才容易控制。

7. 处理完成后留下一条可复用记录

至少写清楚:发生时间、原始错误、实际原因、修改内容和验证结果。例如:“服务能执行 Python,但工作目录下缺少 config.json;补齐配置后,服务 active,/healthz 返回 200。”

实验结束可停止这个单独的用户服务:

systemctl --user stop ops-demo.service

本次练习建立的路线是:看状态 → 找第一次错误 → 核对实际单元与执行环境 → 修改原因对应的配置 → 同时验证进程和接口。下次遇到 failed,便有一条可以重复使用的路径。


评论