ブログ一覧

Cloudflare Workers の Cron で曜日 0 は拒否され、7 は土曜になる

Cloudflare Workers に Cron Trigger を 2 本持つ監視用の Worker をデプロイしたら、GitHub Actions のジョブが赤くなりました。ところが Worker 自体は本番に上がっていて、動いてはいる。ただし Cron が 1 本も登録されていない、という中途半端な状態でした。

原因は cron 式の曜日でした。そして直したつもりの直し方も、もう一段間違えていました。その記録です。

デプロイが「半分だけ」失敗する

wrangler deploy のログです。

Total Upload: 43.82 KiB / gzip: 12.00 KiB
env.HEARTBEAT_CRON ("30 23 * * 0")                                   Environment Variable
Uploaded emdash-monitor (0.74 sec)
No targets deployed for emdash-monitor (0.86 sec)
✘ [ERROR] Trigger configuration for "emdash-monitor" was only partially updated:
    Cron schedules:
      - A request to the Cloudflare API (/accounts/<account-id>/workers/scripts/emdash-monitor/schedules) failed.
        - invalid cron string: 30 23 * * 0 [code: 10100]
  Successful trigger changes were not rolled back.

Uploaded emdash-monitor まで成功したあとに、schedules の登録だけが失敗しています。最後の行にあるとおり、成功した分は巻き戻されません。API で確かめると、スクリプトは存在して Cron は空でした。

$ curl .../workers/scripts/emdash-monitor/schedules
{"result":{"schedules":[]},"success":true}

ジョブの赤だけを見て「デプロイされなかった」と読むと実態を取り違えます。Worker は居る、ただし何も起きない、という状態です。監視用の Worker なので、何も起きないことに気づく手段が無いのが一番困る点でした。

0 は拒否される。では 7 は日曜か

30 23 * * 0 は、Linux の cron でも GitHub Actions の schedule でも「毎週日曜 23:30」です。同じリポジトリの monitor.yml では 0 23 * * 0 がそのまま動いています。Cloudflare に持っていったら invalid cron string でした。

API を直接叩いて確かめると、7SUNsun は通り、0 だけが弾かれます。そこで 7 に書き換え、schedules に載ったことを確認して終わりにしました。

$ curl .../workers/scripts/emdash-monitor/schedules
[{"cron":"0 */3 * * *"},{"cron":"30 23 * * 7"}]

2 日後、別の記事のために公式ドキュメントを読み直していて、この 1 行を見つけました。

Days of the week go from 1 = Sunday to 7 = Saturday, which is different on some other cron systems (where 0 = Sunday and 6 = Saturday).

Cloudflare の曜日は 1 が日曜で、7 は土曜です。Linux の cron の 06 とも、7 を日曜の別名として認める実装とも違います。私は「0 が駄目なら 7」という他の cron の常識でそのまま置き換えて、週 1 回の生存報告を土曜の 23:30 UTC(日曜の 8:30 JST)に登録していました。

ドキュメントは続けてこう勧めています。

To avoid ambiguity you may prefer to use the three-letter abbreviations (e.g. SUN rather than 1).

SUN に直しました。wrangler.jsonccrons と、Worker の中で「どちらの Cron から起動されたか」を見分けるために持たせている環境変数の両方です。

"triggers": {
	"crons": ["0 */3 * * *", "30 23 * * SUN"]
},
"vars": {
	"HEARTBEAT_CRON": "30 23 * * SUN"
}

Worker の中では event.cron を環境変数と比較して、3 時間ごとの定期チェックと週 1 回の生存報告を分岐しています。event.cron に登録した文字列がそのまま入る前提の実装で、7 のときはそれで動いていました。SUN でも同じ文字列が渡るかは、次の日曜の初回発火で確かめます。ここを片方だけ直すと、Cron は日曜に鳴るのに Worker の中では生存報告と認識されない、という別の静かな失敗になります。

この Worker が見張っているもの

Cron の話だけでは何のための Worker か分からないので、短く書いておきます。

Cloudflare D1 の無料枠は 1 日 500 万行の読み取りです。8 月の下旬、8 サイトを載せた 1 アカウントで 1 日 2,110 万行(枠の 422%)を読んでいて、気づいたきっかけは Cloudflare からの警告メールでした。原因はサイドバーの件数集計、ミドルウェアの先読み、タームページの集計と、すべてコードの側にあり、テンプレートとキャッシュの見直しで 1 日 156 万〜196 万行(31〜39%)まで下げました。

問題は、同じことをもう一度やっても、次も警告メールで気づくことでした。そこで「減らす仕組み」ではなく「戻っていないことを見張る仕組み」として、この Worker を作りました。

  • 先行指標は「1 起動あたりの読み取り行数」。 日次の合計はアクセスの波に埋もれ、暇な日は正常に見えます。1 起動あたりはアクセス量に左右されず、コード由来の劣化にそのまま出ます。実際、8 月の障害はトラフィックの監視には一切現れませんでした
  • 閾値はサイト別のベースラインの 3 倍。 平常時のばらつきが最大 2.5 倍あり、2 倍だと小さいサイトが誤検知しました。実際の劣化は 4〜16 倍だったので、3 倍で分離できます
  • 3 時間ごと。 対策前の最悪の 1 時間は持続可能レートの 8.39 倍で、そのペースが続くと 2.9 時間で枠が尽きます。対処の時間を引いて 3 時間にしました
  • 正常時は無音。 異常時だけ Slack と GitHub Issue に出し、同じラベルの Issue が開いている間はコメントを積むだけにする。人間が Issue を閉じる操作がそのまま状態のリセットになります
  • 週 1 回だけ、正常でも生存報告を出す。 無音を正常のサインにすると、故障と無音の区別がつきません。今回の Cron の間違いは、まさにこの生存報告の側で起きました

Cloudflare の GraphQL Analytics API(d1AnalyticsAdaptiveGroupsworkersInvocationsAdaptive)を Worker から叩いているだけなので、本番サイトには一切アクセスしません。GitHub Actions からの死活チェックが Bot Fight Mode に弾かれて 403 になった ことがあり、本番に HTTP を出さない監視にした理由の一つです。無料プランのスケジュール実行は CPU 10ms ですが、API の往復は I/O で CPU 時間に乗らず、分割は不要でした。

この構成で気をつけること

  • Cloudflare の Cron Trigger の曜日は 1 = 日曜 〜 7 = 土曜。0invalid cron string で拒否され、7 は通るが土曜になる
  • 曜日は SUN のような名前で書く。数字で書くと、他の cron に慣れた自分が読み間違える
  • wrangler deploy は Cron の登録だけ失敗してもスクリプトは上がる。ジョブが赤いときは GET /accounts/{acc}/workers/scripts/{name}/schedules で登録内容を見る
  • event.cron の文字列で分岐しているなら、cron 式を直すときは比較先も一緒に直す
  • 監視の Worker は「何も起きない」が故障と区別できない。生存報告を 1 本持たせて、それが鳴らなかったら疑う

生存報告の Cron が土曜に登録されていた 2 日間は、まだ土曜が来ていなかったので実害は出ていません。実害が出る前に見つかったのは、記事を書くためにドキュメントを読み直したからで、運用の手順で見つけたわけではありませんでした。

関連

この記事をシェア