エコに GitHub Actions で外部ジョブの実行終了を待機しよう
はじめに
GitHub Actions で実行時間がかかる処理を待機する場合、どうしますか?
ちゃんと作るなら Argo Workflows や AWS 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 で処理を実行し待機するととその分だけ実行時間がかかります。
コスト計算をちゃんとしたことないですが、実行時間でお金がかかるはずです。
お金がかからないにしても何もしない時間が多いのにリソースを使うのはもったいないですよね。エコじゃないです。SDGsに反します。
そこで考えたのが GitHub Actions の schedule cron の設定を切り替えることにより行う方法です。サンプルコード的に考えてみたのでご紹介します。
用語整理
ジョブ: Kubernetes の Jobs
ワークフロー: GitHub Actions の Workflow
フロー
ジョブをワークフローから起動してその実行を待機するのをイメージしています。
- Jobs を起動しクーロンワークフローを有効にするワークフローをキックする
- クーロンワークフローを有効にする
- クーロンワークフローにてジョブの実行完了を監視する
- ジョブの実行完了が確認できたらクーロンワークフローを無効にするワークフローをキックする
- クーロンワークフローを無効にする
サンプルコード
説明のためにフローは以下のように簡略化します。
ジョブが起動されクーロンを有効、ジョブ監視をし無効にするまでの流れです。
ジョブを管理するワークフロー 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行目)をただコメントアウトの切り替えをしているだけです。(とりあえず動けばいいやで作ったのでシェルスクリプトがあまりにもイけていない...)
おわりに
考えてはみたもののちょっと複雑ですし安定稼働できるか自信もないです。
実務ではとりあえずジョブ起動のワークフローでジョブの結果を待機する実装にすると思います。
また、記事を書いていて気づいたのですがこのフローだとジョブ結果待機に対してタイムアウトを設定できていないですね...
誰かの参考になれば幸いです。
参考
Discussion