⏱️

エコに GitHub Actions で外部ジョブの実行終了を待機しよう

2023/12/23に公開

はじめに

https://qiita.com/advent-calendar/2023/github-actions

GitHub Actions で実行時間がかかる処理を待機する場合、どうしますか?

ちゃんと作るなら Argo WorkflowsAWS Step Functions などを組み合わせてワークフローを組むのでしょう。でもめんどくさい...

実行を終了したことをジョブ自体が通知できるのであれば非同期( Pub/Sub )を使うのも手でしょう。でも実行終了を通知できない または 通知する仕組みを作るのがめんどくさい場合はどうでしょう。

手を抜いて GitHub Actions 内で待機するのも手です。いわゆるポーリングです。イメージやコードとしては以下のようなものになります。

job.trigger.yaml
name: job trigger
run-name: ${{ github.ref_name }} by @${{ github.actor }} at ${{ github.workflow }}
on:
  workflow_dispatch:
jobs:
  trigger:
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Trigger
        run: echo "trigger"
      - name: Check
        run: |
          count=1
          while [[ true ]] ; do
            if [[ ${count} -gt 3 ]]; then
              echo "timeout"
              exit 1
            fi
            count=$((count + 1))
            sleep 60
          done
          echo "done"

でも GitHub Actions で処理を実行し待機するととその分だけ実行時間がかかります。
コスト計算をちゃんとしたことないですが、実行時間でお金がかかるはずです。

https://docs.github.com/ja/billing/managing-billing-for-github-actions/about-billing-for-github-actions

お金がかからないにしても何もしない時間が多いのにリソースを使うのはもったいないですよね。エコじゃないです。SDGsに反します。

そこで考えたのが GitHub Actions の schedule cron の設定を切り替えることにより行う方法です。サンプルコード的に考えてみたのでご紹介します。

用語整理

ジョブ: Kubernetes の Jobs
https://kubernetes.io/docs/concepts/workloads/controllers/job/

ワークフロー: GitHub Actions の Workflow
https://docs.github.com/ja/actions/using-workflows/about-workflows

フロー

ジョブをワークフローから起動してその実行を待機するのをイメージしています。

  1. Jobs を起動しクーロンワークフローを有効にするワークフローをキックする
  2. クーロンワークフローを有効にする
  3. クーロンワークフローにてジョブの実行完了を監視する
  4. ジョブの実行完了が確認できたらクーロンワークフローを無効にするワークフローをキックする
  5. クーロンワークフローを無効にする

サンプルコード

説明のためにフローは以下のように簡略化します。
ジョブが起動されクーロンを有効、ジョブ監視をし無効にするまでの流れです。

ジョブを管理するワークフロー cron.yaml は以下のような実装となります。

name: cron
run-name: ${{ github.ref_name }} by @${{ github.actor }} at ${{ github.workflow }}
on:
  workflow_dispatch:
  schedule:
    - cron: '*/10 * * * *'
jobs:
  schedule_cron:
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Check
        id: check
        # 理想はここでジョブの実行をチェックして実行が終了したかどうかのフラグを立てる
        run: echo "done=true" >> "$GITHUB_OUTPUT"
      - name: Generate token
        if: ${{ steps.check.outputs.done }}
        id: generate-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.BOT_GITHUB_APP_ID }}
          private-key: ${{ secrets.BOT_GITHUB_APP_PRIVATE_KEY }}
      - name: Repository dispatch
        if: ${{ steps.check.outputs.done }}
        uses: peter-evans/repository-dispatch@v2
        with:
          token: ${{ steps.generate-token.outputs.token }}
          repository: ${{ github.repository }}
          event-type: disable

ジョブを有効にするワークフロー cron.enable.yaml は以下のような実装となります。

name: cron enable
run-name: ${{ github.ref_name }} by @${{ github.actor }} at ${{ github.workflow }}
on:
  workflow_dispatch:
jobs:
  enable:
    runs-on: ubuntu-22.04
    steps:
      - name: Generate token
        id: generate-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.BOT_GITHUB_APP_ID }}
          private-key: ${{ secrets.BOT_GITHUB_APP_PRIVATE_KEY }}
      - name: Checkout
        uses: actions/checkout@v4
        with:
          token: ${{ steps.generate-token.outputs.token }}
      - name: Enable
        env:
          GH_USER_EMAIL: <app_id>+<app_name>@users.noreply.github.com
          GH_USER_NAME: <app_name>
          GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
        run: |
          ./script/enable.sh ./.github/workflows/cron.yaml
          diff=$(git diff ./.github/workflows/cron.yaml)
          if [[ -n "${diff}" ]]; then
            git config --global user.email ${{ env.GH_USER_EMAIL }}
            git config --global user.name ${{ env.GH_USER_NAME }}
            git add ./.github/workflows/cron.yaml
            git commit -m "enable"
            git push
          fi

cron.yaml のクーロンスケジュールを有効にする ./script/enable.sh

#!/bin/bash

count=0

while read line; do

    count=$(($count + 1))

    if [[ $count -eq 5 ]] ; then
        sed -i -e '5s/^#//' $1
    fi

    if [[ $count -eq 6 ]]; then
        sed -i -e '6s/^#//' $1
    fi

done < $1

ジョブを無効にするワークフロー cron.disable.yaml は以下のような実装となります。

name: cron disable
run-name: ${{ github.ref_name }} by @${{ github.actor }} at ${{ github.workflow }}
on:
  workflow_dispatch:
  repository_dispatch:
    types:
      - disable
  schedule:
    - cron: '0 15 * * *'
jobs:
  disable:
    runs-on: ubuntu-22.04
    steps:
      - name: Generate token
        id: generate-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.BOT_GITHUB_APP_ID }}
          private-key: ${{ secrets.BOT_GITHUB_APP_PRIVATE_KEY }}
      - name: Checkout
        uses: actions/checkout@v4
        with:
          token: ${{ steps.generate-token.outputs.token }}
      - name: Disable
        env:
          GH_USER_EMAIL: <app_id>+<app_name>@users.noreply.github.com
          GH_USER_NAME: <app_name>
          GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
        run: |
          ./script/disable.sh ./.github/workflows/cron.yaml
          diff=$(git diff ./.github/workflows/cron.yaml)
          if [[ -n "${diff}" ]]; then
            git config --global user.email ${{ env.GH_USER_EMAIL }}
            git config --global user.name ${{ env.GH_USER_NAME }}
            git add ./.github/workflows/cron.yaml
            git commit -m "disable"
            git push
          fi

cron.yaml のクーロンスケジュールを無効にする script/disable.sh

#!/bin/bash

count=0

while read line; do

    count=$(($count + 1))

    if [[ ${line:0:1} == "#" ]]; then
        continue
    fi

    if [[ $count -eq 5 ]] ; then
        sed -i -e '5s:^:#:' $1
    fi

    if [[ $count -eq 6 ]]; then
        sed -i -e '6s:^:#:' $1
    fi

done < $1

コードを見ていただいたようにシェルスクリプトにて cron.yaml のスケジュール設定(5行目6行目)をただコメントアウトの切り替えをしているだけです。(とりあえず動けばいいやで作ったのでシェルスクリプトがあまりにもイけていない...)

おわりに

考えてはみたもののちょっと複雑ですし安定稼働できるか自信もないです。
実務ではとりあえずジョブ起動のワークフローでジョブの結果を待機する実装にすると思います。
また、記事を書いていて気づいたのですがこのフローだとジョブ結果待機に対してタイムアウトを設定できていないですね...
誰かの参考になれば幸いです。

参考

https://zenn.dev/tmknom/articles/github-apps-token

Discussion