はじめに
サポートチームのヘルスモニターには、同じ小さなチェックを 2 通りの方法で実行する必要があります。1 つはユーザーのリクエスト後に実行する方法、もう 1 つはユーザーのリクエストなしで定期的に実行する方法です。この実験では、両方を実装し、受付確認と処理完了を区別し、ローカルでイベントの有効期間をテストしたうえで、実際のスケジュール済みクラウド呼び出しを確認します。
この独立した VM は、Node.js 22.22.0、プロジェクトローカルの Wrangler 4.131.1、分離評価用の Miniflare 4.20260730.0、および合成的な内部ヘルスサービスのフィクスチャを備えた /home/labex/project/task-monitor から開始します。以前に学習したサービスバインディングとデバイス認証のスキルを再利用しますが、以前の VM やリソースは使用しません。自分の学習用アカウントを使用してください。ドメイン、ストレージ製品、有料アップグレードは必要ありません。リクエストとスケジュール済み呼び出しは、通常のアカウント使用量としてカウントされます。
ターミナルは 1 つ開いたままにしてください。1 分間隔の短いスケジュールは、使い捨てのテスト用です。最後にログストリームを停止し、認証済みの状態で両方の Worker を削除し、存在しないことを確認してログアウトします。この実験のバックグラウンド処理は時間が制限され、永続化されません。信頼性の高いキュー配信を保証するものとして使用しないでください。
バックグラウンド処理の完了前に応答する
このステップでは、公開エンドポイントが小さなヘルスチェックのリクエストを受け付けたことを返し、用意された内部サービスが非同期処理を実行します。この VM は独立した環境です。サービスは新しいフィクスチャであり、以前の実験のリソースではありません。
cd /home/labex/project/task-monitor
node --version
npx wrangler --version
cat health/index.js
このフィクスチャは 250 ミリ秒待機して、合成 JSON を返します。外部リクエストは送信せず、データも保存しません。ユニークなベース名を生成して、公開 Worker と内部 HEALTH サービスを設定します。これは以前に使用した標準的なサービスバインディングです。
WORKER_NAME="labex-tasks-$(openssl rand -hex 6)"
cat > wrangler.jsonc <<CONFIG
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-07-30",
"workers_dev": true,
"preview_urls": false,
"services": [
{
"binding": "HEALTH",
"service": "$WORKER_NAME-health"
}
],
"triggers": {
"crons": []
}
}
CONFIG
cat > health/wrangler.jsonc <<CONFIG
{
"name": "$WORKER_NAME-health",
"main": "index.js",
"compatibility_date": "2026-07-30",
"workers_dev": false,
"preview_urls": false
}
CONFIG
cat > src/index.js <<'JS'
async function checkHealth(env, details) {
const url = new URL('https://health.internal/health');
url.searchParams.set('probe', details.probe);
const response = await env.HEALTH.fetch(url, {signal: AbortSignal.timeout(3000)});
if (!response.ok) throw new Error('health_service_unavailable');
const data = await response.json();
if (data.status !== 'ok' || data.service !== 'labex-health-fixture' || data.probe !== details.probe) {
throw new Error('unexpected_health_response');
}
console.log(JSON.stringify({event: 'health_check', ...details, status: data.status, service: data.service}));
}
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (url.pathname === '/health' && request.method === 'GET') {
return Response.json({status: 'ok'}, {headers: {'Cache-Control': 'no-store'}});
}
if (url.pathname !== '/checks') return Response.json({error: 'not_found'}, {status: 404});
if (request.method !== 'POST') {
return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'POST'}});
}
const probe = url.searchParams.get('probe') || '';
if (!/^[a-z0-9-]{1,48}$/.test(probe)) {
return Response.json({error: 'invalid_probe'}, {status: 400});
}
ctx.waitUntil(checkHealth(env, {source: 'request', probe}).catch(() => {
console.error(JSON.stringify({event: 'health_check_failed', source: 'request', probe}));
}));
return Response.json({accepted: true, probe}, {status: 202, headers: {'Cache-Control': 'no-store'}});
}
};
JS
checkHealth は実際の依存先からの応答を検証し、小さな構造化結果を出力します。3 秒の中断シグナルによって、リクエストの処理時間を制限しています。公開ハンドラーは Promise を ctx.waitUntil に渡し、すぐに HTTP 202 を返します。202 はこの短時間の試行を受け付けたことを示すだけで、永続的な配信を保証するものではありません。catch は生の例外、リクエスト、認証情報を出力せずに失敗を記録します。ここに示した合成プローブ値だけを使用してください。
npx wrangler dev -c wrangler.jsonc -c health/wrangler.jsonc --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
プロンプトが表示されるまで待ってから、ログを確認します。公開開発サーバーがポート 8080 で起動したら続行してください。まだ起動中の場合は少し待って、もう一度ログを読み取ります。
cat dev.log
curl -i http://127.0.0.1:8080/health
curl -i -X POST "http://127.0.0.1:8080/checks?probe=manual-one"
cat dev.log
フォアグラウンドのヘルスルートは 200 と status ok を返します。POST は accepted true とプローブ manual-one を含む 202 を返します。依存先の処理が完了すると、ログには event health_check、source request、同じプローブ、および status ok が含まれます。ログを早く読みすぎた場合は、少し待ってからもう一度読み取ってください。後からログが出力されたことは観察結果であり、厳密な性能ベンチマークではありません。
サーバーを起動したまま検証を実行します。分離されたランタイムは、制御されたゲートの背後に独自のヘルスフィクスチャを保持します。まずフォアグラウンドの応答が返ることを確認してからゲートを開き、その後にバックグラウンド処理の完了を確認します。また、公開ヘルスルートと、拒否されるメソッドやプローブのケースも確認します。このテストから Cloudflare が呼び出されることはありません。
scheduled ハンドラーをローカルで呼び出す
このステップでは、Cron トリガーから呼び出されるハンドラーで、先ほどのヘルスチェック処理を再利用します。コードを変更する前に、実際の開発ジョブを確認して停止してください。ジョブ番号が例と異なる場合は、現在の番号に置き換えます。
jobs
kill %1
cat > src/index.js <<'JS'
async function checkHealth(env, details) {
const url = new URL('https://health.internal/health');
url.searchParams.set('probe', details.probe);
const response = await env.HEALTH.fetch(url, {signal: AbortSignal.timeout(3000)});
if (!response.ok) throw new Error('health_service_unavailable');
const data = await response.json();
if (data.status !== 'ok' || data.service !== 'labex-health-fixture' || data.probe !== details.probe) {
throw new Error('unexpected_health_response');
}
console.log(JSON.stringify({event: 'health_check', ...details, status: data.status, service: data.service}));
}
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (url.pathname === '/health' && request.method === 'GET') {
return Response.json({status: 'ok'}, {headers: {'Cache-Control': 'no-store'}});
}
if (url.pathname !== '/checks') return Response.json({error: 'not_found'}, {status: 404});
if (request.method !== 'POST') {
return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'POST'}});
}
const probe = url.searchParams.get('probe') || '';
if (!/^[a-z0-9-]{1,48}$/.test(probe)) {
return Response.json({error: 'invalid_probe'}, {status: 400});
}
ctx.waitUntil(checkHealth(env, {source: 'request', probe}).catch(() => {
console.error(JSON.stringify({event: 'health_check_failed', source: 'request', probe}));
}));
return Response.json({accepted: true, probe}, {status: 202, headers: {'Cache-Control': 'no-store'}});
},
async scheduled(controller, env) {
await checkHealth(env, {
source: 'scheduled',
probe: `cron-${controller.scheduledTime}`,
cron: controller.cron,
scheduledTime: controller.scheduledTime
});
}
};
JS
cat > wrangler.jsonc <<CONFIG
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-07-30",
"workers_dev": true,
"preview_urls": false,
"services": [
{
"binding": "HEALTH",
"service": "$WORKER_NAME-health"
}
],
"triggers": {
"crons": [
"* * * * *"
]
}
}
CONFIG
5 フィールドの式 * * * * * は、UTC で毎分実行することを意味します。この意図的に頻繁なスケジュールは、短時間の使い捨て実験にのみ使用します。scheduled はヘルス処理を await するため、呼び出し結果から処理の完了を確認できます。HTTP レスポンスは返しません。ログには、トリガーの実際の Cron 式とスケジュール時刻が記録されます。
npx wrangler dev -c wrangler.jsonc -c health/wrangler.jsonc --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
プロンプトが表示されるまで待ってから、ログを確認します。公開開発サーバーがポート 8080 で起動したら続行してください。まだ起動中の場合は少し待って、もう一度ログを読み取ります。
cat dev.log
curl -i "http://127.0.0.1:8080/cdn-cgi/local/scheduled?cron=*+*+*+*+*&time=1700000000000&format=json"
cat dev.log
ローカルトリガーは outcome ok を返し、アプリケーションログには source scheduled、Cron * * * * *、scheduledTime 1700000000000 が含まれるはずです。この古いタイムスタンプは意図的な合成テスト入力であり、現在のクラウド実行を示すものではありません。検証を実行して、別の制御されたスケジュール時刻を使い、ヘルスサービスが呼び出されたことを確認します。
HTTP 呼び出しの場合、waitUntil はレスポンス送信後、またはクライアント切断後、最大 30 秒間処理を延長できます。この猶予は、リクエストのバックグラウンド Promise で共有されます。永続的なキューや再試行を保証するものではありません。この実験の 3 秒に制限された小さな処理は、この用途に適しています。信頼性の高い配信や再試行が必要な処理には、この実験の範囲外で適切なキューまたはワークフロー設計を使用してください。呼び出しの有効期間の違いについては、context API と scheduled handler のドキュメントを参照してください。
デプロイしてリクエストのバックグラウンド処理を確認する
このステップでは、2 つの新しい Worker を自分の学習用アカウントにデプロイします。実際のローカルジョブを停止し、使い慣れたデバイスフローで、この新しい VM を認証してください。
jobs
kill %1
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_tail:read
表示されたデバイスリンクをログイン済みのブラウザーで開き、コードを入力します。要求されたアクセス権を確認し、学習用アカウントを選択してください。ターミナルに成功が表示されるまで待ちます。
npx wrangler whoami --json
アカウントが 1 つだけ表示される場合でも、アカウント名を確認します。以下の両方の設定で YOUR_ACCOUNT_ID を実際の ID に置き換えてください。生成済みの同じ Worker 名と、1 分間隔のスケジュールを使用します。公開 Worker では Workers Logs も有効にするため、その呼び出しログとアプリケーションログを Dashboard の Observability で確認できます。これはライブの wrangler tail 接続とは別のものです。この使い捨て実験では合成ヘルスチェックデータだけを出力します。認証情報は決してログに記録しないでください。Workers Logs のドキュメントを参照してください。
cat > wrangler.jsonc <<CONFIG
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-07-30",
"workers_dev": true,
"preview_urls": false,
"services": [
{
"binding": "HEALTH",
"service": "$WORKER_NAME-health"
}
],
"triggers": {
"crons": [
"* * * * *"
]
},
"observability": {
"enabled": true
},
"account_id": "YOUR_ACCOUNT_ID"
}
CONFIG
cat > health/wrangler.jsonc <<CONFIG
{
"name": "$WORKER_NAME-health",
"main": "index.js",
"compatibility_date": "2026-07-30",
"workers_dev": false,
"preview_urls": false,
"account_id": "YOUR_ACCOUNT_ID"
}
CONFIG
npx wrangler deploy -c health/wrangler.jsonc
npx wrangler deploy
内部ヘルスサービスを先にデプロイして、公開 Worker のバインディングが解決できるようにします。公開 Worker のデプロイ出力に schedule: * * * * * が含まれていることを確認します。実際の workers.dev アドレスをコピーして、以下に設定してください。
APP_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
curl -i "$APP_URL/health"
npx wrangler tail --format pretty > events.log 2> tail-errors.log &
tail 接続の初期化に数秒かかるため、少し待ってから合成チェックを 1 回送信します。
sleep 5
curl -i -X POST "$APP_URL/checks?probe=remote-one"
cat events.log
POST イベントと、source request およびプローブ remote-one を含む health_check ログを探します。まだイベントが表示されない場合は tail-errors.log を確認し、少し待ってからリクエストをもう一度送信し、events.log を再度読み取ります。以前の実験で作成されたログファイルは、このデプロイの証拠にはなりません。
Dashboard で、このアカウントの Compute → Workers & Pages を開きます。2 つの正確な名前、公開 Worker のアドレス、および公開 Worker の HEALTH バインディングが対応する内部サービスを指していることを確認します。検証を実行すると、認証済みの所有権とエンドポイント/バインディングのメタデータを確認した後、独自の短時間の live-tail セッションを開き、新しいプローブを送信してバックグラウンド処理の完了を確認します。評価システムは、証拠として events.log だけを信頼しません。次のステップのために、学習用の tail ジョブは実行したままにしてください。
実際の Cron 実行を確認する
このステップでは、デプロイ設定と実行を区別します。Dashboard で公開 Worker を開き、Settings → Trigger events → Cron triggers に移動します。スケジュールが Every minute と表示されていることを確認してください。Next time は予測時刻であり、実行が完了したことを示すものではありません。スケジュールが設定されているだけでは、ハンドラーが実行された証拠にはなりません。
以下の例は、サポートされている最短間隔を示しています。* * * * *、つまり 1 分に 1 回です。Worker 名を自分の設定と比較してください。名前と表示時刻は例です。Wrangler で設定済みの場合、Dashboard で別のトリガーを追加する必要はありません。

既存の学習用 tail 接続は実行したままにします。実際のスケジュール済みイベントが発生するまで待ち、同じログファイルを確認します。
sleep 60
cat events.log
読みやすい tail 出力から、Cron 式と実行時刻によって識別できる、成功したスケジュール済み呼び出しを探します。その health_check ログには、source scheduled、Cron * * * * *、status ok、サービス labex-health-fixture、および scheduledTime が含まれている必要があります。プローブは、その scheduledTime を後ろに付けた cron- です。独立した検証システムは、Cloudflare の構造化イベントメタデータとこのアプリケーションログを別々に照合します。POST イベント、ローカルの合成タイムスタンプ、空のログは、この結果の証拠になりません。
ブラウザーで出力を確認するには、同じ Worker の Observability → Events ページを開きます。待機中は Live を使用するか、デプロイ時刻を含む時間範囲で保存済みイベントクエリを更新します。このハンドラーの呼び出し行には * * * * * と表示されます。1 件を展開して View invocation を選択し、関連付けられたアプリケーションログの行を展開してヘルスチェックのフィールドを確認します。構造化アプリケーションログでは Message セルが空白になることがあります。データがないと判断せず、行を展開してください。読み取り中はライブ表示を一時停止できます。
この実際の例では、source は scheduled、status は ok、service は labex-health-fixture です。cron-... プローブは、アプリケーションログのミリ秒単位の scheduledTime と一致します。Dashboard は表示タイムゾーン(ここでは GMT+8)で時刻を表示しますが、Cron スケジュールは UTC を使用します。Worker 名、呼び出し ID、時刻は自分の環境では異なります。これらのフィールドを呼び出し結果と併せて確認してください。スクリーンショットだけでは処理完了の証拠になりません。

Cron の更新が反映されるまで最大 15 分かかる場合があります。1 分待って確認するサイクルを繰り返し、デプロイ成功時点から最大 17 分まで待機してください。ストリームが空、または停止している場合は tail-errors.log を確認します。この時間内に一致するイベントが表示されない場合は停止し、設定、認証、トリガーの状態を調査してください。成功したと報告してはいけません。これは時間を制限した学習用の観察であり、正確な実行遅延を保証するものではありません。反映と UTC スケジュールについては、Cron Triggers のドキュメントを参照してください。保存された Workers Logs の表示にも少し時間がかかる場合があります。取り込み時間を考慮してからクエリを更新してください。新しい Worker の別の Past Cron Events 履歴にイベントが表示されるまで、最大 30 分かかる場合があります。履歴が空でも失敗の証拠にはなりません。この実験で定めた時間内に、上記のリアルタイム観察を使用してください。
実行を確認した後に検証を実行します。検証では、デプロイ済みのスケジュールを確認し、最大 70 秒間、ヘルス結果を含む実際のスケジュール済みイベントを別のライブストリームで監視します。これにより、接続開始後の 1 分境界を完全にカバーできます。イベントがないという結果だけでは判断できないため、接続と反映の状態を確認し、同じ観察時間内で再試行してください。評価システムがスケジュール済みイベントを作り出すことはありません。検証が成功してから、学習用 tail を停止します。実際のジョブ番号を使用してください。
jobs
kill %1
次のステップを完了して、使い捨てのスケジュールを速やかに終了します。毎分実行される学習用ジョブを監視なしで残さないでください。
スケジュールテスト用の両方の Worker を削除する
このステップでは、認証済みの状態で、公開 Worker とそのトリガーを削除し、その後に内部ヘルスフィクスチャを削除します。両方の設定に、この実験で使用した正確な名前と対象アカウントが含まれていることを確認します。
cat wrangler.jsonc
cat health/wrangler.jsonc
npx wrangler delete
npx wrangler delete -c health/wrangler.jsonc
一致する名前の確認プロンプトが表示されるたびに、1 文字の y を入力します。関係のないプロジェクト、アカウント、サブドメインは変更しないでください。固定バージョンの Wrangler では、Worker 削除後に既知のレガシー KV クリーンアップ認証診断が表示される場合があります。そのメッセージも、ネットワークリクエストの失敗も、削除完了の証拠にはなりません。この診断のために権限を広げないでください。
Dashboard を更新して検証を実行します。認証済みの Worker 一覧で、両方の名前が存在しないことを確認できれば成功です。これはデプロイ済みリソースが削除されたことを確認するものであり、すべてのスケジューラー変更が全世界に即座に反映されたことを示すものではありません。ログアウトや VM を閉じるだけでは、このクリーンアップは実行されません。
Lab VM を切断する
このステップでは、この VM を切断する前に、tail ジョブが停止し、リソースが削除されていることを確認します。
jobs
npx wrangler logout
npx wrangler whoami --json
loggedIn: false が明示されていることを確認してください。構造化された未認証コマンドは、ゼロ以外の終了コードになる場合があります。ネットワークエラーは同じ結果ではありません。検証を実行して VM を終了します。ブラウザーのログイン状態は、後で行う独立した実験のために残しておいてかまいません。
まとめ
waitUntil を使って、フォアグラウンドで受付確認を返した後も、時間を制限したヘルスチェックを完了させました。その後、同じ処理をスケジュール済みハンドラーから再利用しました。制御されたローカルテストによって、応答と遅延処理を分離して確認し、実際のクラウドイベントによってデプロイ後のスケジュール実行を確認しました。設定、手動呼び出し、ライブ実行から、それぞれ異なる種類の証拠を得ました。
所有権とサービスバインディングを確認し、合成プローブと構造化ログを関連付け、呼び出しの有効期間の制限を守り、切断前に使い捨てのスケジュール済みアプリケーションを削除しました。信頼性の高い長時間の配信には、この短時間のバックグラウンド処理パターンとは異なるアーキテクチャが必要です。

