上周帮朋友排查一台CentOS 7.9上的内部网关,服务凌晨三点悄悄退出,systemd把它标记成failed,但因为没有人盯控制台,第二天早上整个链路断了好几个小时才被发现。这种场景我见过太多次了。不是服务本身多难维护,而是systemd其实把状态、日志、资源占用全都摆在那里,关键是你有没有一套顺手的方法去实时盯住它。
这篇文章就围绕systemd服务的实时监控展开,把我平时真正在用的命令组合、脚本思路、踩坑记录整理出来。适合运维、后端开发、以及所有自己维护Linux服务器的同学。不管你是刚接触systemd的新手,还是已经在生产环境摸爬滚打的老手,里面应该都有你能直接拿走用的东西。
1. 先搞清楚监控目标:systemd服务到底要盯什么
很多人一说到监控systemd服务,第一反应就是“服务挂了没有”,然后写个脚本判断 systemctl is-active 是不是active。这个思路没错,但太粗糙了。实时监控的关键不只是知道服务死没死,而是要能在它“快死”、“假死”、“半死不活”的时候提前发现问题。
1.1 服务生命周期状态:不只是active/inactive
systemd把unit的状态拆得很细,systemctl status 里会显示两行:Loaded和Active。Loaded描述unit文件有没有被正确加载,Active描述服务当前处于什么生命周期阶段。Active这一行除了active(running),还有active(exited)、active(waiting)、inactive(dead)、failed、activating、deactivating。每种状态背后的含义完全不同。
我举个例子,active(exited) 这个状态很容易被误判。它表示服务进程正常退出了,但systemd认为这是“预期行为”,所以不算故障。像一些oneshot类型的服务,执行完任务退出,显示的就是这个状态。如果你只判断“不是active就是挂了”,那就误报了。反过来,activating 表示服务正在启动过程中,但如果一个服务长时间停留在activating,那很可能是启动脚本卡住了,这时候进程还活着,但服务实际上已经不可用。
1.2 依赖关系与目标(target)视角
systemd服务的另一个特点是有依赖关系。一个服务挂了,可能连带一票服务起不来。很多人在单个服务上盯了半天,没发现问题,实际上根源在它的依赖上。systemctl list-dependencies 能列出服务的依赖树,systemd-analyze critical-chain 能告诉你当前启动路径上最耗时的环节。
从监控的角度看,我建议定期(比如每天一次)跑一遍 systemctl list-dependencies --reverse,看看哪些服务依赖你负责的核心服务。这样一旦核心服务抖动,你能提前知道影响面有多大,而不是等下游服务全挂了才去一个一个查。
1.3 日志与资源消耗:状态的“隐性指标”
服务状态是“果”,日志和资源消耗是“因”。一个服务反复重启,systemctl status 里可能只看到 active (running),但 journalctl 里全是报错。不盯日志,你根本不知道它在“带病运行”。
systemd通过CGroup统计服务的资源使用情况。systemctl status 里会直接显示Tasks、Memory、CPU占用,这些数据背后是CGroup在实时统计。对于内存泄漏类问题,盯住Memory这个字段的涨势比看什么监控图都直观。我踩过的坑是,某些服务在内存涨到一定程度后,会被OOM Killer干掉,systemd自动拉起,然后继续涨,形成一个循环。只看status永远都是active,但看Memory曲线就能发现规律。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实时监控三板斧:systemctl、journalctl、watch的配合
systemd生态里最常用的实时监控工具就是systemctl和journalctl,但很多人只用了它们最基本的功能。其实把watch命令和它们组合起来,就能形成一套不需要装任何额外软件就能用的实时监控方案。
2.1 systemctl status:用好这个被低估的入口
systemctl status 显示的信息非常丰富,但很多人只瞟一眼Active那行就完事了。我建议你仔细看这几个字段:
bash复制systemctl status nginx
输出里的Main PID、Tasks、Memory、CGroup这几个字段在实时排查时非常有用。Main PID告诉你服务主进程的PID,排查时可以快速定位到这个进程去查线程、连接数。CGroup段会列出服务拉起的所有子进程,如果发现服务有“野孩子”进程,在这里一眼就能看出来。
另外,systemctl status 末尾会显示最近几条日志,这些日志其实就是从journald里捞出来的,有时候不用专门敲journalctl也能快速判断问题。我习惯先敲status看一眼,再决定要不要深入查日志。
2.2 journalctl -f:日志流的实时盯梢
journalctl的实时模式是 -f 参数,类似 tail -f。配合 -u 指定服务,就能只盯着一个服务的日志看。
bash复制journalctl -u nginx -f
这个组合适合在发布、重启、排查故障时使用。但我建议再加两个参数:-n 控制显示历史条数,--since 控制时间范围。
bash复制journalctl -u nginx -n 100 -f
先显示最近100条,然后持续跟随新日志。这样启动服务时,能看到日志从启动到运行的全过程,而不是只看到新输出的几行。
journalctl还有几个特别好用的过滤维度。按优先级过滤 -p err,只看错误级以上的日志;按时间过滤 --since "10 minutes ago",只查最近十分钟;按启动周期 -b -1,查上一次启动的日志。这些参数单独用都不复杂,组合起来就能快速定位“某某时间点服务到底发生了什么”。
2.3 watch + systemctl:让状态“动”起来
systemctl status 默认输出一次就结束了,但如果用watch包一层,就能实现类似“实时刷新”的效果。
bash复制watch -n 2 systemctl status nginx
每两秒刷新一次状态页,屏幕上的Tasks、Memory、Active状态都会动态变化。这个组合在压测、排查性能瓶颈时特别顺手。我曾经排查过一个Java服务内存持续上涨的问题,就是开着这个命令,盯着Memory字段一路涨到OOM,整个过程一目了然。
用watch还能盯多个服务的状态汇总。比如:
bash复制watch -n 5 'systemctl list-units --type=service --state=running | grep -E "nginx|mysql|redis"'
这个命令每5秒刷新一次,只显示三个服务的运行状态。我建议把这类命令整理成别名放在 .bashrc 里,要用的时候直接敲一个词就行。
注意:watch默认每2秒刷新一次,对systemctl status这种轻量命令没压力。但如果服务数量非常多,或者你盯的是Docker容器里跑的systemd服务,刷新间隔可以适当拉长到5秒,避免浪费CPU。
3. 进阶:自建一个轻量级实时监控脚本
命令组合适合人肉盯,但总不可能24小时守在终端前面。我建议写一个简单的shell脚本,把核心服务的状态、日志、资源消耗做成一屏显示,再配合循环刷新,相当于一个极简版监控面板。这里分享一个我实际在用的脚本,代码不复杂,胜在灵活。
3.1 脚本设计思路与关键点
脚本的核心逻辑是循环检查目标服务,把状态和关键指标汇总输出。我用的关键命令是 systemctl show,它能输出服务的详细属性,比 systemctl status 更适合脚本解析。
systemctl show 的看点在于,你可以用 -p 参数只提取某个属性,比如:
bash复制systemctl show nginx -p ActiveState -p SubState -p MainPID -p MemoryCurrent
输出格式是 Key=Value,用shell处理起来很直接。ActiveState是服务是否活跃的状态,SubState是更细的子状态(running、exited、failed等),MainPID是主进程号,MemoryCurrent是当前内存占用。这些都是实时数据,直接从CGroup读出来的。
另一个关键点是日志错误的实时检测。我利用journalctl的 --since 参数,每次循环只检查最近1分钟内的错误日志数量:
bash复制systemctl show nginx -p ActiveState
journalctl -u nginx --since "1 minute ago" -p err --no-pager | wc -l
两条命令组合,就能同时判断“服务是否活着”和“最近有没有报错”。这个思路比单纯看active状态要全面得多。
3.2 完整脚本与运行效果
下面是我整理后的一个监控脚本,你可以直接保存成 svc_monitor.sh 使用:
bash复制#!/bin/bash
# 实时监控systemd服务状态
# 用法: ./svc_monitor.sh [服务名] [刷新间隔秒数]
# 示例: ./svc_monitor.sh nginx 3
SERVICE="${1:-nginx}"
INTERVAL="${2:-3}"
while true; do
clear
echo "==== systemd 服务实时监控: $SERVICE (每${INTERVAL}秒刷新) ===="
echo
# 服务状态与资源
ACTIVE=$(systemctl show "$SERVICE" -p ActiveState --value)
SUB=$(systemctl show "$SERVICE" -p SubState --value)
PID=$(systemctl show "$SERVICE" -p MainPID --value)
MEM=$(systemctl show "$SERVICE" -p MemoryCurrent --value)
TASKS=$(systemctl show "$SERVICE" -p TasksCurrent --value)
# 单位转换
if [ "$MEM" -gt 1048576 ]; then
MEM_READABLE="$(echo "scale=1; $MEM/1048576" | bc)M"
else
MEM_READABLE="${MEM}K"
fi
echo "ActiveState : $ACTIVE"
echo "SubState : $SUB"
echo "MainPID : $PID"
echo "Memory : $MEM_READABLE"
echo "Tasks : $TASKS"
echo
# 最近1分钟错误日志数
ERR_COUNT=$(journalctl -u "$SERVICE" --since "1 minute ago" -p err --no-pager 2>/dev/null | wc -l)
if [ "$ERR_COUNT" -gt 0 ]; then
echo "!! 最近1分钟错误日志: $ERR_COUNT 条"
journalctl -u "$SERVICE" --since "1 minute ago" -p err --no-pager | tail -5
else
echo "最近1分钟错误日志: 0 条"
fi
echo
echo "按 Ctrl+C 退出"
sleep "$INTERVAL"
done
脚本里用 --value 参数直接从 systemctl show 输出中提取属性值,比用sed/awk去解析 Key=Value 格式简洁多了。内存单位转换那段是为了可读性,KB数值太大看着累,转成MB更直观。
运行效果就是每3秒刷新一屏,上面是服务状态,下面是错误日志统计。这个脚本你完全可以根据自己的需求改,比如增加多个服务循环监控、把输出重定向到文件、或者加点颜色区分状态。
3.3 systemd-analyze:启动过程与时间损耗定位
实时监控不只是盯运行中的状态,还要关注服务启动过程。systemd-analyze 系列命令就是专门干这个的。它有三个子命令我经常用:
bash复制systemd-analyze blame
systemd-analyze critical-chain
systemd-analyze verify /etc/systemd/system/xxx.service
blame 按耗时从高到低列出所有服务的启动时间,可以快速找出启动慢的“罪魁祸首”。critical-chain 展示系统启动的关键路径,能看出是哪个服务拖慢了整体启动时间。这两个命令在排查“为什么开机这么慢”时非常好用。
verify 则是用来验证unit文件格式的。写好的service文件在正式部署前,先跑一遍 systemd-analyze verify,可以把配置里的拼写错误、非法参数提前暴露出来。我见过太多因为unit文件里一个单词拼错,导致服务起不来的案例。
启动时间的优化在实时监控里经常被忽视,但对那些依赖快速恢复的服务来说,启动慢本身就是一种故障。比如一个网关服务需要30秒才能完成启动,那每次重启就意味着至少30秒的业务中断,这个指标值得重点盯。
4. 高频故障场景排查实录
实时监控最大的价值体现在故障排查时。这里整理几个我实际遇到的systemd相关故障场景,附带排查思路和结论。
4.1 WSL中systemd用户会话失败的经典问题
在WSL(Windows Subsystem for Linux)里跑systemd服务时,常会遇到这样一个报错:
text复制wsl: failed to start the systemd user session for 'root'. see journalctl for details
这个报错出现时,很多服务虽然看起来是active状态,但用户态的systemd会话并没有真正启动。排查的第一步是 /etc/wsl.conf 里的systemd配置项:
ini复制[boot]
systemd=true
如果这一项没配,那WSL默认不启用systemd,很多依赖systemd的服务根本起不来。配置了但报错,就得看journalctl里具体是什么原因。常见的情况是systemd版本和WSL内核不兼容,或者用户目录权限有问题。
我建议在WSL里使用systemd前,先用 systemctl --version 确认版本,再确认 /etc/wsl.conf 配置正确,最后重启WSL让配置生效。排错时把 journalctl -b -p err --no-pager 全部打出来,比一个个猜要快得多。
4.2 CentOS/Oracle Linux中systemd服务的兼容坑
CentOS和Oracle Linux虽然同属RHEL系,systemd版本和默认配置也不完全一样。Oracle Linux特别容易遇到的问题,是服务脚本里用了只在新版本systemd才支持的参数,在旧版本上直接报错。
比如 systemctl show 的 --value 参数,在systemd 231版本之后才支持。CentOS 7自带的systemd 219版本就没有这个参数,你写脚本时用了 --value,跑起来就会提示invalid option。这是个很容易踩的兼容坑。我的建议是写成兼容方式:先用 -p 提取,再用 cut -d= -f2 取值,牺牲一点简洁度,换兼容性。
另一个CentOS 7上的典型问题,是服务里用了 ExecStartPost 和 ExecStopPost,这两个参数在CentOS 7上是支持的,但CentOS 7.2之前存在一些bug,可能导致服务停止后残留进程。这类问题的排查思路,是遇到诡异行为时先看systemd版本,再决定是不是版本bug。
4.3 xinetd和systemd:老派服务托管方式的对比
很多老管理员习惯了xinetd托管服务的方式,一个 rsync、一个 telnet,都放在xinetd底下来按需启动。在实际运维中,systemd已经替代了xinetd的大部分场景。xinetd的核心思路是“超级服务”:由一个常驻进程监听多个端口,客户端连接时才拉起对应服务进程。这种方式省内存,但每次连接都有拉起开销,而且配置复杂。
systemd用socket激活机制实现了类似效果。一个服务可以通过 .socket 单元监听端口,真正有连接进来时才启动 .service。配置比xinetd简洁,而且能享受到systemd全套的依赖管理、日志收集、资源统计能力。
如果你的系统里还留着xinetd托管的服务,我的建议是逐步迁移到systemd的socket激活。迁移过程不复杂,但要注意:xinetd默认的并发模型是每个连接拉起新进程,而systemd的socket激活默认是单实例模式,高并发场景下可能需要调整服务参数。
4.4 Kubernetes监控链中systemd的隐性角色
在Kubernetes集群里,systemd其实扮演着一个容易被忽略的角色。每个节点上的kubelet通常由systemd托管,节点上的其他核心服务(如容器运行时)也一样。当kubelet异常退出,整个节点会变成NotReady状态。
kube-state-metrics会暴露节点状态,但它监控的是Kubernetes对象,不会直接监控systemd服务。如果kubelet挂了,kube-state-metrics本身可能不受影响,能正常输出数据,但Kubernetes API里的节点状态已经变了。这个场景下,如果只盯kube-state-metrics的指标,可能发现不了问题。需要去节点上查systemd服务状态,才能定位根因。
我踩过的一个坑是:节点的kubelet服务反复重启,但Kubernetes集群里没有任何告警指标异常,因为节点上的Pod数据都还在,只有点开节点详情才看到NotReady。后来我在每个节点上加了一个systemd服务状态监控脚本,盯住kubelet和容器运行时这两个关键服务,一旦 systemctl is-active 不是active就上报。这个监控粒度是Kubernetes层面的工具给不到的,只能靠systemd层补充。
5. 长期实践感悟与监控习惯
日常用下来的经验是,系统监控类的实践,工具从来不是问题,问题在于“你知不知道什么该看、什么时候看”。
5.1 实时监控的“实时”粒度怎么定
不是所有服务都需要按秒级去盯。我的习惯是把服务分成三类:核心链路服务、重要服务、普通服务。核心链路服务可以接受1到5秒的刷新频次,比如网关、数据库;重要服务30秒到1分钟检查一次是否活着;普通服务定期看日志就够了。这样既保证了核心服务的实时性,又不会让无关的日志刷屏。
对于核心服务,我还建议不只盯状态,把 systemctl status 里的Memory字段也纳入监控范围。通过观察内存的增速来判断是否泄漏,比等故障发生后再查日志更能提前止损。我维护的一个Redis缓存服务,曾经因为客户端连接泄漏导致内存以每小时5%的速度上涨,就是靠盯Memory趋势发现并处理的。
5.2 日志轮转和journald配置
journald默认会把日志存到 /var/log/journal/,但如果你没改配置,有些系统的journal日志是不会持久化的,重启之后就全丢了。我建议确认一下 /etc/systemd/journald.conf 里的 Storage 选项,设置为 persistent。
日志文件大小也要注意。SystemMaxUse 参数控制journald日志占用的最大磁盘空间,默认可能只有几GB,对于高频日志的服务来说可能不够。但也不需要给太大,日志体积和磁盘空间要平衡。我一般设置成 SystemMaxUse=500M,配合 SystemMaxFileSize=50M,避免单文件过大不好查。
5.3 把监控命令沉淀成自己的工具箱
最后分享一个习惯:把常用的监控命令整理成一个脚本目录,逐步沉淀成自己的工具箱。比如我现在的 .bashrc 里就有几个别名:
bash复制alias svc-watch='watch -n 2 systemctl status'
alias svc-err='journalctl -p err -b --no-pager | tail -50'
alias svc-recent='journalctl --since "10 minutes ago" --no-pager'
alias svc-blame='systemd-analyze blame | head -20'
这些命令单看都不复杂,但组合起来就是一个轻量级监控方案。遇到问题先看状态,再查错误日志,再分析启动耗时,一条链路下来,大多数问题都能定位。这个习惯比任何监控软件都重要——因为监控软件本质上是把你手动执行的命令自动化了,如果你手动连命令都不熟,自动化的监控设计出来也是漏洞百出。
