systemd 与 journalctl 速查:unit 状态、失败重启计数、按时间与优先级过滤
服务起不来或反复重启时,systemctl status 只给结论,细节全在 journal 里。我的固定动作是三步:systemctl status 看当前状态与最近几行,systemctl show 看重启计数与退出码,journalctl -u ... --since 把时间窗打开看完整上下文。三步之外还有两个隐藏开关会影响结论:journal 是否持久化、以及速率限制是否已经把日志丢掉了。
unit 状态与重启
| 目的 | 命令 | 读什么 |
|---|---|---|
| 当前状态 | systemctl status app.service | Active: 一行(active/failed/inactive)、Main PID、末尾 10 行日志 |
| 是否失败 | systemctl is-failed app.service | 退出码 0 表示 failed,适合脚本判断 |
| 全机失败单元 | systemctl list-units --state=failed | 一次列出所有 failed,排查连带故障 |
| 重启次数与退出码 | systemctl show -p NRestarts -p ExecMainStatus -p Result app.service | NRestarts 持续增长说明在重启循环 |
| 依赖关系 | systemctl list-dependencies app.service | 上游 target 未起来时服务不会启动 |
| 开机自启 | systemctl is-enabled app.service | enabled/disabled/static |
| 配置是否生效 | systemctl cat app.service | 显示所有 drop-in 合并前的原文 |
| 改了配置之后 | systemctl daemon-reload && systemctl restart app.service | 只 restart 不 reload 会用旧单元定义 |
NRestarts 是判断"重启循环"的直接证据。如果它每分钟涨几次,配合 journalctl -u app.service -n 50 就能看到每次退出前的最后一行。ExecMainStatus 给的是主进程退出码,被信号杀掉时 Result 会显示 signal,此时对照 dmesg 里的 OOM 记录能快速区分"自己退出"和"被杀"。
journalctl 过滤
| 目的 | 命令 | |
|---|---|---|
| 指定单元 | journalctl -u app.service | |
| 本次启动 | journalctl -b / 上次启动 journalctl -b -1 | |
| 时间窗 | journalctl --since "2026-11-10 09:00:00" --until "2026-11-10 10:00:00" | |
| 相对时间 | journalctl --since "-30min" / --since today / --since yesterday | |
| 优先级 | journalctl -p err / 区间 -p warning..crit | |
| 正则搜 | `journalctl -g "timeout | refused"` |
| 内核消息 | journalctl -k -b | |
| 只看消息体 | journalctl -o cat -u app.service | |
| 结构化输出 | journalctl -o json-pretty -n 5 -u app.service | |
| 磁盘占用 | journalctl --disk-usage | |
| 清理到指定大小 | journalctl --vacuum-size=500M |
-p 的语义容易记错:给单个级别时,显示的是该级别及更严重(数值更小)的所有消息,-p err 等价于 err、crit、alert、emerg 四档,不是只有 err。要看区间就写 -p err..debug 这种 FROM..TO 形式。
三个容易踩的开关
第一,journal 可能根本没落盘。Storage= 默认由编译期决定,auto 的行为是"/var/log/journal 存在就持久化,否则只写内存"。所以在 /run/log/journal 里翻历史,重启后就没了。判断方法很直接:ls -d /var/log/journal 存在才算持久化,journalctl --disk-usage 的数字也才有长期意义。
第二,速率限制会静默丢日志。RateLimitIntervalSec= 与 RateLimitBurst= 默认是 30 秒内 10000 条,超出的消息被丢弃,只在日志里留一条"suppressed"提示。刷屏的服务在故障时恰好是最需要日志的时候,这时候要把这两个值调大或置 0,并接受由此带来的磁盘增长。
第三,时间过滤要用 short-full 的格式核对。--since/--until 接受 2012-10-30 18:17:16 这种写法,省略时间部分会补 00:00:00,省略日期会补今天。输出默认是 short,它不带年份,肉眼对照容易错位;加 -o short-full 就能看到与过滤参数完全一致的时间戳。
unit 里最常改的几行
[Service]
Restart=on-failure # 只在非零退出/被杀时重启
RestartSec=5s # 重启间隔,太短会打满 journal
StartLimitIntervalSec=60 # 与 StartLimitBurst 配合,限制重启风暴
StartLimitBurst=5
ExecStart=/usr/local/bin/app --config /etc/app.conf
Restart=always 与 on-failure 的区别在"正常退出要不要拉起"。一次性任务用 Type=oneshot 且不要配 Restart,否则每次成功退出都会被当成需要重启。StartLimitBurst 是防止无限重启的最后一道闸:超过次数后 systemd 会把 unit 标记为 failed 并停止尝试,此时 NRestarts 停止增长,status 里会出现 "start request repeated too quickly" 这样的提示。
启动即退出的三步判断
第一步看 status 的 Active: 行与末尾日志,判断是"从没起来"还是"起来后退了"。第二步看 show -p ExecMainStatus -p Result,退出码 0 但状态 failed 通常意味着 Type= 配错(比如把常驻进程写成 oneshot);Result=signal 要去 dmesg 里找 OOM 或段错误。第三步才去看应用自己的日志文件——如果 ExecStart 的输出没有进 journal,说明进程自己重定向了 stdout,这时 journal 里只有 systemd 的启停记录,看不到应用输出,需要去应用配置的路径找。
排查顺序反过来做也能成立,但会多花时间:先翻应用日志往往看到的是"启动成功"然后戛然而止,根因(端口被占、配置文件权限、依赖服务未就绪)反而在 systemd 侧的状态里。
日志量与保留策略
journal 落盘后会一直增长,直到碰到上限。上限由 SystemMaxUse= 控制,默认是所在文件系统的 10%、且封顶 4G;单个文件的上限由 SystemMaxFileSize= 控制,默认是 SystemMaxUse= 的八分之一、封顶 128M。这两个默认值意味着:一个 500G 的数据盘上,journal 最多也只用到 4G 左右,不会出现"日志把盘写满"的情况——前提是持久化真的开着。
调整只需要一个 drop-in,不要直接改 /usr/lib 下的厂商文件:
mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nSystemMaxUse=2G\nSystemMaxFileSize=256M\n' > /etc/systemd/journald.conf.d/99-size.conf
systemctl restart systemd-journald
journalctl --disk-usage
--vacuum-size= 只对已归档的文件生效,所以清理前先 --rotate 把当前活跃文件归档,回收量才对得上 --disk-usage 显示的差值。这一点手册里有明确提示,实际执行时很容易忽略,然后误以为"清了没用"。
小结
排查服务类故障的顺序建议固定为:先 list-units --state=failed 确认范围,再 status + show 拿到状态与重启计数,最后用 journalctl -u --since 拉时间窗。这套顺序的好处是每一步都有可复核的输出,写故障报告时直接把命令和输出贴进去即可。
需要留意的边界是容器。debian:12-slim 这类镜像里既没有 systemctl 也没有 journalctl,容器内 command -v systemctl 返回空,日志由容器运行时收集到别处(docker logs 或宿主 journal)。在容器里按文中命令操作会直接报 command not found,这属于环境边界,不是配置错误。
数据来源与检索时间
| 来源 | URL | 对应数据 |
|---|---|---|
| man7.org · journalctl(1) | https://man7.org/linux/man-pages/man1/journalctl.1.html | --since/--until 时间语法、-p 单值与区间语义、-b、-u、-g、-o 输出格式、--disk-usage、--vacuum-size |
| man7.org · journald.conf(5) | https://man7.org/linux/man-pages/man5/journald.conf.5.html | Storage= 的 volatile/persistent/auto 行为、RateLimitIntervalSec/RateLimitBurst 默认 10000 条/30 秒、SystemMaxUse= 上限 |
检索时间:2026-11-10(Asia/Shanghai)。
核验说明
- 真跑(环境边界确认):在
debian:12-slim容器内执行command -v systemctl与command -v journalctl均无输出,确认该镜像不含 systemd,因此文中所有systemctl/journalctl命令未在本机执行。 - 仅依据文档,未在本机运行:
NRestarts/ExecMainStatus的实际取值、-p err..crit的区间过滤效果、--vacuum-size的回收量、速率限制触发后的 suppressed 提示,均按上面两个手册页描述记录,未构造服务验证。 - 未验证:
Storage=auto在/var/log/journal存在与不存在两种布局下的落盘差异,属文档明文但本机无法运行 systemd 验证。