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で確認する
- Cron式の解説に式を貼り付けます。形式はフィールドの数から自動で判定されます。
- タイムゾーンをUTCにするとGitHub Actionsの既定の動作を、Asia/Tokyoにすると意図した日本時間を確認できます。
- 解説文と次回5回分の実行日時を、想定したスケジュールと比べます。
式はブラウザ内で解析し、サーバーには送信しません。
まとめ
- Cronにタイムゾーンはありません。GitHub ActionsはUTC、Kubernetesはコントローラーのローカルタイムゾーンが既定です。
- UTCに直すときは、日付をまたぐなら曜日と日のフィールドもずらします。可能なら
timezone(GitHub)か.spec.timeZone(Kubernetes)を指定してください。 - 日と曜日の両方を指定すると、どちらか一方に合う日に実行されます。
- GitHub Actionsでは0分を避け、KubernetesのJobは冪等にしてください。