Linux 与系统

systemd 服务管理与日志排查:配置、启动语义与故障定位

后台进程需要有人负责启动、停止、收集日志和处理异常退出。systemd 的 service unit 声明这些规则。排障时要区分 unit 配置是否加载、进程是否存活,以及业务是否已经可用。

后台进程需要有人负责启动、停止、收集日志和处理异常退出。systemd 的 service unit 声明这些规则。排障时要区分 unit 配置是否加载、进程是否存活,以及业务是否已经可用。

先读当前状态

bash4 行
systemctl status nginx --no-pager
systemctl cat nginx
systemctl show nginx -p FragmentPath -p MainPID -p ActiveState -p SubState -p Result
journalctl -u nginx -b --no-pager -n 100

status 提供摘要,cat 展示主文件与 drop-in,show 便于读取结构化属性。日志用启动批次和时间窗定位,先看最早异常,再看重复重启的结果。

enable 管理开机依赖链接,start 启动当前实例;enable --now 同时执行这两件事。执行 daemon-reload 让管理器重新读取 unit 文件,应用自身配置是否重读还取决于 reload 或重启操作。

单元文件目录与加载优先级

unit 文件可以放在多个目录,加载时按优先级合并。本地安装或覆盖的单元要放在优先级更高的目录。

优先级 目录路径 说明
最高 /etc/systemd/system/ 本地管理员自定义或覆盖的单元文件
中 /run/systemd/system/ 运行时动态生成的单元
低 /usr/lib/systemd/system/ 软件包安装提供的默认单元
低 /lib/systemd/system/ 旧路径,通常是上一项的符号链接
低 /usr/local/lib/systemd/system/ 本地手动安装的单元

查看完整的搜索路径列表:

plaintext1 行
systemd-analyze unit-paths

/usr/lib/systemd/system/ 属于软件包,下次升级会被覆盖,不要直接修改。需要调整时改用下面的 drop-in 机制,在 /etc/systemd/system/ 下覆盖。

写清启动与退出约定

下面是一个前台运行的示例应用单元,保存为 /etc/systemd/system/example-app.service。app 用户、程序路径及工作目录需要先准备好。

ini20 行
[Unit]
Description=Example application
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=exec
User=app
Group=app
WorkingDirectory=/opt/example-app
ExecStart=/opt/example-app/bin/server
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
NoNewPrivileges=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target

Type=exec 能发现可执行文件启动失败,但业务端口可能还未准备好。支持 sd_notify 的应用可使用 Type=notify 报告就绪;Type=forking 对应会派生后台进程的传统服务。选项含义见 systemd.service。

After=network.target 指定排序,不保证 DNS 或远程数据库可用。应用仍需要处理依赖暂时不可用的情况。重启策略也必须有间隔和频率限制,避免错误配置引发连续启动。

bash3 行
sudo systemd-analyze verify /etc/systemd/system/example-app.service
sudo systemctl daemon-reload
sudo systemctl enable --now example-app

随后访问真实业务端点。服务 active 和请求成功是两项独立检查。这里的 5 秒重启间隔和 30 秒停止预算只是示例参数,应按应用关闭连接与落盘所需时间调整。

使用 drop-in 修改现有服务

通过 systemctl edit example-app 创建覆盖配置,避免修改发行版管理的 unit 文件。它会在 /etc/systemd/system/example-app.service.d/override.conf 打开编辑窗口,保存后自动执行 daemon-reload。覆盖列表型指令时要核对合并语义,例如替换 ExecStart 常需先写空的 ExecStart= 清空原值。

systemd 加载单元时自动扫描 <单元名>.d/ 目录下的 .conf 文件,按目录优先级合并进主配置,后面的覆盖前面的。不在 /etc/systemd/system/ 直接改动,也方便追查哪些文件参与覆盖。

修改前记录现有 drop-in,修改后执行语法验证和 systemctl daemon-reload。涉及 ExecStart、运行用户或环境变量时,还需 systemctl restart example-app 创建新进程,再检查 MainPID、实际命令行和业务响应。应用自身支持的 reload 仅用于对应应用配置。回滚时恢复此次修改前的文件,再执行相同步骤;已有其他人的覆盖配置时,不能直接用全量恢复命令抹掉它们。

日志查看:journalctl

systemd 把日志统一收到 journald,由 journalctl 查询。先分清三个概念:

概念 说明
systemd-journald 后台服务,负责收集日志(内核、服务 stdout/stderr、syslog 转发等)
journal 日志数据库,位于 /run/log/journal/(内存)或 /var/log/journal/(持久化)
journalctl 用户态查询、过滤、导出命令

基础查询

plaintext11 行
# 查全部(默认分页)
journalctl

# 不分页直接输出
journalctl --no-pager

# 最近 N 行
journalctl -n 100

# 实时跟随(像 tail -f)
journalctl -f

按服务过滤

调试某个服务起不来时最常用:

plaintext3 行
journalctl -u nginx
journalctl -u nginx -n 200
journalctl -u nginx -f

按启动会话过滤

plaintext8 行
# 本次启动以来的日志
journalctl -b

# 上一次启动的日志(服务刚重启崩溃时可查)
journalctl -b -1

# 列出所有启动会话
journalctl --list-boots

按时间过滤

plaintext5 行
journalctl --since today
journalctl --since yesterday
journalctl --since "10 min ago"
journalctl --since "1 hour ago"
journalctl --since "2026-06-01 09:00:00" --until "2026-06-01 10:00:00"

按日志级别过滤

plaintext3 行
journalctl -p err        # 仅错误
journalctl -p warning    # 警告及以上
journalctl -p info       # info 及以上

优先级从低到高:debug → info → notice → warning → err → crit → alert → emerg。

按进程 / 用户过滤

plaintext6 行
# 按 PID
journalctl _PID=1234

# 当前用户的用户服务日志
journalctl --user
journalctl --user -u myservice

查询用户服务(--user)的日志要求该用户的 systemd 实例已启用:未开 linger 时,只有处于活跃登录会话才能读取,必要时 loginctl enable-linger <user> 保持实例常驻。

日志占用与清理

服务器跑久了 /var/log/journal/ 可能占掉数 GB:

plaintext11 行
# 看占用
journalctl --disk-usage

# 按大小清理到 200MB
sudo journalctl --vacuum-size=200M

# 按时间清理(保留 7 天)
sudo journalctl --vacuum-time=7d

# 按文件数清理
sudo journalctl --vacuum-files=5

可以放进 cron 或 systemd timer 定时执行。

持久化日志

journald 默认可能只存内存,重启后日志丢失,/var/log/journal/ 不存在就是未持久化的表现。检查:

plaintext1 行
ls /var/log/journal/ 2>/dev/null || echo "未持久化"

编辑 /etc/systemd/journald.conf 启用持久化并限制占用:

plaintext4 行
[Journal]
Storage=persistent
SystemMaxUse=500M
SystemKeepFree=1G
配置项 含义
Storage=persistent 强制写到磁盘
Storage=auto(默认) 有 /var/log/journal/ 就持久化,没有就内存
SystemMaxUse=500M journal 最多占 500MB
SystemKeepFree=1G 磁盘至少留 1GB 给其它用途

改完重启 journald:

plaintext1 行
sudo systemctl restart systemd-journald

从退出原因收敛故障

观察 排查方向 后续动作
executable 或权限错误 路径、执行权限、挂载参数 用服务用户验证可读与可执行权限
端口占用 已有实例或冲突配置 ss -lntp 查占用进程
反复重启 程序异常、依赖失败、启动超时 先修首个异常,再重置失败状态
SIGKILL / 内存相关日志 主机或 cgroup 内存压力 联查内核日志和 memory.events
active 但请求失败 应用路由、连接池、远程依赖 发业务请求并关联应用日志

Restart= 可以恢复进程,数据一致性仍由应用和存储协议保证。对写操作,恢复后还要检查提交状态与重复执行的处理方式。

排查服务启动失败的标准流程

新装服务跑不起来时按顺序排查,同时兼顾配置层面与进程层面:

plaintext13 行
# 1. 看状态
systemctl status nginx

# 2. 看最近 100 行日志
journalctl -u nginx -n 100

# 3. 实时跟随日志并手动重启
journalctl -u nginx -f &
systemctl restart nginx

# 4. 仍定位不到时,直接前台跑 ExecStart 看错误输出
systemctl cat nginx     # 找到 ExecStart 字段
/usr/sbin/nginx -g 'daemon off;'   # 直接执行,观察输出

异常对应检查方向:端口占用用 ss -lntp;文件找不到检查路径与权限;配置语法错误可直接前台执行复现。

systemctl 命令速查

命令 含义
systemctl status <unit> 查看服务详细状态
systemctl start <unit> 立即启动
systemctl stop <unit> 立即停止
systemctl restart <unit> 重启(先停再启)
systemctl reload <unit> 重新加载配置(不中断,需服务支持)
systemctl enable <unit> 设置开机自启
systemctl disable <unit> 取消开机自启
systemctl is-enabled <unit> 查询是否开机自启
systemctl is-active <unit> 查询是否正在运行
systemctl list-units --type=service 列出所有服务单元
systemctl list-unit-files --type=service 列出所有服务文件及启用状态
systemctl daemon-reload 重新加载所有单元文件
systemctl edit <unit> 创建 drop-in 覆盖配置
systemctl cat <unit> 查看完整生效配置(含覆盖)
systemctl show -P FragmentPath <unit> 查这个单元真正加载的是哪个文件

排查“改的配置有没有生效”时,先看 systemctl show -P FragmentPath nginx.service 确认加载的文件,再看 systemctl cat nginx.service 的合并结果。在 /etc/systemd/system/ 写了同名 .service 却忘了 daemon-reload,管理器仍用旧文件,怎么 restart 都无效。

实用经验

  • 改配置必 daemon-reload:单元文件改完不 reload 等于没改。
  • 不要直接改 /usr/lib/systemd/system/ 下的文件,用 systemctl edit 创建 drop-in。
  • 生产服务 enable 带 --now:systemctl enable --now nginx 等于 enable + start。
  • journalctl -u xxx -f 是调试新服务最直接的方式,开一个终端常驻。
  • journal 太大别全删,按 --vacuum-time=30d 保留近期内容。
← 返回文章