LoginSignup
12
0

GitHub Actions + Cloud Deploy で Cloud Run にデプロイする

Last updated at Posted at 2023-12-06

はじめに

この記事は Cocone Advent Calendar 2023 7日目の記事です。

GitHub Actions と Google Cloud Deploy を使用して Cloud Run へデプロイします。おおまかな流れは以下の図のようになります。

cloud_deploy_000.png

この図を説明すると、

  • リポジトリの main ブランチにプッシュ(かプルリクエストをマージ)されるとワークフローが実行され、 Cloud Deploy の環境とステージング用 Cloud Run サービスが作成(デプロイ)されます。(①②③)
  • そのあと、 Cloud Deploy のプロモートを行うと本番用の Cloud Run サービスへデプロイされます。(④⑤)

本番環境をデプロイするまでに手動で行うことは①のプッシュと④のプロモートだけになります。

GCP の設定

GitHub から GCP へアクセスさせるため準備を行います。 gcloud コマンドの初期設定と必要な API の有効化は済んでいるものとして割愛します。また、プロジェクト ID とプロジェクト番号の表記をそれぞれ ${GCP_PROJECT_ID}${GCP_PROJECT_NUM} としてあります。それらは実際の値に置き換えます。

IAM サービスアカウント

GitHub から接続するためのサービスアカウントを作成します。

gcloud iam service-accounts create my-sa-gha

もう1つ、 Cloud Run 実行時に指定するサービスアカウントを作成します。ただ今回 Cloud Run のコンテナでは特別なことはしないのでこのアカウントには何も権限を付与しません。

gcloud iam service-accounts create my-sa-run

Workload Identity 連携

Workload Identity 連携を設定します。これを行うことでサービスアカウントのシークレットキーを発行することなく GitHub から GCP へアクセスできるようになります。シークレットキーの漏洩による事故がなくなり、セキュリティが強化されます。詳しくは こちらこちら を参考にしてください。

Workload Identity プールを作成します。

gcloud iam workload-identity-pools create my-wi-pool --location="global"

GitHub Actions の OIDC プロバイダを作成します。

gcloud iam workload-identity-pools providers create-oidc my-oidc-gha \
  --location="global" \
  --workload-identity-pool="my-wi-pool" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository"

サービスアカウントと Workload Identity プールを接続します。この ${USERNAME}/${REPOSITORY} は実際の GitHub ユーザー名/リポジトリに置き換えます。

gcloud iam service-accounts add-iam-policy-binding my-sa-gha@${GCP_PROJECT_ID}.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/${GCP_PROJECT_NUM}/locations/global/workloadIdentityPools/my-wi-pool/attribute.repository/${USERNAME}/${REPOSITORY}"

IAM カスタムロール

Cloud Deploy の操作に必要な権限を持つカスタムロールを作成します。権限を記述したファイルを作成し、それを指定してカスタムロールを作成します。 Cloud Run サービスの作成は別のサービスアカウントになり代わって行われるため、ここではそれらの権限は付与していません。

permissions.yaml
title: myCloudDeployRole
stage: GA
includedPermissions:
- clouddeploy.deliveryPipelines.create
- clouddeploy.deliveryPipelines.get
- clouddeploy.deliveryPipelines.update
- clouddeploy.operations.get
- clouddeploy.releases.create
- clouddeploy.releases.get
- clouddeploy.rollouts.create
- clouddeploy.rollouts.get
- clouddeploy.rollouts.list
- clouddeploy.targets.update
- iam.serviceAccounts.actAs
- storage.buckets.create
- storage.buckets.get
- storage.buckets.list
- storage.objects.create

ロールを作成します。

gcloud iam roles create myCloudDeployRole --project="${GCP_PROJECT_ID}" --file="permissions.yaml"

サービスアカウントにロールを割り当てます。

gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID} \
  --member="serviceAccount:my-sa-gha@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="projects/${GCP_PROJECT_ID}/roles/myCloudDeployRole"

Artifact Registry

コンテナイメージのプッシュ先となる Artifact Registry のリポジトリを作成します。

gcloud artifacts repositories create my-app --repository-format="docker" --location="asia-northeast1"

この Artifact Registry リポジトリを操作する権限をサービスアカウントに付与します。

gcloud artifacts repositories add-iam-policy-binding my-app \
  --location="asia-northeast1" \
  --member="serviceAccount:my-sa-gha@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/artifactregistry.writer"

GitHub の設定

リポジトリの変数とシークレットを設定します。ここの ${GCP_PROJECT_ID}${GCP_PROJECT_NUM} も実際の値に置き換えます。

種類 名前
変数 APP my-app
変数 GAR_LOCATION asia-northeast1
シークレット GCP_PROJECT_ID ${GCP_PROJECT_ID}
シークレット RUN_SERVICE_ACCOUNT my-sa-run@${GCP_PROJECT_ID}.iam.gserviceaccount.com
シークレット WIF_PROVIDER projects/${GCP_PROJECT_NUM}/locations/global/workloadIdentityPools/my-wi-pool/providers/my-oidc-gha
シークレット WIF_SERVICE_ACCOUNT my-sa-gha@${GCP_PROJECT_ID}.iam.gserviceaccount.com

Git で管理するファイルを用意します。
各種ファイルは このリポジトリのサンプル をベースに作成しています。

  • main.go
  • Dockerfile
  • cloud-deploy.template.yaml
  • skaffold.template.yaml
  • app-stg.template.yaml
  • app-prod.template.yaml
  • .github/workflows/cloud-deploy-to-cloud-run.yml

それぞれのファイルを説明していきます。

main.go

Hello world を返すだけのシンプルなHTTPサーバーです。確認のために環境変数も表示しています。 TARGET は Stg か Prod 、 SHA には Git のコミットハッシュが入ります。

main.go
package main

import (
	"fmt"
	"net/http"
	"os"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, world %s %s\n", os.Getenv("TARGET"), os.Getenv("SHA"))
}

func main() {
	http.HandleFunc("/", hello)
	http.ListenAndServe(":8080", nil)
}

Dockerfile

こちらもビルドと実行だけのシンプルなものです。

Dockerfile
FROM golang:1.21.4-alpine AS builder

COPY main.go ./
RUN CGO_ENABLED=0 go build -trimpath -ldflags='-w -s' -o /hello main.go

FROM scratch
COPY --from=builder /hello /

USER 65534:65534
EXPOSE 8080
CMD ["/hello"]

cloud-deploy.template.yaml

Cloud Deploy デリバリーパイプラインとターゲットを作成するための構成ファイルのテンプレートです。他に設定できる項目については こちらのページ を参照してください。

また、構成ファイルやテンプレート内にある ${APP} のようなシェル変数の記述は置き換えずそのままにします。これらはワークフロー実行時に置き換わります。テンプレートはステップ途中で envsubst コマンドで展開され cloud-deploy.yaml のような名前で構成ファイルが生成されます。

cloud-deploy.template.yaml
apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
  name: '${APP}'
description: 'Deployment pipeline for ${APP}'
serialPipeline:
  stages:
    - targetId: 'stg'
      profiles: ['stg']
    - targetId: 'prod'
      profiles: ['prod']
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
  name: 'stg'
description: 'Staging target'
run:
  location: 'projects/${PROJECT_ID}/locations/${REGION}'
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
  name: 'prod'
description: 'Production target'
run:
  location: 'projects/${PROJECT_ID}/locations/${REGION}'

skaffold.template.yaml

Cloud Deploy リリースを作成するための Skaffold 構成ファイルのテンプレートです。デプロイ先を Cloud Run にしてステージングと本番用のCloud Run サービス構成ファイルを指定しています。設定できる項目は こちらのページ にあります。

skaffold.template.yaml
apiVersion: skaffold/v4beta7
kind: Config
metadata:
  name: '${APP}'
deploy:
  cloudrun: {}
profiles:
  - name: 'stg'
    manifests:
      rawYaml:
        - 'app-stg.yaml'
  - name: 'prod'
    manifests:
      rawYaml:
        - 'app-prod.yaml'

app-stg.template.yaml と app-prod.template.yaml

ステージング環境と本番環境の Cloud Run サービス構成ファイルのテンプレートです。 image: 'app' は Cloud Deploy リリースを作成するステップ実行時に置き換えられます。 設定できる項目は こちらのページ が参考にしてください。

app-stg.template.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: '${APP}-stg'
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/maxScale: '1'
        run.googleapis.com/execution-environment: gen2
    spec:
      serviceAccountName: ${RUN_SERVICE_ACCOUNT}
      containers:
      - name: '${APP}'
        image: 'app'
        env:
          - name: 'TARGET'
            value: 'Stg'
          - name: 'SHA'
            value: '${SHA}'
app-prod.template.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: '${APP}-prod'
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/maxScale: '1'
        run.googleapis.com/execution-environment: gen2
    spec:
      serviceAccountName: ${RUN_SERVICE_ACCOUNT}
      containers:
      - name: '${APP}'
        image: 'app'
        env:
          - name: 'TARGET'
            value: 'Prod'
          - name: 'SHA'
            value: '${SHA}'

.github/workflows/cloud-deploy-to-cloud-run.yml

GitHub Actions ワークフローの構成ファイルです。

.github/workflows/cloud-deploy-to-cloud-run.yml
name: Cloud Deploy to Cloud Run

on: 
  push:
    branches:
      - main

jobs:
  deploy:
    permissions:
      contents: 'read'
      id-token: 'write'

    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout'
        uses: 'actions/checkout@v4'

      - name: 'Google auth'
        id: 'auth'
        uses: 'google-github-actions/auth@v1'
        with:
          workload_identity_provider: '${{ secrets.WIF_PROVIDER }}'
          service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}'

      - name: 'Set up Cloud SDK'
        uses: 'google-github-actions/setup-gcloud@v1'
        with:
          project_id: '${{ secrets.GCP_PROJECT_ID }}'

      - name: 'Docker auth'
        run: |-
          gcloud auth configure-docker ${{ vars.GAR_LOCATION }}-docker.pkg.dev

      - name: 'Build and push container'
        run: |-
          docker build -t "${{ vars.GAR_LOCATION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ vars.APP }}/${{ vars.APP }}:${{ github.sha }}" .
          docker push "${{ vars.GAR_LOCATION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ vars.APP }}/${{ vars.APP }}:${{ github.sha }}"

      - name: 'Render templatised config manifests'
        run: |-
          export PROJECT_ID="${{ secrets.GCP_PROJECT_ID }}"
          export REGION="${{ vars.GAR_LOCATION }}"
          export APP="${{ vars.APP }}"
          export SHA="${GITHUB_SHA::7}"
          export RUN_SERVICE_ACCOUNT="${{ secrets.RUN_SERVICE_ACCOUNT }}"
          for template in $(ls *.template.yaml); do envsubst < ${template} > ${template%%.*}.yaml ; done

      - name: 'Create Cloud Deploy delivery pipeline'
        run: |-
          gcloud deploy apply --file cloud-deploy.yaml --region ${{ vars.GAR_LOCATION }}

      - name: 'Create release name'
        run: |-
          echo "RELEASE_NAME=${{ vars.APP }}-${GITHUB_SHA::7}-${GITHUB_RUN_NUMBER}" >> ${GITHUB_ENV}

      - name: 'Create Cloud Deploy release'
        id: 'release'
        uses: 'google-github-actions/create-cloud-deploy-release@v0'
        with:
          delivery_pipeline: '${{ vars.APP }}'
          name: '${{ env.RELEASE_NAME }}'
          region: '${{ vars.GAR_LOCATION }}'
          description: '${{ env.GITHUB_COMMIT_MSG }}'
          skaffold_file: 'skaffold.yaml'
          images: 'app=${{ vars.GAR_LOCATION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ vars.APP }}/${{ vars.APP }}:${{ github.sha }}'

      - name: 'Report Cloud Deploy release'
        run: |-
          echo "Created release ${{ steps.release.outputs.name }} "
          echo "Release link ${{ steps.release.outputs.link }} "

このワークフローで行われていることはおおまかに、

  • 認証
  • コンテナをビルドして Artifact Registry へプッシュ
  • テンプレートから構成ファイルを生成
  • Cloud Deploy デリバリーパイプライン作成
  • Cloud Deploy リリース作成

となっています。

これらの7つのファイルが main リポジトリへプッシュされるとワークフローの実行を通して、 Cloud Deploy デリバリーパイプラインとリリース、ステージング用の Cloud Run サービスがデプロイされます。

GCP のリソースを確認

まだ本番用の Cloud Run サービスはデプロイされていません。 Cloud Deploy デリバリーパイプラインのプロモートを行うことによってデプロイされます。

Cloud Deploy デリバリーパイプラインの Web UI 画面では以下の画像のようになっています。プロモートをクリックして進行します。

cloud_deploy_001.png

プロモートが完了すると以下のようになり本番用の Cloud Run サービスがデプロイされます。

cloud_deploy_002.png

作成された Cloud Run サービスへのアクセスは制限されています。必要に応じて開放します。例えばインターネットに公開する場合は以下のようにコマンドを実行します。

gcloud run services add-iam-policy-binding my-app-prod \
  --region="asia-northeast1" \
  --member="allUsers" \
  --role="roles/run.invoker"

実際にアクセスするとこのように表示されます。

cloud_run_001.png

おわりに

GitHub Actions + Cloud Deploy を使用して Cloud Run へデプロイを行いました。

これで main ブランチにプッシュされるたびにステージング環境へデプロイが行われ、本番環境へのデプロイは Web UI から数クリックで行えるようになりました。ロールバックしたい時も同じく Web UI から数クリックで行えます。

今回は試していませんが Cloud Deploy デリバリーパイプラインには他にも、複数のターゲットに並列にデプロイする、プロモート時に承認を設ける、カナリアデプロイ、自動化の構成などもあり、まだまだ色々なことができそうで興味深いです。

参考リンク

12
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
0