GitHub Actionsで第2世代のCloud Functionsをデプロイしてみた

食欲の秋 芸術の秋 自動デプロイの秋
2023.10.17

毎年この時期になるとブログ上で 栗きんとん (おせちのではない)の話ばかりしていることに気づきましたが今年も栗きんとんの話がしたいです。
栗のポテンシャルを一番活かしたシンプルなのに手間のかかった岐阜(中津川市が有名)の名物お菓子だと私は考えています。本当においしい。
全国のデパートでも買えるところは増えているはずなので、見かけた際は是非買ってみてください。


▲ 以前も栗きんとんの画像を使ったことがあったようなので別の栗アイコンを探しました

こんにちは。データアナリティクス事業本部 インテグレーション部 機械学習チームのShirotaです。
おいしいものをゆっくり楽しむためにも、できる限り仕事は楽にこなしたいものです。「楽」の定義は人によって違うと思いますが、私は「再現性があり」「できるだけ人の手が入らずヒューマンエラーのリスクが少ない」といった作業が実現できれば楽に仕事ができると考えています。
そこで、今回はCloud Functions(第2世代)をGitHub Actionsでデプロイするワークフローを作ってみることにしました。

Cloud Functions(第1世代)は公式がワークフローを用意してくれている

まず、今回ワークフローを作った経緯を簡単に説明させてもらいます。
Cloud Functions(第1世代)をデプロイするアクションは公式であるGoogleが提供してくれているものがあります。

しかし第2世代のCloud Functionsのデプロイには対応しておらず、要望として以下issueがオープンしたりはしていますが代わりにsetup-gcloudのアクションを使いgcloudコマンドを実行して欲しいとのコメントがされています。(2023年10月16日現在)

We do not currently support Cloud Functions gen2. If you need support for gen2, you can use the setup-gcloud action and run a gcloud command manually.

Cloud Functions(第1世代)デプロイのアクションでCloud Functions(第2世代)はデプロイできないの?

世代、とだけいうと性能が良くなったサービスであり根本的にやれることは変わらないのでちょっとゴリ押したらデプロイできたりしないのか?と思われる方もいるかもしれないのですが、Cloud Functionsは第1世代と第2世代でアーキテクチャそのものが変わっているという経緯があります。

Cloud Functions には、Cloud Functions(第 1 世代)と Cloud Functions(第 2 世代)という 2 種類のプロダクトがあります。第 2 世代は、Cloud Run と Eventarc をベースにした新しいバージョンで、機能が強化されています。

第1世代と第2世代の相違点や第2世代での新機能については上記公式ドキュメントに詳しく記載されているので気になる方はこちらを参照してください。

今回Cloud Functions(第2世代)の利用を決定した理由

現状ではGitHub Actionsの公式アクションは対応していないCloud Functions(第2世代)を利用することに決めた理由も簡単に記載しておきます。
上述のドキュメント内で、Google Cloud公式が新規で作成する関数については第2世代を選択することを推奨していたのでCloud Functions(第2世代)の利用を決めました。
  

可能な限り、新しい関数には Cloud Functions(第2世代)を選択することをおすすめします。しかしながら、今後も Cloud Functions(第1世代)のサポートを継続する予定です。

もちろん、第1世代のサポートも上述のように続けてくれるそうです。
また、今後第1世代から第2世代への移行の機能も提供してくれるそうなので、今は第1世代のCloud Functionsを利用している方も第2世代のことについて今のうちから意識しておくといいのではないかと思います。

GitHub ActionsでCloud Functions(第2世代)をデプロイする

では、早速本題に入っていきましょう。
今回の作業を実行することでGoogle Cloud上にできるリソースは以下になります。

  • Cloud Functions(第2世代)
    • Cloud Functionsのランタイムサービスアカウント
  • GitHub Actionsのワークフローを実行するサービスアカウント
  • Workload Identity連携
    • Workload Identityプール
    • Workload Identityプロバイダ

実際にGitHub Actionsのアクションで作成されるリソースはCloud Functionsだけで、それ以外は前準備として作成しておく必要があります。(Cloud Functionsのランタイムサービスアカウントはコマンドのオプションで指定しない場合はデフォルトで作成されるので、必要に応じて事前に作成してください)

今回メインとなるところはGitHub Actionsのワークフローについてなので詳細は省きますが、なるべくセキュリティを上げつつ認証部分を楽にするためにWorkload Identity連携を使いました。

各種サービスアカウントに付与したロール

今回のワークフロー実行のために、サービスアカウントには以下ロールを付与しました。

  • Cloud Functionsのランタイムサービスアカウント
    • Secret Manager の Secret Accessor(roles/secretmanager.secretAccessor)
      • 今回作成した関数がSecret Managerにアクセスする必要があったので付与した
  • GitHub Actionsのワークフローを実行するサービスアカウント
    • Cloud Functions 管理者(roles/cloudfunctions.admin)
      • Cloud Functions(第2世代)をデプロイするのに必要
    • サービス アカウント ユーザー(roles/iam.serviceAccountUser)
      • GitHub Actionsのワークフローがこのサービスアカウントになりかわるために必要

GitHubのSecretsとVariableに値を登録する

機密情報をSecretsに、変更する可能性のある環境変数をVariableに登録しました。
今回は以下の値を登録しています。

  • Secrets
    • Workload IdentityのプロバイダーID
  • Variable
    • プロジェクトID
    • Cloud Functions名
    • Cloud Functionsで使う環境変数の値

ディレクトリ構成

cloudfunctions-gen2-deploy

├── .github
│   └── workflows
│       └── deploy-cloudfunctions.yml
├── .gitignore
└── 今回デプロイしたいCloud Functionsのディレクトリ
    ├── main.py
    └── requirements.txt

ようやく本題であるワークフローの中身に辿り着きました。
以下で実際に使っているYAMLと簡単な説明を記載します。

deploy-cloudfunctions.ymlの中身と解説

deploy-cloudfunctions.yml

name: Deploy Cloud Functions
on:
  # 手動ormainブランチにpush時に実行
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  deploy-job:
    runs-on: 'ubuntu-latest'
    permissions:
      contents: 'read'
      id-token: 'write'

    steps:
      # ソースコードのチェックアウトをする
    - uses: 'actions/checkout@v3'

    - id: 'auth'
      name: 'Authenticate to Google Cloud'
      # Workload Identity連携を利用してGitHub ActionsからGoogle Cloudへ認証を行う
      uses: 'google-github-actions/auth@v1'
      with:
        workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER_ID }}
        service_account: gha-cloudfunctions-deploy@${{ vars.PROJECT_ID }}.iam.gserviceaccount.com

    - name: 'Set up Cloud SDK'
      # gcloudコマンドを実行するためにCloud SDKを準備する
      uses: 'google-github-actions/setup-gcloud@v1'

    - name: 'Deploy Cloud Functions gen2 Using gcloud command'
      # gcloudコマンドを利用してCloud Functionsをデプロイする
      run: >-
        gcloud functions deploy ${{ vars.CLOUD_FUNCTIONS_ID }}
        --gen2
        --runtime=python310
        --region=asia-northeast1
        --source=./今回デプロイしたいCloud Functionsのディレクトリ
        --entry-point=Cloud Functionsで利用するエントリーポイント
        --trigger-http
        --no-allow-unauthenticated
        --set-env-vars GCP_PROJECT=${{ vars.PROJECT_ID }}
        --run-service-account 事前に作っておいたCloud FunctionsのランタイムサービスアカウントID@${{ vars.PROJECT_ID }}.iam.gserviceaccount.com

このワークフローを実行すると、想定通りのCloud Functions(第2世代)がデプロイできました。


▲ デプロイしたかったファイルだけちゃんとデプロイできている

最終的に運用フェーズに入ればmainブランチに直接pushする機会は少なくなるため以下のように変更を加えより運用に適したトリガーに変更しようと思っているので、こちらも参考までにトリガー部分の変更内容も記載しておきます。

deploy-cloudfunctions.yml

on:
  # 手動orプルリククローズ時に実行
  workflow_dispatch:
  pull_request:
    types:
      - closed
    branches:
      - main

デプロイしたCloud Functionsを更新したい時にはどうするの?

実際に検証をしていて私が疑問に思ったので備忘録がてら記述しておくと、gcloudコマンドのgcloud functions deployは更新も兼ねているコマンドです。

gcloud functions deploy - create or update a Google Cloud Function

そのため、デプロイ以降Cloud Functionsを更新する際にもコマンドの分岐などを実装する必要がなくこのままワークフローを運用に乗せることができて便利だと感じました。

運用を楽にするためにも自動化・コード化を進めていこう

Google Cloudが利用を推奨しているCloud Functions(第2世代)も、gcloudコマンドのセッティングをしてしまえば簡単にGitHub Actionsからデプロイできました!
Workload Identity連携や各種セットアップに便利なアクションが事前に用意されているので、gcloudコマンドを使えることで様々なリソースの作成や更新も自動化していけるのではないでしょうか。
第1世代のCloud Functionsデプロイ用にアクションが用意されていることが便利だったので少しだけ不安だったのですが、第2世代のCloud Functionsもそれなりに手軽に実装できているのではないかと思います。

最終的にはリソースの管理や運用のコストはどうしても無くなるものではないため、できるだけ軽く安全に進められる方法を選択していきたいところです。
第2世代のCloud Functionsの自動デプロイや運用方法に悩んでいる人の助けになれば幸いです。