💨

無駄なデプロイを抑える

に公開

はじめに

開発環境へのデプロイを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_IDPRIVATE_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 }}
GitHubで編集を提案
ambr Tech Blog

Discussion