はじめに
システム開発や運用において、「毎日決まった時間にスクリプトを動かしたい」というケースは多いですよね。
この記事では、私が実際に作成した2つの定期バッチを例に、GitHub Actionsのcron機能の遅延問題と、代替案として構築した「Cloud Scheduler + Cloud Run Job」の精度について比較した結果をご紹介します。
1. 10時に設定したバッチが10時に動かない?
日々の業務を自動化するため、新たに2つの定期実行バッチを作成しました。
最初は手軽に設定できるGitHub Actionsのcron機能を使っていたのですが、運用開始直後に違和感を覚えました。
「毎朝10時に設定したはずなのに、10時に実行されない…?」
毎回30分以上遅れて、Slackに通知が来ています。
そこで、もう1つのバッチをGoogle Cloudの「Cloud Scheduler + Cloud Run Job」構成で作成し、両者の実行タイミングにどのような違いが出るのかを比較してみました。
2. GitHub Actionsのcron設定と注意すべき「罠」
まずは、GitHub Actionsにおける定期実行の設定方法と、最初につまずきやすいポイントをおさらいします。
cron式の書き方と「UTC」の罠
GitHub Actionsで定期実行を設定するには、ワークフローファイルのon.scheduleにcron式を記述します。
ここで注意すべき最大の罠は、時間がUTC(協定世界時)ベースであることです。
日本のJST(日本標準時)はUTCから+9時間なので、日本時間で「平日朝10時」に動かしたい場合は、9時間を引いて「UTCの深夜1時」と指定する必要があります。
on:
schedule:
# JSTの10:00に実行したい場合は、UTCの01:00を指定する
- cron: '0 1 * * 1-5'
手動実行との組み合わせ
開発中やエラー発生時のリトライを考慮して、workflow_dispatch を組み合わせておくのがおすすめです。
これにより、GitHubの画面上から手動で即時実行が可能になります。
on:
schedule:
- cron: '0 1 * * 1-5'
workflow_dispatch: # 手動実行用トリガー
3. なぜGitHub Actionsの定期実行は時間がズレるのか?
指定通りにcronを書いているのに、なぜ時間がズレるのでしょうか。
その理由は、GitHub Actionsのシステムアーキテクチャにあります。
指定時刻から実行までのメカニズム
GitHub Actionsのcronは「指定した時刻で即座に実行される」わけではありません。
正確には以下のようなフローを辿ります。
- 指定時刻になる
- ジョブがキュー(待機列)に積まれる
- 共有のRunner(実行環境)が確保され次第、実行が開始される
共有インフラの高負荷が遅延を生む
GitHubのホストランナーはジョブごとに新しいVMが割り当てられますが、インフラ全体は共有されているため、高負荷時には遅延が発生します。
公式ドキュメントによると、毎時の開始時点は特に高負荷になりやすいとのことです。
GitHubの公式ドキュメントでは以下のように言及されています。
GitHub Actions のワークフローの実行によって高い負荷がかかっている間、schedule イベントが遅延する可能性があります。高負荷の時間帯には、毎時の開始時点が含まれます。負荷が十分に高い場合、キューに登録されたジョブの一部が削除される可能性があります。
4. 実際どのくらいズレる?遅延の検証結果
では、実際にどれくらいの遅延が発生するのかを見てみましょう。
以下は、該当のバッチ処理が完了した際に送られてくるSlackの通知時刻を複数日分並べたものです。
スクリーンショットから分かるように、54分・38分・43分と、毎回30分以上のズレが発生しています。
遅延の幅もまちまちで、いつ実行されるかの予測が立ちません。
厳密な時間管理が求められるバッチ処理には不向きです。
5. Cloud Scheduler + Cloud Run Jobの構成
GitHub Actionsの遅延問題を踏まえ、もう一つのバッチはGoogle Cloud(GCP)のサービスである「Cloud Scheduler」と「Cloud Run Job」を組み合わせて構築しました。
全体像
アーキテクチャは非常にシンプルです。
Cloud Scheduler → (HTTPリクエスト) → Cloud Run Job実行
構築のステップ
- Dockerfileの作成: 実行したいスクリプトをコンテナ化
- Cloud Run Jobの作成: ビルドしたイメージをArtifact Registryにプッシュし、Cloud Run Jobとして登録
- Cloud Schedulerの設定: 指定した時刻にCloud Run JobへHTTPリクエストを送るよう設定
詳細な構築手順は公式ドキュメントを参照してください。
Cloud SchedulerはタイムゾーンをJSTなどで直接指定できます。
UTCに変換する必要があるGitHub Actionsとは異なり、直感的に設定できる点もメリットの一つです。
6. Cloud Schedulerはなぜ時間がズレないのか?
Cloud Schedulerの構成では時間がほとんどズレません。
「キュー」ではなく、「ダイレクトなHTTP実行」
GitHub Actions: 指定時刻 → キューに積む → runner確認待ち → 実行
Cloud Scheduler: 指定時刻 → HTTPリクエスト送信 → Cloud Run Job起動
Cloud Schedulerは、時間になると指定されたエンドポイントを直接叩きに行きます。
共有の実行待ちキューに並ぶプロセスがないため、設定した時間通りにトリガーが引かれます。
コンテナ起動によるわずかなタイムラグ
ただし、スケジューラーが実行をトリガーしてからSlack通知が届くまでの間に、コンテナの起動やスクリプトの処理時間が加わります。
実際の運用では、設定時刻から1分程度のズレで安定しています。
実際に運用しているSlack通知の画面がこちらです。
連日「10:01」に通知が来ており、遅延時間が非常に安定している(予測可能である)ことが分かります。
7. まとめ:どちらの定期実行を選ぶべきか?
最後に、それぞれの特徴を一覧表で整理しました。
| GitHub Actions cron | Cloud Scheduler + Cloud Run Job | |
|---|---|---|
| 時刻精度 | 低い | 高い |
| タイムゾーン指定 | UTCのみ | 直接指定可能 |
| 設定のしやすさ | 簡単 | やや手間 |
| リポジトリ依存 | あり | なし |
| コスト | 無料枠あり | 従量課金 |
- 時刻精度:GitHub Actionsは30分以上の遅延が発生しますが、Cloud Schedulerは約1分程度で安定
- タイムゾーン指定:GitHub ActionsはUTCのみのため、JSTに変換する必要がありますが、Cloud SchedulerはタイムゾーンをJSTで直接指定可能
- コスト:GitHub Actionsはパブリックリポジトリなら無料で、Cloud Schedulerは少量の実行なら無料枠内に収まる
今回作成した2つのバッチは実行内容が異なるため、厳密な比較実験ではありません。
実運用の中で気づいた差異として参考にしてください。
結論
- 数十分の遅延が許容できる・設定の手間を省きたい → GitHub Actions cron
- 決まった時間に正確に実行したい・独立した基盤が欲しい → Cloud Scheduler + Cloud Run Job
用途に合わせた使い分けが、快適な運用への近道です。

