🚫

GitHub Actionsの schedule トリガーで祝日をスキップさせて不要なコストを削減した話

2024/12/08に公開

こんにちは、ツクリンクでSREエンジニアをやってるida.です。
以前、社内でGitHub Actionsでscheduleトリガーを利用して定期的に環境の起動・停止するワークフローを実装しました。その中で、祝日は実行が不要なのに毎回ワークフローが実行されていたので、稼働時間を節約するために祝日は実行しないようにしたいなと思い対応しました。他のサイトにも同様の対応があることにはあるのですが、ジョブを跨いで制御している記事はあまりなかったので記事にしようと思います。

なぜ祝日にスキップする対応が必要なのか?

GitHub Actionsで定期実行する方法としてscheduleというトリガーイベントがありますが、こちらはcron形式になります。cronでは規則的な日時または間隔でトリガーできますが、祝日のようなイレギュラーな日に対応することができません。
そのため、ワークフローのはじめに祝日かどうかを判定して、祝日の場合は後続ジョブを実行せずにスキップすることで実質実行しないようなワークフローにすることにしました。

GitHub Actionsでは、scheduleトリガーを利用して定期実行のジョブを設定できます。このトリガーではcron形式を使って、規則的な日時や間隔でジョブをトリガーできますが、祝日のようなイレギュラーな日を考慮することはできません。
例えば、祝日にジョブを実行すると以下の問題が発生します。

  • サーバーリソースや実行コストが無駄になる
  • チームメンバーが対応できないタイミングで通知やエラーが発生し、無駄になる
  • (あまりないと思いますが)外部APIやパッケージが障害等で動作しない場合、不要なエラーや実行が終わらないような不具合が祝日に発生する可能性がある

このような問題を避けるために、ワークフローの最初で「今日が祝日かどうか」を判定し、祝日であればジョブをスキップする仕組みを導入しました。

GitHub Actionsで祝日に処理をスキップする方法

前述した通り、ワークフローのはじめに祝日かどうかを判定するジョブを追加します。
コードを見た方が早いと思うので先にコードを記載します。

on:
  schedule:
    - cron: '0 5 * * 1-5'

jobs:
  ##### 祝日かどうか確認 #####
  check_holiday:
    runs-on: ubuntu-latest
    outputs:  
      holiday: ${{ steps.is_holiday.outputs.holiday }}
    steps:
      - uses: actions/setup-node@v4
      - run: npm install @holiday-jp/holiday_jp
      - uses: actions/github-script@v7
        id: is_holiday
        with:
          script: |
            const holiday_jp = require(`${process.env.GITHUB_WORKSPACE}/node_modules/@holiday-jp/holiday_jp`)
            // new Date() はUTCなので、日本時間に変換
            const jstDate = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Tokyo' }));
            core.setOutput('holiday', holiday_jp.isHoliday(jstDate));

  ##### 後続ジョブの実行 #####
  subsequent_job:
    needs: [check_holiday]
    # 祝日じゃなければ後続ジョブを実行する
    if: needs.check_holiday.outputs.holiday != 'true'

コードの説明

  • outputsでジョブ間で祝日かどうかを連携する
    一つのジョブ内で祝日かどうか判定し後続ステップを実行する際は問題ないですが、ジョブを分ける場合はoutputsで祝日かどうかのフラグを持ち回る必要があります。
    以下の通りoutputsを明示して定義します。

    outputs:  
      holiday: ${{ steps.is_holiday.outputs.holiday }}
    

    その後、後続ジョブでneedsフィールドで依存関係を明記した上でoutputsで受け取って情報を利用して祝日かどうか判定します。

    needs: [check_holiday]
    if: needs.check_holiday.outputs.holiday != 'true'
    
  • 祝日判定ジョブの概要
    祝日判定ジョブはnodeを利用します。
    日本時間に変換して、実行時の時間が祝日かどうか確認します。
    setOutputで出力して後続ジョブに渡すようにします。

    script: |
      const holiday_jp = require(`${process.env.GITHUB_WORKSPACE}/node_modules/@holiday-jp/holiday_jp`)
      // new Date() はUTCなので、日本時間に変換
      const jstDate = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Tokyo' }));
      core.setOutput('holiday', holiday_jp.isHoliday(jstDate));
    

実装時の注意事項

  1. タイムゾーンの設定
    GitHub ActionsではUTCがデフォルトです。そのため、通常実行するとUTCで処理されるので、深夜帯実行のワークフローだと実行日がずれてしまう可能性があります。日本時間に変換して判定をするようにします。
     
  2. checkoutはしないようにする
    はじめはcheck_holidayでもcheckoutするようにしていたのですが、すでにリポジトリ内でnpmを利用している場合、npm installがリソースに干渉して失敗することがあります。
    check_holidayではcheckoutする必要がないのでcheckoutをしないようにしました。

最後に

GitHub Actionsのcronスケジュールに祝日を考慮することで、無駄なジョブ実行を回避し、効率的な運用を実現できます。ただ、この方式でもワークフロー自体は一瞬ですが、動作していることになります。やっぱりスケジュール段階で制御して起動しないようできると本当は嬉しいな。

Discussion