用 systemd timer 实现轻量服务告警

记录接入轻量生产监控的过程,用 systemd timer、Python 健康检查和 SMTP 邮件解决服务离线无人感知的问题。

一、故障背景与监控需求

这次接入监控的直接原因,是前端在短时间内出现过两次离线,而我都没有在第一时间感知到。

第一次问题来自部署流程。前端和反向代理相关的 Caddy 配置还在分支上,没有合并到主线;当时线上仍然使用旧的部署脚本,导致前端入口离线。这个问题不是服务代码本身异常,而是部署脚本和真实配置状态没有对齐。

第二次问题更直接。前端容器已经创建出来,但停留在 Created 状态,没有真正启动。后端、数据库、Redis、模型网关这些服务都还在,API 健康检查也能返回,但用户访问前端页面时仍然打不开。最后手动启动 web 容器后,服务才恢复。

这两次故障暴露的问题不是缺少复杂指标,而是缺少基础可用性告警。当前阶段,我需要及时知道服务是否仍在正常运行。

二、监控方案选型

Prometheus、Alertmanager 和 Grafana 是更完整的监控组合。它们适合采集时间序列指标,做告警路由,画长期趋势图,也适合后面分析“什么时候开始变慢”“哪类请求错误率更高”“资源是否接近瓶颈”。

但这次需求还没到那个阶段。当前服务规模不大,告警接收人也只有我自己,真正要解决的是“服务异常时能否及时感知”。如果为了这个目标再部署一整套 Prometheus 体系,反而会引入新的维护成本:Prometheus 自己要部署,Alertmanager 要配置,规则要维护,Grafana 也要管理。

所以这次选型很克制:用 systemd timer 每分钟触发一个 Python 脚本,脚本检查公网入口和 Docker 容器状态,连续失败达到阈值后通过 SMTP 发邮件。它不是完整可观测性平台,但足够回答当前最关键的问题:服务是否还可用。

三、健康检查范围

检查对象分成两层:用户入口是否可访问,以及内部组件是否处于健康运行状态。

公网入口只保留最关键的几个点:

检查项 目的
/ 首页是否能正常返回 HTML
/app 主要业务页面入口是否能打开
/admin 管理或次级业务页面入口是否能打开
/health 后端 API 健康检查是否正常

这几项不是为了覆盖所有业务细节,而是为了判断用户入口和后端基础状态是否可用。前端离线这类问题,单看后端 health endpoint 是看不出来的,所以页面入口必须单独检查。

容器侧则只看关键组件:

容器 作用
project-web-1 前端静态站点
project-api-1 API 后端
project-worker-1 后台任务 worker
project-postgres-1 PostgreSQL 数据库
project-redis-1 Redis
project-gateway-1 内部网关或下游依赖

这里检查的是 Docker 状态里的 running 和 healthcheck 结果。它不能证明每条业务链路都完整可用,但能及时发现“容器未启动”“容器 unhealthy”这类基础故障。

四、实现与部署方式

实现上没有写成长驻进程,而是用 systemd timer 定时调起脚本。健康检查本身是短任务,执行完成后即可退出,不需要再维护一个额外的服务进程。

整体流程是:timer 每分钟触发一次 Python 脚本,脚本依次检查 URL 和容器状态,然后把结果写进本地状态文件。如果连续失败次数达到阈值,就发告警邮件;如果之前处于异常状态,后来恢复了,就发恢复邮件。

  graph TD
    A[systemd timer 每分钟触发] --> B[Python 健康检查脚本]
    B --> C[检查公网 URL]
    B --> D[检查 Docker 容器状态]
    B --> E[更新本地状态文件]
    E --> F{连续失败达到阈值?}
    F -->|是| G[SMTP 发送告警邮件]
    F -->|否| H[只记录状态]
    E --> I{从失败恢复?}
    I -->|是| J[SMTP 发送恢复邮件]

告警策略也尽量简单:每 60 秒检查一次,连续 2 次失败才发邮件。同一故障持续存在时,不每分钟重复发,而是隔一段时间再提醒;恢复后单独发一封恢复邮件。这样能过滤短暂网络抖动,也不会让真实故障沉默太久。

配置放在 root 只读的 env 文件里,SMTP 授权码不写进日志,也不写进文章。类似这样:

PROJECT_HEALTH_WATCHDOG_FAILURE_THRESHOLD=2
PROJECT_HEALTH_WATCHDOG_REPEAT_INTERVAL_SECONDS=10800
PROJECT_HEALTH_WATCHDOG_TIMEOUT_SECONDS=10
SMTP_HOST="smtp.example.com"
SMTP_PORT=465
SMTP_USER="sender@example.com"
SMTP_PASSWORD="<smtp-authorization-code>"
SMTP_USE_TLS=true
ALERT_TO="receiver@example.com"

部署时把脚本、env 文件、systemd service 和 timer 都收进 Ansible role。Ansible 负责创建目录、渲染配置、安装 unit,并启用 timer。这样以后调整检查项或者换机器,不需要再靠手工记忆去服务器上改文件。

上线后只看 active 还不够,还要确认 timer 真的在触发、日志里每个检查项都是预期结果,并且测试邮件能送达。健康检查脚本能跑通,不代表 SMTP 链路一定可用;邮件链路必须单独验证。

五、适用边界与后续演进

这套方案适合服务规模还不大、维护者较少、第一目标是“异常时能够收到通知”的阶段。它的价值在于低成本、低维护、可解释:出现问题后,可以直接查看 systemctljournalctl、状态文件和脚本输出。

它不适合替代完整的可观测性平台。请求耗时趋势、错误率统计、资源曲线、下游服务调用成本、多级告警、长期历史指标,这些都不是它擅长的事情。等服务规模变大,或者开始需要分析“为什么慢”“什么时候开始变差”,Prometheus 这类体系仍然应该引入。

但在这次场景里,先做轻量监控是更实际的选择。前端曾经因为部署脚本和容器状态问题离线过,说明当前最缺的不是复杂指标,而是一套基础可用性告警机制。先把服务从“异常无人感知”变成“连续异常会触发邮件通知”,这一步已经足够有价值。

使用 Hugo 构建
主题 StackJimmy 设计