Linux 告警通知策略设计

1次阅读

告警通知发不出去需先排查 systemd-journald 与 rsyslog 冲突:journald 默认存二进制日志,rsyslog 需启用 imjournal 模块并加入 systemd-journal 组;logger 测试须指定 -p facility;rsyslog 需显式允许 local0-local7;service 要配置 standardoutput/standardError;避免 inotifywait + tail-f 组合导致重复或漏告。

Linux 告警通知策略设计

告警通知发不出去?先查 systemd-journaldrsyslog 是否冲突

linux 告警链路常卡在日志采集层——systemd-journald 默认把日志存在二进制文件里,而很多告警工具(比如 monit、自定义脚本)只读 /var/log/messages 这类文本日志。如果同时开了 rsyslog 又没配好转发,journald 会静默丢弃部分日志。

  • rsyslog 启动后默认不自动拉取 journald 日志,需确认 /etc/rsyslog.conf 里有 $IncludeConfig /etc/rsyslog.d/*.conf,且 /etc/rsyslog.d/10-journal.conf 存在并启用 imjournal 模块
  • 检查 journalctl --no-pager -u rsyslog 是否报 imjournal: journal is not accessible,这是权限问题,要把 rsyslog 加入 systemd-journal 用户组:usermod -a -G systemd-journal syslog
  • 别直接改 /etc/rsyslog.conf$ModLoad imjournal 行——有些发行版(如 centos 8+)要求用 module(load="imjournal") 语法,旧写法会导致模块加载失败,但 rsyslog 不报错,只静默跳过

logger 测试告警通路时,为什么加了 -t 却收不到?

logger 是最轻量的告警触发方式,但标签(-t)和 facility 配合不当,会被过滤掉。很多 rsyslog 规则靠 programnamesyslogtag 匹配,不是所有规则都认 -t 的值。

  • 测试命令别只写 logger -t myalert "disk full",先加 -p local0.info 显式指定 facility:logger -p local0.info -t myalert "disk full"
  • 对应 rsyslog 规则要写成 if $programname == 'myalert' and $syslogfacility-text == 'local0' then /var/log/myalert.log,注意 $programname 取的是 -t 值,但仅当 -p 指定的 facility 被 rsyslog 允许接收时才生效
  • CentOS/RHEL 默认 rsyslog 配置会丢弃 local0-local7 的日志,除非显式放开:$SystemMaxFileSize 100M 下面加一行 $AllowedSender udp 127.0.0.1 不管用,得在 /etc/rsyslog.conf 开头加 $ModLoad imudp$UDPServerRun 514 才行——但这其实是走 UDP 路径,和本地 logger 无关;真正要改的是 /etc/rsyslog.d/50-default.conf 里的 *.*;auth,authpriv.none 这行,把 local0.* 加进去

systemd 服务挂了,怎么让告警带出 exit code 和 stdout?

单纯靠 systemctl is-failed myapp.service 只能知道“失败”,但不知道是段错误还是配置写错。想拿到完整上下文,必须绕过 journalctl 的默认截断和缓冲逻辑。

  • 在 service 文件里加 StandardOutput=journal+consoleStandardError=journal+console,否则 journalctl -u myapp --no-pager -n 100 可能拿不到最后几行输出
  • exit code 要用 systemctl show --Property=ExecMainStatus myapp.service,不是 systemctl status 输出里的 “code=exited, status=1”——后者可能被 systemd 缓存,而 ExecMainStatus 是实时读 cgroup 的
  • 写告警脚本时别用 systemctl status myapp | grep "failed" 判断状态,这容易误判(比如服务刚启动中,status 输出含 “activating” 但还没 failed)。正确做法是:systemctl is-active --quiet myapp && systemctl is-failed --quiet myapp,两个都返回非零才真失败

告警重复发送?大概率是 inotifywait + tail -F 组合翻车

用文件监控做告警(比如监听 /var/log/nginx/error.log)时,inotifywait 触发太快,tail -F 还没 flush,导致同一条日志被反复读;或者 logrotate 一转,tail -F 自动跟新文件,但 inotifywait 还在盯旧 inode,漏告警。

  • 放弃 inotifywait + tail 组合,改用 journalctl -u nginx --since "1 hour ago" --no-pager | grep "error" 定时轮询——虽然低效,但稳定。如果非要实时,用 systemd-cat 把 nginx 日志打到 journal,再用 journalctl -o json-sse -u nginx 流式消费
  • 如果坚持用文件监控,inotifywait -m -e move_self,create /var/log/nginx/ 比监听 modify 更可靠:logrotate 做 rename 时会触发 move_self,这时 reload tail 进程
  • tail -F 在 NFS 或某些容器挂载路径下行为异常,换成 tail -n 0 -F 并加 --pid=$$ 配合信号捕获,收到 SIGHUP 时手动 kill $(cat /tmp/tail.pid) 再重启,避免僵尸 tail 占着文件句柄

事情说清了就结束。真正难的不是写通知逻辑,是厘清日志从进程 stdout 到落盘、再到被采集、最后匹配规则的每一跳权限和缓冲策略——中间任何一环默认值变了,告警就哑火。

text=ZqhQzanResources