一个Go二进制文件,一个YAML文件,一个SQLite数据库:我编写了我的监控工具
2026年7月9日,我需要监控一组异构服务:HTTP端点、PostgreSQL数据库、一些Oracle实例、Redis、必须保持新鲜的Elasticsearch索引、应答ping的机器,以及一些Prometheus指标。我需要通过Telegram、短信或Signal被告知,什么时候出现故障,什么时候恢复。传统的解决方案是一个监控平台:Prometheus加Alertmanager加Grafana加少量的exporters,或者运行Node.js应用的容器及其数据库。所有这些都是很好的工具。但是对于几十个检查,我不想仅仅为了知道第一个系统是否正常工作而操作第二个分布式系统。而且所有轻量级的选择都无法在不安装Oracle客户端库的情况下查询Oracle。因此,我写了Gjallar:一个简约的监控服务。一个静态二进制文件,一个YAML配置文件,一个SQLite文件。一个黑红色的状态页面,带有历史记录,使用HTMX刷新。大约3400行Go代码。MIT许可证。为了避免CGO,我故意选择了这个方案。整个工具的构建命令是CGO_ENABLED=0:CGO_ENABLED=0 go build -trimpath -ldflags "-s -w"。这只有在今天,所有传统上绑定到C库的依赖项都有纯Go替代品的情况下才能实现,而且这些替代品都非常出色:pgx用于PostgreSQL:无需libpq;go-ora用于Oracle:无需Oracle Instant Client,仅此一项就justify了这个项目。如果你曾经在一个最小的机器上部署过Oracle客户端,你就知道了;对ICMP echo的强制检测,特权或非特权;modernc.org/sqlite用于存储:SQLite转译为纯Go,无需libsqlite3;Redis根本不需要驱动程序:检查直接发出协议:TCP连接, 可选的AUTH, PING,预期+PONG。结果是一个单一的自包含二进制文件(大约36 MB,其中大部分为SQLite和Oracle驱动程序),可以从我的笔记本电脑交叉编译到任何目标,使用GOOS / GOARCH,并通过scp进行部署。没有Docker,没有包管理器,没有共享库,没有“在我的机器上工作”。一个无锁的警报管道。监控工具天生是并发的,每个监控程序在大部分时间内都在网络上等待,而并发正是侧项目通常成长出第一个互斥锁丛林的地方。Gjallar完全没有对其状态加锁,因为管道的形状是这样的:一个goroutine用于监控──▶结果通道──▶单一消费者(状态机+SQLite写入)。每个监控程序在自己的goroutine中运行检查循环,并将check.Result值发送到一个共享通道。单一的消费者goroutine拥有下游的所有内容:up/down状态机、事件行和历史写入。由于只有一个goroutine会接触到状态映射和数据库连接,因此没有需要加锁的内容,而SQLite相对不喜欢并发写入,因此只会得到一个。每个监控器的状态很小且明确:type monitorState struct { down bool consecutiveFails int downSince time.Time lastNotified time.Time threshold int // 连续失败后DOWN触发的阈值 realert time.Duration // 当处于DOWN状态时的提醒间隔;0 = 禁用 notifiers []string } 两个设计点在生产环境中证明了自己的价值:状态能够在重启后存活。在启动时,每个监控器的状态由SQLite中的任何开放事件初始化。在某项服务故障期间重启不会重新发送DOWN警报,也不会错过恢复通知。在故障期间部署新版本是一个非事件。通知是异步发送的。消费者绝不能阻塞:一个缓慢的SMTP服务器或一个限流的Telegram API不能给整个管道造成压力。发送在自己的goroutines中进行,超时为15秒。警报在N次连续失败后触发,而不是在第一次短暂故障时触发,没有震荡噪声,且可选的重新警报间隔在事件保持开放期间提醒您。尊重操作的配置。所有内容都存在于一个YAML文件中,包含默认值、命名的通知者和监控组:defaults: interval: 60s timeout: 10s failure_threshold: 3 alerts: [ops-telegram] alerts: ops-telegram: url: "telegram://TOKEN@telegram?chats=123456789" monitors: - name: app-db type: postgres dsn: "postgres://monitor:${PG_PASSWORD}@db1:5432/app" query: "SELECT count(*) FROM jobs WHERE status = 'stuck'" rule: "== 0" 三个小特性使其便于操作:热重载SIGHUP:systemctl reload gjallar在完全验证新的配置后应用,但在此之前不会发生。损坏的YAML会保持运行配置,并记录错误,而不会因此导致监控服务停止。你的监控工具应该是最后一个因打字错误而停止工作的服务。${VAR}环境扩展用于秘密,如果引用的变量未定义则清晰启动失败,而裸露的$(例如,在~^OPEN$正则表达式规则中)则保持不变。-check标志用于干运行验证,以便CI可以在配置到达服务器之前对其进行lint。它有意不做的事情:没有集群,没有代理,没有插件系统,没有时间序列仪表板,没有用户帐户。历史将在可配置的保存期(默认30天)后被修剪,以便SQLite文件保持
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡