用 Cloudflare Workers 免费档搭一个服务巡检机器人:定时触发、静默失败与告警的三处坑
用 Cloudflare Workers 免费档搭一个服务巡检机器人:定时触发、静默失败与告警的三处坑
个人站、小服务最怕的不是挂了,而是挂了没人知道。租一台 VPS 跑巡检脚本要花钱、要保活;用 Cloudflare Workers 免费档搭一个巡检机器人,零服务器、零费用,定时触发、异常发邮件,十几分钟就能跑起来。但免费档有三处坑是文档里写明、新手最容易踩的:Cron Trigger 静默失败、CPU 只有 10 毫秒、cron 只认 UTC。本文按官方限额(Workers Limits 文档,2026 年 10 月 29 日更新)写具体做法。
一、免费档能干什么:先对齐限额
动手前先把数字对齐,避免搭完才发现超限:
每天 10 万次请求(UTC 零点重置),巡检一次只消耗几次请求,额度绰绰有余;每账号最多 5 个 Cron Trigger,单个 Worker 最多 3 个(免费档);单次触发 CPU 时间上限 10 毫秒——注意是 CPU 时间,不是总耗时;每次触发最多 50 个子请求(fetch、KV、D1 等合计);定时触发的 wall time 上限 15 分钟;cron 最小粒度 1 分钟,且只认 UTC,没有时区选项。
结论:巡检“每 5 分钟查一次自己的站,挂了发邮件”完全在免费档内;巡检几十个目标、每次跑几分钟重逻辑,就要拆分或升级付费档。
二、最小实现:一个 Worker + 一次 fetch
wrangler.toml:
name = "uptime-watchdog"
main = "src/index.js"
compatibility_date = "2026-05-24"
[triggers]
crons = ["*/5 * * * *"]
[[kv_namespaces]]
binding = "STATE"
id = "<你的 KV namespace id>"
src/index.js:每次触发 fetch 目标站,超时 8 秒,状态写 KV,失败发邮件:
const TARGETS = ["https://example.com", "https://api.example.com/health"];
export default {
async scheduled(event, env, ctx) {
ctx.waitUntil(this.check(env));
},
async check(env) {
for (const url of TARGETS) {
const ok = await this.probe(url);
await env.STATE.put(`last:${url}`, JSON.stringify({ ok, at: Date.now() }));
if (!ok) await this.alert(env, url);
}
},
async probe(url) {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(8000) });
return res.ok;
} catch {
return false;
}
},
async alert(env, url) {
await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${env.RESEND_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "watchdog@yourdomain.com",
to: "you@example.com",
subject: `巡检告警:${url} 不可达`,
text: `${url} 在 ${new Date().toISOString()} 连续探测失败,请检查。`,
}),
});
},
};
密钥不要写进代码,用 npx wrangler secret put RESEND_KEY 放进 Secrets。部署后 npx wrangler tail 看一次触发,确认 cron 真的跑起来了。
三、坑一:Cron Trigger 失败是静默的
这是最容易翻车的一点:Cron Trigger 没有自动重试,也没有失败告警。一次触发里抛了异常,这次运行就直接丢了,不会有人告诉你。官方文档和几篇实测文章都确认了这一点。
对策是给巡检本身再加一道“看门狗”:第一,每次触发成功都在 KV 里写一个心跳时间戳(上面代码的 last:<url> 就是);第二,另起一个触发频率更低的 Worker(或外部免费 cron 服务),每次只干一件事:读 KV 里的心跳时间戳,如果超过 15 分钟没更新,说明主巡检 Worker 已经死了,立刻发告警;第三,告警发送本身也要 try/catch,失败写 KV 留痕,不要让一次发信失败拖垮整轮巡检。
原则:没有自检的巡检等于没巡检。心跳 + 看门狗这两层缺一不可。
四、坑二:10 毫秒 CPU 不等于 10 毫秒总耗时
新手看到“单次 CPU 10 毫秒”常以为巡检跑不了:一次 fetch 往返就要几十上百毫秒。关键点是:网络 I/O 的等待时间不计入 CPU 时间,只有真正执行 JS 的时间才算。一次 fetch 等 3 秒,CPU 可能只花了零点几毫秒。
真正要小心的有两处:第一,50 个子请求上限:一次触发里 fetch、KV 读写、发邮件全算子请求。巡检目标一多就容易顶满。做法是控制目标数量(比如每 Worker 不超过 10 个目标),或把目标拆到多个 Worker、多个触发器;第二,15 分钟 wall time 是硬上限:AbortSignal.timeout 的超时要设得比它小得多(上面用 8 秒),单个目标挂起不能拖住整轮;多目标串行探测时,总耗时是各目标超时之和,目标一多就改并行 Promise.allSettled。
五、坑三:cron 只认 UTC,北京时间要自己换算
Cron Trigger 的表达式只认 UTC,没有时区选项。想让巡检在北京时间每天 9 点跑,表达式要写成 0 1 * * *(UTC 1 点),而不是 0 9 * * *。这处换算错一次,巡检就会跑错 8 小时。
做法:表达式旁边写注释标明北京时间,例如 crons = ["0 1 * * *"] # 北京时间 09:00;部署后看一次 wrangler tail 的触发时间戳,核对是不是你想要的那个北京时间;跨夏令时的地区更麻烦:UTC 没有夏令时,表达式不用跟着调,但你的“北京时间换算”在夏令时切换前后会变,最好固定用 UTC 思考。
六、验收清单:部署完先故意弄挂一次
巡检机器人上线当天做三件事:第一,wrangler tail 确认 cron 按预期时间触发了一次,KV 心跳时间戳在更新;第二,把一个目标地址临时改成不存在的域名,看告警邮件能不能在下一轮触发后收到;第三,停掉主 Worker 的触发器(Dashboard 里关掉一个 cron),看“看门狗”能不能在 15 分钟后报出“心跳过期”。
三条都通过,这个机器人才算真的在值班。否则它只是个部署成功页。
小结
Cloudflare Workers 免费档做巡检的核心就三句话:限额先对齐(10 万次/天、5 个触发器、10 毫秒 CPU、50 子请求、15 分钟 wall time);失败必须自检(心跳 + 看门狗,补上平台不给的重试与告警);时间按 UTC 写(北京时间自己换算,部署后用 tail 核对)。这三处都处理好,一个零成本的巡检机器人就能长期稳定跑下去。
- 点赞
- 收藏
- 关注作者
评论(0)