自从我司采用 Cronitor 监控定时任务,并使用 Terraform 将监控「代码化」已经有一段时间了。相比于常驻的服务,监控定时任务需要关注的往往并不是进程是否持续运行、是否正常接收请求,而是:
几年前,我们给所有定时任务的 结束事件 放置了一个钩子,将任务的成功或失败的通知发送到准备好的 Slack channel。
因此团队内需要安排一名工程师,定期查看 Slack 消息通知来判断 cron jobs 的结果是否正常。时常发生定时任务中途出现无法捕获的 crash,导致不会发送失败通知到 Slack,所以工程师还得核对 channel 中消息的数量,如果与预期不一致,再利用排他法排除成功的消息,从而找出失败的任务。
最初这种方法是奏效的,因为定时任务的数量非常少;随着任务越来越多,依赖肉眼查看的成本、人为失误的概率陡增,工程师们也逐渐受够了这种重复枯燥的劳动。
并且,这套工作流程与 DevOps 思想是相悖的。
于是我们引入了 Cronitor,它的核心逻辑大概是这样的:
由于 Cronitor 只发送失败通知,如此一来,工程师们的工作从 关注海量消息并找出失败的 变成了 仅关注失败的消息,显著降低了人力负担。
Cronitor 支持的 integrations 有 Slack、PagerDuty、Opsgenie、VictorOps 等,我们配置了 Slack。
另外,在 Cronitor 的 web UI 上也能一目了然地看到各个 cron jobs 的状态。
小提示:类似 Cronitor 的产品还有 healthchecks.io 等。
到这一阶段,我们虽然已经降低了人力成本。可还是需要一名工程师关注 Slack 消息,并将错误分发给具体的负责人处理。随着公司的发展,定时任务的数量和开发组的同事逐渐增多,负责看消息的人有时不知道错误该交给谁处理。因此必须将每个定时任务 责任到人。
因为 Cronitor 并没有「负责人」的概念,我们利用 Cronitor 的 tags 和 webhook 定制了自己的解决方案。
从上图顶部的一排 tags 中可以发现有一些名为 owner:* 的 tags,我们为定时任务附上了这样的标签。同时,把原先的 Slack integration 改为使用 webhook,将失败的任务信息统一发送至内部开发的一款名为 Cronitor Failure Dispatcher 的小工具,由它将失败通知发送到相应的 Slack channel。该工具在发送通知前会读取任务的 tags,并在通知信息中 mention 指定的人员,例如 @annie.wang。
关注定时任务执行结果 的工作流程完全自动化了,创建定时任务监控项 仍需在 Web UI 上手动操作。这在少量 cron jobs 时似乎简单易行,但我司大多数项目需要 development、staging、UAT、production 四套运行环境,因此我们不得不为本就数量庞大的定时任务分别创建与之对应的四个监控项。这意味着:
受到 Infrastructure as Code 思想的启发,我们在想:可否实现 Monitors as Code 呢?答案是肯定的。
在我们长期将基础设施代码化的过程中,已经积累了一些 Terraform 相关的实践经验。它的设计思想允许用户声明几乎任何「资源」—— 只需要编写相应的 providers、定义资源属性以及它的增删改方法,Terraform 便能够帮助我们管理这些使用代码定义的资源,例如检查 diff、根据 diff 执行必要的操作等。
其中,providers 的工作就是与具体的资源提供者交互,例如调用 HTTP API、修改数据库记录,甚至是编辑文件。
鉴于 Cronitor 官方和社区没有对应的 Terraform provider,因此我们自己写了一个并开源了:github.com/nauxliu/ter…。以下是声明 cronitor_heartbeat_monitor 的一个简易例子:
terraform { backend "local" { path = "terraform.tfstate" } } variable "cronitor_api_key" { description = "The API key of Cronitor." } provider "cronitor" { api_key = var.cronitor_api_key } resource "cronitor_heartbeat_monitor" "monitor" { name = "Test Monitor" notifications { webhooks = ["https://example.com/"] } tags = [ "foo", "bar", ] rule { # Every day in 8:00 AM value = "0 8 * * *" grace_seconds = 60 } } 复制代码
小提示:推荐 crontab.guru,是一款可以将 Cron Expression 解析为人类可读语言的小工具。
为了能够安全地将以上内容提交到代码管理系统,我们还将 Cronitor 的 API key 解耦,使用 Terraform 的 variables 赋值;这样既可以将它存储到 terraform.tfvars 文件中,又能够通过环境变量设置,方便 CI/CD。
实际的代码当然没有这么简单,我们将多个项目所需要的监控项编写成 Terraform modules 以便于复用,再将各个环境抽象为 Terraform workspaces,在 workspaces 内使用 modules 指令,根据需要引用模块即可;即使部分项目不存在某些环境也能够灵活编排。
最后,我们将所有的代码存放在一个 Git 仓库中,并结合 GitLab CI 实现了完整的 从提交代码到实际变更的自动化。
+--------------+ | Users Commit | +------+-------+ | v +-------+--------+ | Git Repository | +-------+--------+ | +-----------+ | CI/CD <-----> Terraform | | +-----------+ +-----------+ HTTP +--------+----------+ | Cron jobs +-------------> Cronitor Monitors | +-----------+ Request +--------+----------+ | | Webhook v +------------+----------------+ | Cronitor Failure Dispatcher | +--------+----+----+----------+ | | | +-------+ | +---------+ | | | v v v User A User B User C 复制代码