心跳监控
心跳监控反转了方向 - 由你的作业告诉我们它还活着。两种模式:Scheduled(预期间隔)和 Event-Receiver(休眠;仅当触发器匹配负载时才触发)。
模式
- Scheduled:如果在配置的间隔内没有心跳到达,状态翻转为 MISS 并触发告警。
- Scheduled 显式失败:当作业运行了但检测到不良结果时,用 ?status=fail 调用同一 URL。这会记录一次 KO 心跳并立即触发 down 处理。
- Event-Receiver:状态保持休眠。每次心跳存储其负载;带条件的触发器据此评估。Event-Receiver 模式会忽略 ?status=fail,因为由触发器条件决定何为重要。
OK beat:
curl -fsS https://keepitalive.dev/heartbeat/{token}
Explicit failed beat:
curl -fsS "https://keepitalive.dev/heartbeat/{token}?status=fail"SCHEDULED 配方
*/5 * * * * curl -fsS https://keepitalive.dev/heartbeat/{token} >/dev/null0 2 * * * /usr/bin/backup.sh && curl -fsS https://keepitalive.dev/heartbeat/{token}curl -fsS "https://keepitalive.dev/heartbeat/{token}?status=fail&payload=disk+full"*/1 * * * * if ping -c 1 -W 2 192.168.1.50 >/dev/null 2>&1; then \
curl -fsS "https://keepitalive.dev/heartbeat/{token}"; \
else \
curl -fsS -X POST "https://keepitalive.dev/heartbeat/{token}?status=fail" \
-d "unreachable"; \
fi:: C:\scripts\keepalive_heartbeat.bat
@echo off
curl -fsS https://keepitalive.dev/heartbeat/{token} >nul 2>&1
:: schtasks /create /tn "KEEPitALIVE" /tr C:\scripts\keepalive_heartbeat.bat /sc minute /mo 5CRON 计划
普通心跳每 N 秒等待一次信号,这不适合按日历运行的任务:仅工作日运行的备份在每个周日看起来都迟到了 65 小时。改为设置 Cron 表达式后,期限变成“下一次计划运行加上宽限时间”——在有东西到期之前,不存在迟到。请选择任务实际运行的时区:计划在该时区求值,这样夏令时切换后 03:00 的任务仍在 03:00。表达式同时决定费用,因为它确定我们每小时预期多少次运行。接收 URL 不变,改变的只是我们何时期待信号。
# Give the monitor the same schedule the job runs on.
0 3 * * 1-5 # 03:00, weekdays only
*/15 * * * * # every 15 minutes
0 3 1 * * # 03:00 on the 1st of the month
@daily # midnight
# Pick the zone the job runs in, not the server's: that is
# what keeps 03:00 at 03:00 when the clocks change.退出码
把任务的退出码附加到 URL 后面,心跳就会自行上报结果:0 表示成功,其他值表示失败。这比用 && 串联更好——任务失败时 && 什么都不会发送,监控要等到错过时段才会转为宕机,而不是在任务出错的那一刻。退出码会保存在该心跳上,因此触发器条件可以按失败类型分流:payload.exit_code == 137(因内存被终止)可以开启一次中断,而 payload.exit_code == 1 只发送通知。退出码必须在 0-255 之间;如果手头没有退出码,也可以使用 /fail。
# $? is the exit status of the job that just ran
0 2 * * * /usr/bin/backup.sh; curl -fsS https://keepitalive.dev/heartbeat/{token}/$?
# Send the job output too, so the alert says why it failed.
# Capture it first: in a pipeline $? is the last command's status,
# not the job's.
0 2 * * * out=$(/usr/bin/backup.sh 2>&1); \
curl -fsS --data-binary "$out" https://keepitalive.dev/heartbeat/{token}/$?运行时长
作业开始时请求 /start,结束时发送普通信号,耗时会以 duration_ms 记录在该信号上,因此可以对缓慢的运行发出告警——payload.duration_ms > 300000 能捕获耗时超过五分钟的备份,即使它最终成功。/start 完全是可选的:仅一个结束信号本身就是完整的报告,缺失检测和退出码在没有它的情况下同样有效。发送它只带来两样东西:记录的运行时长,以及对永不结束的运行的检测。单独的 /start 不报告任何内容,也不改变监控状态:启动后卡住的作业仍必须错过它的时间窗口。当一次运行仍处于打开状态时,第二个 /start 会被拒绝,因为两次启动之间没有结束意味着第一次运行从未报告;一旦该运行超时,拒绝即解除。打开超过 24 小时的运行会被丢弃。
# /start is optional. Without it you still get miss detection
# and the exit status - just no duration and no stuck-run alert:
0 2 * * * /usr/bin/backup.sh; curl -fsS https://keepitalive.dev/heartbeat/{token}/$?
# With it, the run is timed and a job that never finishes is caught:
0 2 * * * curl -fsS https://keepitalive.dev/heartbeat/{token}/start; \
/usr/bin/backup.sh; \
curl -fsS https://keepitalive.dev/heartbeat/{token}/$?卡住的运行
如果任务访问了 /start 然后卡住,此刻还没有任何东西算迟到——开始并不会刷新任何计时,所以在下一次心跳到期之前,监控看起来一切正常。这个空档正是为任务计时的关键问题:卡住的运行恰恰就是你想捕捉的故障。因此,未发送结束心跳的运行一旦超过间隔加宽限时间就会转为宕机,并在错误中说明已经运行了多久。没有单独的最大运行时长设置:心跳迟到和运行卡住本质是同一个问题——我们要等多久才判定失败——所以你已经设置的宽限值同时回答了两者。
负载
向你的心跳 URL POST JSON 以上报系统指标或任意字段。监控详情页的“最新报告”卡片会尽可能将最新负载渲染为美观的 JSON,并回退为原始文本。
curl -fsS -X POST https://keepitalive.dev/heartbeat/{token} \
-H 'Content-Type: application/json' \
-d '{"job":"nightly-backup","status":"ok","rows":42850}'EVENT RECEIVER:CI/CD 维护
Event Receiver 模式适用于部署流水线。创建一个负载触发器,条件为 payload.status == "maintenance",事件分类为 maintenance。在部署前发送 maintenance 负载,并在 if: always() 的最终步骤发送 running/ok 负载。当表达式匹配时事件开启,当下一个被接受的负载清除它时关闭。
- name: Start maintenance
run: |
curl -fsS -X POST "$KEEPITALIVE_HEARTBEAT_URL" \
-H "Content-Type: application/json" \
-d '{"status":"maintenance","source":"github-actions","sha":"${{github.sha}}"}'
- name: Deploy
run: ./deploy.sh
- name: End maintenance
if: always()
run: |
curl -fsS -X POST "$KEEPITALIVE_HEARTBEAT_URL" \
-H "Content-Type: application/json" \
-d '{"status":"running","source":"github-actions","sha":"${{github.sha}}"}'事件接收器按监控间隔对被接受的负载进行限流。对于非常短的部署作业,使用你套餐允许的最小间隔,或将最终负载延迟到间隔已过之后。如果源变得静默,KEEPitALIVE 不会假定已恢复。
#!/bin/bash
URL="https://keepitalive.dev/heartbeat/{token}"
CPU=$(top -bn1 | grep "Cpu" | awk '{print $2}')
RAM=$(free -m | awk '/Mem/{printf "%.1f", $3/$2*100}')
DISK=$(df -h / | awk 'NR==2{print $5}')
curl -fsS -X POST $URL \
-H "Content-Type: application/json" \
-d "{\"hostname\":\"$(hostname)\",\"cpu\":\"$CPU%\",\"ram\":\"$RAM%\",\"disk\":\"$DISK\"}"$url = "https://keepitalive.dev/heartbeat/{token}"
$os = Get-CimInstance Win32_OperatingSystem
$body = @{
hostname = $env:COMPUTERNAME
cpu = "{0:N1}%" -f (Get-CimInstance Win32_Processor).LoadPercentage
ram = "{0:N1}%" -f ((1 - $os.FreePhysicalMemory / $os.TotalVisibleMemorySize) * 100)
} | ConvertTo-Json
Invoke-RestMethod -Uri $url -Method POST -Body $body -ContentType "application/json"