😇

Firebase CLIとGitHub Actionsで手動デプロイ環境を用意する

2023/08/28に公開

概要

  • Firebase CLIを使ってデプロイコマンドを叩いていたがローカルからだった
  • 簡単にできてこれまで何不自由なかったんですが、ローカル起点なので困ったことが2点
    • フレームワークのバージョンアップ検証途中で別途修正依頼が入った場合、 node_modules 以下がアップデートされるので、一度ぶっ壊してbranchのcheckoutするのが手間
    • 出張中、回線が弱いところでデプロイすると不安定なので欠損したり失敗したりするので困る
  • というわけで、GitHub Actionsでできないかと模索したら記事が色々でてきたので試してみてできた記事

前提

  • Nuxt3を利用しています
  • Firebase Hostingのみのデプロイです
    • Cloud Functionsへもできたんですが、準備することが多いので一旦別記事で
  • GitHubのプランはEnterprise
  • 連携したリポジトリはもちろんprivate
  • GitHub ActionsからFirebaseへデプロイする際に行う認証はOIDC tokenを利用しました
  • 途中 gcloud コマンドを利用するので参考にするならインストール必要です

手順

GitHub Environmentsの用意

こちらを参考にしてもらえれば(いつもありがとうございます!!)

https://dev.classmethod.jp/articles/github-actions-environment-secrets-and-environment-variables/

ただ、僕が触ったタイミングだと、なんか他の記事含め設定できる内容やUIが変わっているようでした※2023/08/21時点

Settings -> Environments -> New environment -> 名前入力

  • Deployment branches: 環境変数を利用するbranchを設定できます
  • Environment secrets: 秘匿したい値を設定できます。設定すると、設定者でも参照できません。Authentication codeを入力して上書きするしかできません。GitHub Actionsのみ参照可能になります。 ${{ secrets.XXX }}
  • Environment variables: 機密性のない変数を設定します。 .env の認識です。 GitHub Actions上から ${{ vars.XXX }} でアクセスできます

FirebaseにはAPI Keyとか設定値が7個ぐらいあって、GitHub Actionsで利用できるように Environment variables を利用しました

サービスアカウントの用意

Firebaseへデプロイするためのサービスアカウントを用意します

発行したメールアドレスはメモしておく(あとで使う)

今回は aipa@service-acount.com とかにしておきます

権限を設定する

今回は2つ

  • Firebase 管理者 ※本当は利用するところだけ設定するのが良い
  • サービス アカウント ユーザー

GitHub ActionsとGCPの連携

その昔(?)は、CI用にTokenを発行する方法があったらしいんですが、今は非推奨になっているとのことです(見たことも試したこともない)

GCPを利用する人なら説明もいらないと思いますが、よくあるService Accountのjsonを落としてきて、そいつを使って認証することが多いと思いますが、今回はOIDCというやつが目に入ったため、こいつを試してみることにしました

OIDCとは

OpenID Connectのことです。正直わからん(勉強中)

OAuthの拡張とふわっと理解していますが、よくないので学べたら別途記事でまとめます

手順

gcloud コマンドを利用して設定していきます。インストールやログインの手順は省略しています

そしてこちらの記事を参考(ほぼコピペだけど)にしています
https://zenn.dev/vvakame/articles/gha-and-gcp-workload-identity

$ export PROJECT_ID=dev-aipa  # 対象のGCPのProject ID
$ export POOL_NAME=github-actions  # poolの名前。何でも良い
$ export PROVIDER_NAME=aipa-provider  # providerの名前。何でも良い
$ export SA_EMAIL=aipa@service-acount.com  # 作成したIAMユーザー
$ export GITHUB_REPO=aipacommander/firebase-deploy  # GitHubのorgnization/repoの形式で渡す

# IAM Service Account Credentials API を有効にする
$ gcloud services enable iamcredentials.googleapis.com --project "${PROJECT_ID}"
$ gcloud iam workload-identity-pools create "${POOL_NAME}" --project="${PROJECT_ID}" --location="global" --display-name="GitHub Actions Pool"
$ export WORKLOAD_IDENTITY_POOL_ID=$(\
    gcloud iam workload-identity-pools describe "${POOL_NAME}" \
        --project="${PROJECT_ID}" --location="global" \
        --format="value(name)" \
)

$ gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_NAME}" --project="${PROJECT_ID}" --location="global" --workload-identity-pool="${POOL_NAME}" --display-name="use from GitHub Actions provider" --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.actor=assertion.actor,attribute.aud=assertion.aud" --issuer-uri="https://token.actions.githubusercontent.com"
$ gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" --project="${PROJECT_ID}" --role="roles/iam.workloadIdentityUser" --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${GITHUB_REPO}"

GitHub Actionsのワークフローの用意

とりあえずこんな感じのやつを用意すればおkです

name: Firebase Deploy

on:
  # 手動で実行できる設定
  workflow_dispatch:
    inputs:
      environment:
        default: development
        options:
          - development
          - production
        required: true
        type: choice

jobs:
  setup:
    runs-on: ubuntu-latest
    # トリガーするイベントに応じてinputをoutputに変換
    steps:
      - id: setup-from-inputs
        if: github.event_name != 'repository_dispatch'
        run: |
          echo environment=${{ inputs.environment }} >> $GITHUB_OUTPUT
          echo branch=${{ github.ref_name }} >> $GITHUB_OUTPUT
      - id: setup-from-payload
        if: github.event_name == 'repository_dispatch'
        run: |
          echo environment=${{ github.event.client_payload.environment }} >> $GITHUB_OUTPUT
          echo branch=${{ github.event.client_payload.branch }} >> $GITHUB_OUTPUT

    # ジョブのoutputとしてenvironmentとbranchを設定
    outputs:
      environment: ${{ steps.setup-from-inputs.outputs.environment || steps.setup-from-payload.outputs.environment }}
      branch: ${{ steps.setup-from-inputs.outputs.branch || steps.setup-from-payload.outputs.branch }}

  development:
    needs: setup
    if: needs.setup.outputs.environment == 'development'
    runs-on: ubuntu-latest # 実行環境を指定
    environment: aipa-env
    permissions:
      contents: read
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3 # リポジトリをチェックアウトするアクションを使用

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v1
        with:
          workload_identity_provider: projects/12341234/locations/global/workloadIdentityPools/aipa-pool/providers/aipa-provider  # 作成したProviderパス?を指定
          service_account: aipa@service-acount.com  # 作成したサービスアカウントを指定
          access_token_lifetime: 600s
          create_credentials_file: true
  
      - name: Install Node.js
        uses: actions/setup-node@v3 # Node.js 環境をセットアップ

      - name: Install Firebase CLI
        run: yarn global add firebase-tools # Firebase CLI をインストール
    
      - name: Install dependencies
        run: yarn install # 依存関係をインストール

      - name: Build Nuxt
        run: NITRO_PRESET=firebase yarn build
        env:
          NUXT_FIREBASE_API_KEY: ${{ vars.NUXT_FIREBASE_API_KEY }}

      - name: Deploy to Firebase
        run: firebase --project dev --config firebase.json deploy --only functions:server,hosting --force # Firebase ホスティングにデプロイ
  
  # TODO: productionの設定も別途必要
  # production:
  #   needs: setup
  #  if: needs.setup.outputs.environment == 'production'
  #  runs-on: ubuntu-latest # 実行環境を指定
  # ...

注意点として、workload_identity_providerの値が${WORKLOAD_IDENTITY_POOL_ID}//providers/${PROVIDER_NAME}となります(これにハマった)

手動でworkflowを実行するための設定

下記ふたつの記事を参考にしました

https://zenn.dev/manalink_dev/articles/manalink-github-actions-improve-deploy-workflow
https://blog.kymmt.com/entry/gha-deploy-workflow

# ...
on:
  # 手動で実行できる設定
  workflow_dispatch:
    inputs:
      environment:
        default: development
        options:
          - development
          - production
        required: true
        type: choice

jobs:
  setup:
    runs-on: ubuntu-latest
    # トリガーするイベントに応じてinputをoutputに変換
    steps:
      - id: setup-from-inputs
        if: github.event_name != 'repository_dispatch'
        run: |
          echo environment=${{ inputs.environment }} >> $GITHUB_OUTPUT
          echo branch=${{ github.ref_name }} >> $GITHUB_OUTPUT
      - id: setup-from-payload
        if: github.event_name == 'repository_dispatch'
        run: |
          echo environment=${{ github.event.client_payload.environment }} >> $GITHUB_OUTPUT
          echo branch=${{ github.event.client_payload.branch }} >> $GITHUB_OUTPUT

    # ジョブのoutputとしてenvironmentとbranchを設定
    outputs:
      environment: ${{ steps.setup-from-inputs.outputs.environment || steps.setup-from-payload.outputs.environment }}
      branch: ${{ steps.setup-from-inputs.outputs.branch || steps.setup-from-payload.outputs.branch }}

  development:
    needs: setup
    if: needs.setup.outputs.environment == 'development'
    runs-on: ubuntu-latest # 実行環境を指定
    # ...

正直よくわかっていない(GitHub Actions難しい)

環境変数の渡し方

↑にも書きましたが、 ${{ vars.XXX }} で渡します。ただ、ハマったんですが、GitHub Actionsで起動するインスタンス(?)にはEnvironmentsの設定はexportされていないため、- env: の階層を用意して、渡してやる必要があります。めんどい

      - name: Build Nuxt
        run: NITRO_PRESET=firebase yarn build
        env:
          NUXT_FIREBASE_API_KEY: ${{ vars.NUXT_FIREBASE_API_KEY }}
	  # ...

ただ、わかっていないだけかもしれないので、間違いとか便利な方法あるよ〜とかあれば教えてほしい

default branchの切り替え

main branchで作業していない場合(というのがほとんどだと思うけど)、Actionsでの手動実行ができないです。そのため、一時的にdefault branchをworkflowのyamlがcommitされているbranchに切り替えて動作確認する必要があります

GitHubでリポジトリのsettingsにて、下記UIから変更ください

動作確認

リポジトリのActionsから選択できるようになります

Actionsの左側からデプロイのワークフローを選択して、右側のRun workflowにて、デプロイしたいbranchを選択して走らせればおk

テストのTips

npm install とか途中含まれていると毎回走らせるのは時間がかかるので、ローカルでテストか、動作確認を簡略化したいなってなると思います

ローカルで試したい場合は、こういうコマンド(ツール?)がありました

https://github.com/nektos/act

ただ、僕の環境だとうまく動かなかった(理由もわからん)ので、GitHub Actions + ワークフローの簡易化でテストしました。

ビルドコマンドはローカルでうまくことはわかっていたので、 google-github-actions/auth 後に、うまくGCP環境へ命令が通るかをテストしたい場合、下記のようにしました

# ...
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3 # リポジトリをチェックアウトするアクションを使用

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v1
        with:
          workload_identity_provider: projects/12341234/locations/global/workloadIdentityPools/aipa-pool/providers/aipa-provider
          service_account: aipa@service-acount.com
          access_token_lifetime: 600s
          create_credentials_file: true
  
      - name: Install Node.js
        uses: actions/setup-node@v3 # Node.js 環境をセットアップ

      - name: Install Firebase CLI
        run: yarn global add firebase-tools # Firebase CLI をインストール
    
      - name: Install dependencies
        run: firebase --project dev-aipa functions:list

それか、Firebaseじゃないなら、Cloud Storageを参照するなど簡単なコマンドで確認できるとあまり時間をかけず試すことができます

雑感

  • 無事簡易に検証デプロイできるようになりました
  • プロダクションへのデプロイ設定は今後
  • Cloud Functionsへのデプロイ設定も今後

参考記事

いっぱいお世話になりました

https://zenn.dev/vvakame/articles/gha-and-gcp-workload-identity

https://christina04.hatenablog.com/entry/workload-identity-federation

https://zenn.dev/ryo_kawamata/articles/c450a7eac006c5

https://github.com/google-github-actions/auth

https://zenn.dev/nbstsh/scraps/7a707bb5d60749

https://www.memory-lovers.blog/entry/2023/05/29/150000

https://www.memory-lovers.blog/entry/2023/05/29/110000

https://dev.classmethod.jp/articles/github-actions-environment-secrets-and-environment-variables/

https://zenn.dev/hashito/articles/aef4de448f341b

https://blog.shibayan.jp/entry/20201217/1608190413

https://zenn.dev/kenpi/articles/af6fb5b4c2675e
https://note.com/yiio/n/nfb4fc77c4927

CBcloud Tech Blog

Discussion