Cloudflare R2 免费额度监控:自动推送额度告警,避免超量付费
侧边栏壁纸
  • 累计撰写 32 篇文章
  • 累计收到 13 条评论

Cloudflare R2 免费额度监控:自动推送额度告警,避免超量付费

怪怪的科长
2026-04-29 / 0 评论 / 1 阅读

作为大善人的忠实用户,R2 存储的免费额度(虽然只有10G)确实香,但总担心用量超标产生额外费用(其实完全没必要担心),手动每天查用量又太繁琐,于是动手搭建了一套自动监控系统,支持 Bark 实时推送、企业微信、飞书多渠道告警,全程零成本、零服务器,部署完彻底省心,实测稳定运行,今天把操作步骤做好记录,仅供需要的人参考。

一、监控核心功能

  • ✅ 每日自动检测:配置凌晨1点定时执行,无需手动操作,后台静默运行;
  • ✅ 多渠道推送:支持 Bark 、企业微信、飞书,按需开启,填了变量就发、不填不发;
  • ✅ 防重复告警:解决同渠道重复推送问题,一次检测只发1条提醒;
  • ✅ 异常容错:API 调用失败、无 R2 桶均不会崩溃,日志可查问题;
  • ✅ 安全轻量:仅读取 R2 用量,无任何写入操作,API 令牌仅给只读权限,最小权限更安全。

二、前置准备(3样东西,提前准备好)

无需复杂工具,只要你有 Cloudflare 大善人账号,跟着准备以下3个核心内容即可:

  1. Cloudflare 账号 + 账号 ID(登录后可查,下文有具体路径)
  2. R2 只读权限 API Token(专门用于读取用量,不涉及修改/删除,安全可控)
  3. 推送渠道(三选一或全选或不选):

    • Bark:iPhone 端,需获取完整 Bark 推送 URL(格式:https://api.day.app/你的设备密钥)
    • 企业微信:需创建机器人,获取 Webhook Key
    • 飞书:创建自定义机器人,获取 Hook Key

三、完整部署步骤

步骤1:创建 Cloudflare Workers

  1. 登录 Cloudflare 后台,找到「Workers \& Pages」,点击「创建应用程序」
  2. 选择「从 Hello World\! 开始」,给 Worker 起个好记的名字(比如 r2-usage-monitor),名字仅用于识别,不影响功能
  3. 点击「部署」,进入代码编辑界面(此时默认是 Hello World 代码,后续替换成我们的监控脚本)

步骤2:创建 R2 只读 API Token

API Token 是脚本读取 R2 用量的凭证,必须只给只读权限,避免泄露后造成风险,步骤如下:

  1. 访问 Cloudflare API Token 页面:https://dash.cloudflare.com/profile/api-tokens
  2. 点击右上角「创建令牌」,选择「创建自定义令牌」→「开始使用」
  3. 权限配置(重点):

    • 令牌名称:自定义即可
    • 权限类型:账户 → Workers R2 存储 → 读取
    • 权限等级:读取(仅读取,其他全部保持默认不用动)
  4. 资源配置:

    • 帐户资源:选择你自己的 Cloudflare 账号
  5. 有效期(TTL):不用设置有效期,避免后续过期需要重新创建
  6. 点击「继续以显示摘要」,确认权限无误后,点击「创建令牌」
  7. 生成 Token 后,立刻复制保存(仅显示一次,丢失需重新创建)

步骤3:配置环境变量

环境变量用于存储敏感信息(账号 ID、API Token、推送密钥),无需写在代码里,后续可随时修改,步骤如下:

  1. 回到创建好的 Worker 详情页,找到「变量和机密」,点击「+ 添加」
  2. 按以下列表添加变量,每个变量单独添加:
变量名是否必填填写说明
CLOUDFLARE_ACCOUNT_ID✅ 必填Cloudflare 账号 ID,登录后在地址栏中域名后的那一串字符
CLOUDFLARE_API_TOKEN✅ 必填步骤2中创建的 R2 只读 API Token
WECOM_WEBHOOK_KEY❌ 可选企业微信机器人 Webhook 链接中「key=」后面的部分(不填则不推送)
BARK_URL❌ 可选iPhone Bark 完整推送 URL(格式:https://api.day.app/设备密钥,不填则不推送)
LARK_WEBHOOK_KEY❌ 可选飞书机器人 Hook 链接中「/hook/」后面的部分(不填则不推送)

添加完成后,点击「保存」,环境变量配置完毕。

步骤4:替换监控脚本

回到 Worker 代码编辑界面,删除默认的 Hello World 代码,复制下面的代码,粘贴后点击「保存并部署」:

export default {
  async scheduled(event, env, ctx) {
    ctx.waitUntil(r2UsageCheck(env, true));
  },

  async fetch(request, env) {
    // 拦截CF预检OPTIONS请求,杜绝一次访问两次执行
    if (request.method === "OPTIONS") {
      return new Response(null, { status: 204 });
    }
    const result = await r2UsageCheck(env, false);
    return new Response(result, { 
      headers: { "Content-Type": "text/plain;charset=utf-8" } 
    });
  }
};

const CONFIG = {
  freeLimitGB: 10,      // R2免费总额度(默认10GB,无需修改)
  warnThresholdGB: 9    // 预警阈值(超过9GB触发推送,可自行调整)
};

// 全局简易防重(单实例短时间防重复)
let lastRunTime = 0;
const COOLDOWN_MS = 5000;

async function r2UsageCheck(env, isSchedule) {
  const now = Date.now();
  // 5秒内重复执行直接拦截,彻底杜绝双击/双请求重复推送
  if (now - lastRunTime < COOLDOWN_MS) {
    return "⏸️ 短时间内重复请求已拦截,防止重复推送";
  }
  lastRunTime = now;

  const { CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN } = env;
  if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
    return "❌ 缺少必要环境变量";
  }

  try {
    const res = await fetch(
      `https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/r2/buckets`,
      { headers: { Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}` } }
    );

    const data = await res.json();
    if (!data.success) return "❌ API权限错误:" + JSON.stringify(data.errors);

    const buckets = Array.isArray(data.result) ? data.result : [];
    let totalBytes = 0;

    for (const bucket of buckets) {
      try {
        const usageRes = await fetch(
          `https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/r2/buckets/${bucket.name}/usage`,
          {
            headers: { Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}` },
            signal: AbortSignal.timeout(3000)
          }
        );
        const usage = await usageRes.json();
        if (usage.success && usage.result?.size) {
          totalBytes += usage.result.size;
        }
      } catch (e) {}
    }
   
    const totalGB = totalBytes / 1073741824;
    const msg = `【R2存储用量告警】
当前已用:${totalGB.toFixed(2)} GB
免费额度:${CONFIG.freeLimitGB} GB
预警阈值:${CONFIG.warnThresholdGB} GB`;

    // 单次流程里 每个渠道只执行一次
    if (totalGB >= CONFIG.warnThresholdGB) {
      if (env.BARK_URL) await sendBark(env.BARK_URL, msg);
      if (env.WECOM_WEBHOOK_KEY) await sendWeCom(env.WECOM_WEBHOOK_KEY, msg);
      if (env.LARK_WEBHOOK_KEY) await sendLark(env.LARK_WEBHOOK_KEY, msg);
    }

    return "✅ 执行成功\n" + msg;
  } catch (err) {
    return "❌ 监控异常:" + err.message;
  }
}

async function sendBark(barkUrl, text) {
  try {
    await fetch(`${barkUrl}/R2存储提醒/${encodeURIComponent(text)}`, { 
      method: "GET" 
    });
  } catch {}
}

async function sendWeCom(key, text) {
  try {
    await fetch(`https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${key}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ msgtype: "text", text: { content: text } })
    });
  } catch {}
}

async function sendLark(key, text) {
  try {
    await fetch(`https://open.feishu.cn/open-apis/bot/v2/hook/${key}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ msg_type: "text", content: { text } })
    });
  } catch {}
}

步骤5:配置定时触发器

脚本部署完成后,需要配置定时任务,让它每天自动检测,步骤如下:

  1. 在 Worker 详情页,找到「触发事件」,点击「+ 添加」
  2. 触发器类型:选择「Cron 触发器」
  3. Cron 表达式:填写 0 1 \* \* \*(每天凌晨1点自动执行,可自行调整时间)
  4. 点击「保存」,定时触发器配置完成

四、常见问题排查(我踩过的坑)

问题1:访问 Worker 地址超时(ERR\_CONNECTION\_TIMED\_OUT)

原因:Cloudflare Workers 自带的 *.workers.dev 域名被墙了,属于正常现象,不影响定时任务执行

解决:无需处理,定时任务会在 Cloudflare 云端正常运行,若想本地测试,可尝试使用手机流量访问,或给 Worker 绑定自定义域名。

问题2:日志空白,访问后无记录

原因:网络拦截导致请求未到达 Cloudflare 服务器,或 API Token 权限错误。

解决:切换手机流量访问 Worker 地址,同时检查 API Token 权限是否仅为 R2 读取,账号 ID 是否填写正确。

问题3:同渠道收到两条重复通知

原因:Cloudflare Workers 访问时会触发 OPTIONS 预检请求 + 正式请求,导致脚本执行两次。

解决:本文提供的最终版代码已拦截预检请求,并添加了5秒冷却防重(可自己延长或缩短时间),无需额外操作。

问题4:推送失败(Bark/企业微信收不到消息)

排查步骤:

  1. 检查环境变量是否填写正确(BARK\_URL 需完整,且不需要跟/,企业微信仅填 Key);
  2. 查看 Worker 日志,是否有推送失败提示;
  3. 用本文提供的测试代码(强制推送),验证推送渠道是否正常;

五、测试方法(确保监控正常运行)

部署完成后,可通过以下方式测试,确保功能正常:

  1. 把脚本中的warnThresholdGB: 9改为0,访问 Worker 地址,查看 Bark/企业微信是否收到推送;
  2. 测试完成后,将参数恢复即可;
  3. 查看 Worker 日志(Observability → 实时日志),确认脚本执行成功。

六、总结

这套 R2 监控系统,全程零成本、零服务器,部署一次,终身省心。每天自动检测用量,超标后实时推送提醒,再也不用手动登录 Cloudflare 查用量,避免不小心超标产生费用(虽然对于小破站来讲并不会超标)。

核心优势:配置简单、安全轻量、多渠道推送、防重复告警,新手也能轻松上手(最主要是不用花钱)

如果你在部署过程中遇到任何问题,欢迎在评论区交流!

0

评论

博主关闭了所有页面的评论