無駄なデプロイを抑える
はじめに
開発環境へのデプロイをGitHub Actionsで平日の毎朝6時に行うようにしているのですが、コミットがない場合は差分がないのにデプロイが走ってしまい、GitHub Actionsの稼働時間がわずかとはいえランナー時間を浪費してしまいます。
これを抑えるために追加ジョブで重複デプロイを防ぐ方法を紹介します。
事前準備
Repository Variables
Repository VariablesにデプロイしたコミットSHAを保存する変数を用意します。
以下のURLのorg/repo
を自分のリポジトリに書き換えてアクセスすると、
https://github.com/org/repo/settings/variables/actions
下記のような設定画面が表示されると思います。
右下の「New repository variable」から新しくRepository Variableを作ります。
ここではワークフロー全体にあるワークフローに記載があるAPI_LATEST_DEPLOYED_COMMIT_ID
という名前で登録します。値は適当に登録します。
GitHub Appsの作成
ワークフロー内でRepository Variablesを読み書きするため、権限を付与したGitHub Appsを作成します。(GitHub Appsを使わずにできる迂回策があるかもしれません...)
今回はリポジトリが所属している組織の設定画面からGitHub Appsを作成します。
Repository permissionsにあるVariablesのAccessをRead and writeに設定してください。
GitHub Appsを作成後、対象のリポジトリにインストールしてください。
インストール後、対象のリポジトリの設定画面からRepository Secretsに作成したGitHub AppsのAPP_ID
、PRIVATE_KEY
を登録してください。
ワークフロー全体
まずはワークフロー全体です。それぞれのジョブがしていることについては後述します。
軽く説明すると、デプロイしたコミットSHAをRepository Variablesに保存しておいて、デプロイ前にデプロイしようとしているコミットSHAと比較してデプロイをスキップできるようにします。
実際のワークフローは以下のように動きます。
- デプロイされていないコミットの場合
- デプロイされているコミットの場合
name: Deploy
on:
schedule:
- cron: "0 21 * * 0-4" # 平日朝6時(日本時間)
workflow_dispatch:
inputs:
environment:
type: environment
required: true
defaults:
run:
shell: bash
jobs:
deployment-check-on-schedule:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
result: ${{ steps.compare.outputs.result }}
steps:
- id: repository
run: echo "name=${GITHUB_REPOSITORY#${GITHUB_REPOSITORY_OWNER}/}" >> $GITHUB_OUTPUT
- uses: actions/create-github-app-token@v1
id: create_token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ steps.repository.outputs.name }}
- name: Compare the current commit id and the latest deployed commit id
id: compare
run: |
current_commit_id=${{ github.sha }}
latest_deployed_commit_id=$(gh variable get API_LATEST_DEPLOYED_COMMIT_ID -R ${{ github.repository }})
echo "current_commit_id: ${current_commit_id}"
echo "latest_deployed_commit_id: ${latest_deployed_commit_id}"
echo "result=$([ $current_commit_id = $latest_deployed_commit_id ]; echo $?)" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ steps.create_token.outputs.token }}
deploy:
name: deploy
needs: deployment-check-on-schedule
if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'schedule' && needs.deployment-check-on-schedule.outputs.result == '1') }}
uses: ./.github/workflows/internal_deploy.yaml
with:
environment: ${{ github.event.inputs.environment || 'dev' }}
workingDirectory: ./cdk/api/
secrets: inherit
set-latest-deployed-commit-id:
name: Set the latest deployed commit id
runs-on: ubuntu-latest
needs: deploy
timeout-minutes: 5
steps:
- id: repository
run: echo "name=${GITHUB_REPOSITORY#${GITHUB_REPOSITORY_OWNER}/}" >> $GITHUB_OUTPUT
- uses: actions/create-github-app-token@v1
id: create_token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ steps.repository.outputs.name }}
- name: Set the latest deployed commit id
run: gh variable set API_LATEST_DEPLOYED_COMMIT_ID -b ${{ github.sha }} -R ${{ github.repository }}
env:
GH_TOKEN: ${{ steps.create_token.outputs.token }}
ワークフローの説明
デプロイ前ジョブ
デプロイ前に、デプロイ済みのコミットSHAとデプロイしようとしているコミットSHAを比較します。
deployment-check-on-schedule:
runs-on: ubuntu-latest
timeout-minutes: 5
# 次のジョブ(deploy)に渡すために比較結果を格納
outputs:
result: ${{ steps.compare.outputs.result }}
steps:
# github contextからリポジトリの名前だけを直接取得できないため、bashの変数展開をつかってリポジトリ名だけを取得する
# org/repo から repo だけ取得
- id: repository
run: echo "name=${GITHUB_REPOSITORY#${GITHUB_REPOSITORY_OWNER}/}" >> $GITHUB_OUTPUT
# Repository Variableを読むためのトークンをGitHub Appsから発行
- uses: actions/create-github-app-token@v1
id: create_token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ steps.repository.outputs.name }}
# デプロイ済みのコミットSHAと、デプロイしようとしているコミットSHAの比較
# 比較結果をGITHUB_OUTPUTに出力
- name: Compare the current commit id and the latest deployed commit id
id: compare
run: |
current_commit_id=${{ github.sha }}
latest_deployed_commit_id=$(gh variable get API_LATEST_DEPLOYED_COMMIT_ID -R ${{ github.repository }})
echo "current_commit_id: ${current_commit_id}"
echo "latest_deployed_commit_id: ${latest_deployed_commit_id}"
echo "result=$([ $current_commit_id = $latest_deployed_commit_id ]; echo $?)" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ steps.create_token.outputs.token }}
デプロイジョブ
実際にデプロイを行うジョブです。
needs
で前提となるデプロイ前ジョブを指定し、if
条件でジョブを実行するか、スキップするかを制御します。
deploy:
name: deploy
needs: deployment-check-on-schedule
# マニュアル実行
# または
# スケジュール実行 かつ コミットSHAの比較結果が一致しなかったとき
# 上記の条件を満たしたときにジョブを実行します
if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'schedule' && needs.deployment-check-on-schedule.outputs.result == '1') }}
uses: ./.github/workflows/internal_deploy.yaml
with:
environment: ${{ github.event.inputs.environment || 'dev' }}
workingDirectory: ./cdk/api/
secrets: inherit
デプロイ後ジョブ
デプロイ成功後、デプロイされたコミットSHAをRepository Variablesに保存するジョブです。
needs
で前提となるデプロイジョブを指定し、gh
コマンドを使ってAPI_LATEST_DEPLOYED_COMMIT_ID
変数を更新します。
set-latest-deployed-commit-id:
name: Set the latest deployed commit id
runs-on: ubuntu-latest
needs: deploy
timeout-minutes: 5
steps:
- id: repository
run: echo "name=${GITHUB_REPOSITORY#${GITHUB_REPOSITORY_OWNER}/}" >> $GITHUB_OUTPUT
- uses: actions/create-github-app-token@v1
id: create_token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ steps.repository.outputs.name }}
- name: Set the latest deployed commit id
run: gh variable set API_LATEST_DEPLOYED_COMMIT_ID -b ${{ github.sha }} -R ${{ github.repository }}
env:
GH_TOKEN: ${{ steps.create_token.outputs.token }}
Discussion