systemd监控服务比脚本更可靠,应关注意图状态而非进程快照;用restart=always、substate检查、execstartpost健康检测、socket激活替代netstat;内存告警看memavailable,cpu看load和procs_running;journalctl需结构化、持久化并去重。

用 systemd 监控服务状态比写脚本更可靠
很多用户一上来就想用 ps + grep 检查进程是否存在,但这种做法在容器化或 fork 多次的进程里极易误判。真正该盯的是服务的“意图状态”,不是“进程快照”。
systemd 自带健康检查能力,只要服务单元配置了 Restart=always 和 RestartSec=10,它就会自动拉起崩溃的服务;再配合 StartLimitIntervalSec 和 StartLimitBurst,还能防住反复崩溃打爆日志。
- 别手动轮询
systemctl is-active <service></service>—— 它返回快,但不反映真实启动结果;改用systemctl show -p SubState <service></service>看子状态(比如running、failed、start-pre) - 若服务启动后需额外就绪检查(如端口监听、HTTP 健康接口),在单元文件里加
ExecStartPost=/usr/bin/curl -f http://localhost:8080/health || exit 1,失败则标记为failed - 注意:启用
Restart=on-failure时,exit 0不触发重启,非零退出才触发 —— 很多人在健康检查脚本里忘了set -e或漏写exit 1
netstat 已弃用,监控端口请用 ss + systemd-socket
netstat 在多数新发行版里已不预装,且扫描全端口慢、权限要求高。更轻量、更准确的方式是直接查 ss,或者——更进一步,把监听行为交给 systemd 管理。
比如想确保 redis 的 6379 端口始终可连,与其定时跑 ss -tln | grep :6379,不如把 redis 改成 socket 激活模式:
redis.socket 文件里写: [Socket] ListenStream=6379 Accept=false
这样端口由 systemd 统一 bind,一旦被占用或关闭,systemctl status redis.socket 就能立刻暴露问题。
-
ss -tlnp | grep :6379比netstat -tulnp快 3–5 倍,且不依赖/proc/net全量读取 - 用
ss -tlnp时注意:没权限时看不到PID/Program name列,别只看端口存在就认为服务活着 - 如果服务本身不支持 socket 激活(如老版本 nginx),至少用
ss -tlnp替代netstat,并过滤掉LISTEN状态而非ESTABLISHED
告警阈值不能只看 CPU / 内存百分比
CPU 使用率 95% 不一定代表有问题,内存 85% 却可能已触发 OOM killer —— 因为 Linux 的内存回收机制和 CPU 调度逻辑完全不同。监控必须分层:看指标,更要看行为。
真正关键的是 /proc/meminfo 里的 MemAvailable(可用内存,含可回收缓存),不是 Free;是 /proc/stat 里的 procs_running(运行队列长度),不是单核 %us。
- 告警内存时,优先用
MemAvailable ,而不是 <code>MemUsed% > 90%—— 后者在大量 page cache 场景下会频繁误报 - CPU 告警应结合
load average和procs_running:若 1 分钟 load > CPU 核数 × 2 且procs_running > 10,才说明有真实调度压力 - 磁盘 IO 要看
iostat -x 1的%util和await:前者接近 100% 只说明设备忙,后者持续 > 50ms 才表明响应延迟异常
journalctl 日志告警容易漏掉关键上下文
直接用 journalctl -u nginx --since "1 hour ago" | grep "error" 做告警,会丢掉错误前后的请求链路、系统状态甚至同一时间其他服务的日志。日志不是孤立事件,是时间线上的切片。
正确做法是用 --output=json 输出结构化日志,再用 jq 提取 __REALTIME_TIMESTAMP 和 SYSLOG_IDENTIFIER,按时间窗口聚合分析。
- 别用
tail -f /var/log/syslog | grep—— journal 日志默认不落盘到 syslog,且tail无法跨 journal 文件滚动 - 用
journalctl -u nginx -o json --since "2 minutes ago"获取原始 JSON,再 pipe 给jq 'select(.PRIORITY == "3")'过滤 error 级别 - 重要:
journalctl默认只保留最近 1–2 周日志,生产环境务必配SystemMaxUse=1G和MaxRetentionSec=3month,否则告警时根本查不到历史上下文
最常被忽略的一点:所有基于日志的告警都必须带时间戳对齐和去重逻辑,否则一次内核 panic 可能触发几百条重复告警 —— 不是监控太灵敏,是没处理好日志的爆发性输出特性。










