本文へ移動
ZEKILO Dev
変換

GitHub Actions・K8sのcronとUTCの罠

2026.10.03 更新 · 6分で読めます

GitHub Actionsのcronは既定でUTC、KubernetesのCronJobはコントローラーのタイムゾーンで動きます。日本時間に合わせる方法、5フィールドの書き方、日と曜日を同時に指定したときのORルールを解説します。

Cron式の解説をすぐに使う

Cron式そのものにタイムゾーンはなく、実行するプラットフォームが決めます。GitHub Actionsのscheduleは既定でUTC、タイムゾーンを指定していないKubernetesのCronJobはkube-controller-managerのローカルタイムゾーンで評価されます。そのため0 9 * * 1-5は、GitHubでは日本時間の午前9時ではなく午後6時に実行されます。時刻をUTCに直して書くか、プラットフォームのタイムゾーン指定を使ってください。式をCron式の解説に貼り付けると、意味と次回の実行日時をUTCと日本時間で確認できます。

5フィールドの書き方

どちらのプラットフォームも、昔からある5フィールドの形式を使います。

┌──────── 分       0-59
│ ┌────── 時       0-23
│ │ ┌──── 日       1-31
│ │ │ ┌── 月       1-12
│ │ │ │ ┌ 曜日     0-6(0 = 日曜日)
* * * * *
演算子 意味 例
* すべての値 * * * * * 毎分
, リスト 0 9,18 * * * 9時と18時
- 範囲 0 9 * * 1-5 月曜から金曜の9時
/ 間隔 */15 * * * * 15分ごと

POSIXが定めているのは、アスタリスク、数値、範囲、リストだけです。/による間隔指定はVixie cronの拡張で、GitHub ActionsとKubernetesのドキュメントはどちらも対応していると記載しています。

タイムゾーンをAsia/Tokyoにして計算した結果です。

式 基準時刻 次回の実行
0 9 * * 1-5 2026-10-02(金)10:00 10-05(月)09:00、続いて10-06・10-07・10-08・10-09の09:00
*/15 * * * * 2026-10-02 10:07 10:15、10:30、10:45、11:00、11:15
0 0 31 2 * いつでも 実行されない(2月31日は存在しない)
0 25 * * * いつでも エラー:時のフィールドは0から23まで

2つのプラットフォームの違い

2026年10月時点の公式ドキュメントの内容です。

項目 GitHub Actionsのschedule KubernetesのCronJob
既定のタイムゾーン UTC kube-controller-managerのローカルタイムゾーン
タイムゾーンの指定 timezoneキー(IANA名) .spec.timeZone(IANA名、v1.27から安定版)
@dailyなどのマクロ 非対応 @yearly @monthly @weekly @daily @hourly
/による間隔 対応 対応
名前での指定 JAN-DEC、SUN-SAT 曜日のsunからsat
疑問符? 記載なし *と同じ意味
最短の間隔 5分ごと 記載なし(ドキュメントの例は毎分実行)

Kubernetesは、.spec.scheduleの中にCRON_TZ=やTZ=を書く方法には対応しておらず、検証エラーになります。タイムゾーンのフィールドを使ってください。

数字で見るUTCの罠

日本時間(JST)はUTC+9で、サマータイムはありません。2026年10月2日(金)の午前10時に、平日9時の実行を期待して0 9 * * 1-5をGitHubのワークフローに入れたとします。UTCで評価されるため、最初の実行は同じ金曜日の09:00 UTC、日本時間の18:00で、その後も毎回18:00に実行されます。

日本時間に合わせるには9時間を引きます。

目標(JST) UTCでの式 説明
月曜から金曜 09:00 0 0 * * 1-5 00:00 UTCは同じ日の09:00
月曜から金曜 08:00 0 23 * * 0-4 23:00 UTCは前日なので、曜日も1日前にずらす
月曜から金曜 08:00 0 23 * * 1-5 誤り:火曜から土曜の08:00に実行される

見落としやすいのは2行目と3行目です。変換した時刻が日付をまたぐときは、曜日と日のフィールドも一緒にずらす必要があります。変換できないスケジュールもあります。「毎月1日の00:00 JST」は前月の最終日の15:00 UTCですが、5フィールドのcronには「最終日」を書く方法がありません。

タイムゾーンを直接指定すれば、こうした計算は不要です。ドキュメントの文法に従うと次のようになります。

# GitHub Actions
on:
  schedule:
    - cron: "0 9 * * 1-5"
      timezone: "Asia/Tokyo"
# Kubernetes CronJob
spec:
  schedule: "0 9 * * 1-5"
  timeZone: "Asia/Tokyo"

Kubernetesでは、いま正しく動いていても指定しておくことをおすすめします。マネージドクラスターではkube-controller-managerのタイムゾーンを把握しにくいためです。サマータイムのある地域では、UTCで書いたスケジュールが現地時間では年に2回、1時間ずれます。GitHubのドキュメントによると、timezoneを指定したスケジュールがサマータイム開始で飛ばされる時間帯に当たると、次の有効な時刻に進みます(例:午前2:30は3:00)。

日と曜日を同時に指定するとOR

POSIXは、日(または月)と曜日の両方を指定した場合、どちらか一方に合う日を一致とみなすと定めています。ANDではなくORです。

そのため0 0 13 * 5は「13日の金曜日」ではありません。Asia/Tokyoで2026-10-02(金)10:00から計算すると、10-09(金)、10-13(火)、10-16(金)の00:00に実行されます。毎週金曜日と毎月13日の両方です。

GitHubのドキュメントはPOSIXのcron構文を使うと明記しています。Kubernetesのドキュメントがリンクしているrobfig/cronも、2つのフィールドがどちらもアスタリスクでなければ、片方が合うだけで実行する実装です。「13日かつ金曜日」が必要なら、一方の条件だけをスケジュールにし、もう一方はジョブの中で確認してください。Cron式の解説ツールは、両方を指定するとこのルールを案内します。

そのほかに知っておきたい動作

GitHub Actions

  • 負荷が高いときはスケジュール実行が遅れることがあり、ドキュメントは毎時0分を負荷の高い時間として挙げています。負荷が非常に高いと、待機中のジョブが実行されないこともあります。17 0 * * *のように0分以外を選んでください。
  • スケジュールされたワークフローは既定のブランチの最新コミットで実行され、ワークフローファイルが既定のブランチに存在する必要があります。
  • パブリックリポジトリでは、60日間リポジトリに活動がないと、スケジュールされたワークフローが自動的に無効になります。

Kubernetes

  • CronJobはスケジュールごとにJobを「おおよそ1回」作成します。ドキュメントは、状況によってJobが2つ作られたり作られなかったりすることがあるため、Jobを冪等にするよう案内しています。
  • concurrencyPolicyは、前のJobがまだ実行中のときの動作を決めます:Allow(既定)、Forbid、Replace。
  • startingDeadlineSecondsは、逃したJobをどれだけ遅れて開始してよいかを決めます。逃したスケジュールが100を超えると、コントローラーはJobを開始せずエラーを記録します。

ZEKILO Devで確認する

  1. Cron式の解説に式を貼り付けます。形式はフィールドの数から自動で判定されます。
  2. タイムゾーンをUTCにするとGitHub Actionsの既定の動作を、Asia/Tokyoにすると意図した日本時間を確認できます。
  3. 解説文と次回5回分の実行日時を、想定したスケジュールと比べます。

式はブラウザ内で解析し、サーバーには送信しません。

まとめ

  • Cronにタイムゾーンはありません。GitHub ActionsはUTC、Kubernetesはコントローラーのローカルタイムゾーンが既定です。
  • UTCに直すときは、日付をまたぐなら曜日と日のフィールドもずらします。可能ならtimezone(GitHub)か.spec.timeZone(Kubernetes)を指定してください。
  • 日と曜日の両方を指定すると、どちらか一方に合う日に実行されます。
  • GitHub Actionsでは0分を避け、KubernetesのJobは冪等にしてください。

この記事に関連するツール

出典と基準

ほかのガイド