背景
自從我司採用 Cronitor 監控定時任務,並使用 Terraform 將監控「程式碼化」已經有一段時間了。相比於常駐的服務,監控定時任務需要關注的往往並不是程式是否持續執行、是否正常接收請求,而是:
- 是否在既定時間啟動?
- 執行結果成功還是失敗?
- 執行過程中有無發生錯誤?
- 執行時長較往常是否有激增或是驟減?
上古時代
幾年前,我們給所有定時任務的 結束事件 放置了一個鉤子,將任務的成功或失敗的通知傳送到準備好的 Slack channel。
因此團隊內需要安排一名工程師,定期檢視 Slack 訊息通知來判斷 cron jobs 的結果是否正常。時常發生定時任務中途出現無法捕獲的 crash,導致不會傳送失敗通知到 Slack,所以工程師還得核對 channel 中訊息的數量,如果與預期不一致,再利用排他法排除成功的訊息,從而找出失敗的任務。
最初這種方法是奏效的,因為定時任務的數量非常少;隨著任務越來越多,依賴肉眼檢視的成本、人為失誤的概率陡增,工程師們也逐漸受夠了這種重複枯燥的勞動。
並且,這套工作流程與 DevOps 思想是相悖的。
引入 Cronitor
於是我們引入了 Cronitor,它的核心邏輯大概是這樣的:
- 通過 UI 或 API 建立定時任務監控項;包括 cron jobs 應當開始的時間、可容忍的閾值等。
- 在任務開始結束或發生異常時,通過 HTTP API 發起 /ping 請求向 Cronitor 上報。
- 如果 Cronitor 未在預期的時間收到 start ping 或 finish ping,抑或收到 failed ping 則認為任務失敗;其它情況類似。
- 將失敗或錯誤通知到 Slack 或 webhook 等。
由於 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 四套執行環境,因此我們不得不為本就數量龐大的定時任務分別建立與之對應的四個監控項。這意味著:
- 工作量大,並且存在人為失誤的可能性(例如同一監控項的各個環境的配置不統一);尤其是隨著時間推移,後續建立的監控項配置很有可能與先前建立的存在細微差異,這一點因為難以核對讓我們非常頭疼。
- 不可追溯。web UI 缺少操作日誌,當配置變更時,不易找出變更的發起者和原因。
- 幾乎無法實施全域性改動(例如增加一套環境、修改所有監控項的配置等)。
引入 Terraform
受到 Infrastructure as Code 思想的啟發,我們在想:可否實現 Monitors as Code 呢?答案是肯定的。
在我們長期將基礎設施程式碼化的過程中,已經積累了一些 Terraform 相關的實踐經驗。它的設計思想允許使用者宣告幾乎任何「資源」—— 只需要編寫相應的 providers、定義資源屬性以及它的增刪改方法,Terraform 便能夠幫助我們管理這些使用程式碼定義的資源,例如檢查 diff、根據 diff 執行必要的操作等。
其中,providers 的工作就是與具體的資源提供者互動,例如呼叫 HTTP API、修改資料庫記錄,甚至是編輯檔案。
鑑於 Cronitor 官方和社群沒有對應的 Terraform provider,因此我們自己寫了一個並開源了:https://github.com/nauxliu/terraform-provi...。以下是宣告 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
本作品採用《CC 協議》,轉載必須註明作者和本文連結