📊

Streamlit with Google Cloud: GitHub、GitLab、Cloud Build での CI/CD

2023/02/27に公開
2

今回は Streamlit アプリケーションを CI/CD パイプラインからデプロイする方法をみていきます。

この連載では、Google Cloud 上で Streamlit を上手に動かす方法をご紹介しています。


Cloud Build での CI/CD

Cloud Build にはビルドトリガーという機能があり、これを設定すると Google Cloud の Cloud Source Repositories (CSR) のみならず、GitHub や GitLab、それらの Enterprise 版に対してのイベントを契機に CI/CD パイプラインが起動できます。

1. Cloud Build に権限を付与

まずは Cloud Build が内部的に利用するサービス アカウントに権限を付与します。

export project_id=$( gcloud config get-value project )
export project_number=$(gcloud projects describe ${project_id} \
    --format="value(projectNumber)")
gcloud projects add-iam-policy-binding "${project_id}" \
    --member "serviceAccount:${project_number}@cloudbuild.gserviceaccount.com" \
    --role "roles/run.developer"
gcloud iam service-accounts add-iam-policy-binding \
    sa-app@${project_id}.iam.gserviceaccount.com \
    --member "serviceAccount:${project_number}@cloudbuild.gserviceaccount.com" \
    --role "roles/iam.serviceAccountUser"

2. ビルド構成ファイルの作成

どんなパイプラインとするかを YAML で定義できます。docker build + Artifact Registry に docker push した後に Cloud Run へデプロイ、並行して静的解析もしておく例は以下です。cloudbuild.yaml として保存してみましょう。

cloudbuild.yaml
steps:
  - id: 'test'
    name: python:3.11
    entrypoint: bash
    args: ["-c", "python -m pip install poetry && poetry install --no-interaction --no-ansi --no-root && ./test.sh"]

  - id: 'push'
    name: 'gcr.io/kaniko-project/executor:latest'
    args: ['--destination=asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-apps/streamlit:$SHORT_SHA',
      '--cache=true', '--cache-ttl=6h']
    waitFor: ['-']

  - id: 'deploy'
    name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args: ['run', 'deploy', 'my-app', '--region=asia-northeast1',
      '--image=asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-apps/streamlit:$SHORT_SHA',
      '--service-account=sa-app@$PROJECT_ID.iam.gserviceaccount.com']

3. お試しビルド

正しく動作するか、まずは手元からパイプラインを起動してみます。実際のパイプラインでは $SHORT_SHA として補完されるはずの git hash ですが、ローカルから実行する際はコマンドライン引数として何かを渡してあげる必要があります。

gcloud builds submit . --config cloudbuild.yaml --substitutions SHORT_SHA=test

テストフェーズでの依存解決がキャッシュされない影響で、パイプラインの完了まで 7 分強かかります。高速化を図るためには、Poetry を事前にビルドしたイメージを使ってもよさそうです。

4. トリガーの設定

GitHub を例にすると

  1. ソース リポジトリに接続
  2. トリガーを作成すれば OK です
  3. リポジトリが非公開なら SSH 認証鍵を使ったアクセスもあります

トリガーの ビルド構成ファイル には 2. で作った cloudbuild.yaml を設定しましょう。

GitHub Actions または GitLab CI/CD での CI/CD

いずれのケースにも共通した準備ステップがあります。

1. サービス アカウントの作成

sa-cicd という名前で、CI と連携するサービス アカウントを作ります。

gcloud iam service-accounts create sa-cicd \
    --display-name "SA for CI/CD"
gcloud projects add-iam-policy-binding "${project_id}" \
    --member "serviceAccount:sa-cicd@${project_id}.iam.gserviceaccount.com" \
    --role "roles/viewer"
gcloud projects add-iam-policy-binding "${project_id}" \
    --member "serviceAccount:sa-cicd@${project_id}.iam.gserviceaccount.com" \
    --role "roles/run.admin"
gcloud projects add-iam-policy-binding "${project_id}" \
    --member "serviceAccount:sa-cicd@${project_id}.iam.gserviceaccount.com" \
    --role "roles/storage.admin"
gcloud projects add-iam-policy-binding "${project_id}" \
    --member "serviceAccount:sa-cicd@${project_id}.iam.gserviceaccount.com" \
    --role "roles/artifactregistry.writer"
gcloud projects add-iam-policy-binding "${project_id}" \
    --member "serviceAccount:sa-cicd@${project_id}.iam.gserviceaccount.com" \
    --role "roles/cloudbuild.builds.editor"
gcloud iam service-accounts add-iam-policy-binding \
    sa-app@${project_id}.iam.gserviceaccount.com \
    --member "serviceAccount:sa-cicd@${project_id}.iam.gserviceaccount.com" \
    --role "roles/iam.serviceAccountUser"

2. Workload Identity の準備

一時的な認証トークンの発行を可能にする IAM Service Account Credentials API を有効化します。

gcloud services enable iamcredentials.googleapis.com

ファイル形式でのクレデンシャルなしに CI を実行できるよう、Workload Identity に ID プールを作ります。

gcloud iam workload-identity-pools create "idpool-cicd" --location "global" \
    --display-name "Identity pool for CI/CD services"
idp_id=$( gcloud iam workload-identity-pools describe "idpool-cicd" \
    --location "global" --format "value(name)" )

3. GitHub Actions の場合

Workload Identity 連携を通してサービス アカウントの利用を許可する単位は設定でよしなに変えられるのですが、ここでは 特定の GitHub プロジェクトにのみ許可する 設定例を取り上げます。

repo=<org-id>/<repo-id>
gcloud iam workload-identity-pools providers create-oidc "idp-github" \
    --workload-identity-pool "idpool-cicd" --location "global" \
    --issuer-uri "https://token.actions.githubusercontent.com" \
    --attribute-mapping "google.subject=assertion.sub,attribute.repository=assertion.repository" \
    --display-name "Workload IdP for GitHub"
gcloud iam service-accounts add-iam-policy-binding \
    sa-cicd@${project_id}.iam.gserviceaccount.com \
    --member "principalSet://iam.googleapis.com/${idp_id}/attribute.repository/${repo}" \
    --role "roles/iam.workloadIdentityUser"
gcloud iam workload-identity-pools providers describe "idp-github" \
    --workload-identity-pool "idpool-cicd" --location "global" \
    --format "value(name)"

GitHub プロジェクトの Actions secrets and variables に以下の値を設定します。

  • GOOGLE_CLOUD_PROJECT: プロジェクト ID
  • GOOGLE_CLOUD_WORKLOAD_IDP: Workload Identity の IdP ID

.github/workflows/release.yaml として以下の内容を保存し、git push をしてみましょう。

.github/workflows/release.yaml
name: Release to Cloud Run

on:
  push:
    branches:
    - main

env:
  GOOGLE_CLOUD_REGION: "asia-northeast1"

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest

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

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.11'

    - name: Test
      run: |
        pip install poetry
        poetry install --no-interaction --no-ansi --no-root
        ./test.sh

    - name: Auth
      uses: google-github-actions/auth@v1
      with:
        workload_identity_provider: ${{ secrets.GOOGLE_CLOUD_WORKLOAD_IDP }}
        service_account: "sa-cicd@${{ secrets.GOOGLE_CLOUD_PROJECT }}.iam.gserviceaccount.com"

    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@v1

    - name: Configure docker
      run: gcloud auth configure-docker ${{ env.GOOGLE_CLOUD_REGION }}-docker.pkg.dev --quiet

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2

    - name: Build the image
      uses: docker/build-push-action@v3
      with:
        tags: ${{ env.GOOGLE_CLOUD_REGION }}-docker.pkg.dev/${{ secrets.GOOGLE_CLOUD_PROJECT }}/my-apps/streamlit:${{ github.sha }}
        push: true

    - name: Release
      uses: google-github-actions/deploy-cloudrun@v1
      with:
        service: my-app
        region: ${{ env.GOOGLE_CLOUD_REGION }}
        image: ${{ env.GOOGLE_CLOUD_REGION }}-docker.pkg.dev/${{ secrets.GOOGLE_CLOUD_PROJECT }}/my-apps/streamlit:${{ github.sha }}
        flags: --service-account=sa-app@${{ secrets.GOOGLE_CLOUD_PROJECT }}.iam.gserviceaccount.com

4. GitLab CI/CD の場合

Workload Identity 連携を通してサービス アカウントの利用を許可する単位は設定でよしなに変えられるのですが、ここでは 特定の GitLab プロジェクトにのみ許可する 設定例を取り上げます。

repo=<org-id>/<repo-id>
gcloud iam workload-identity-pools providers create-oidc "idp-gitlab" \
    --workload-identity-pool "idpool-cicd" --location "global" \
    --issuer-uri "https://gitlab.com/" \
    --allowed-audiences "https://gitlab.com" \
    --attribute-mapping "google.subject=assertion.sub,attribute.project_path=assertion.project_path" \
    --display-name "Workload IdP for GitLab"
gcloud iam service-accounts add-iam-policy-binding \
    sa-cicd@${project_id}.iam.gserviceaccount.com \
    --member "principalSet://iam.googleapis.com/${idp_id}/attribute.project_path/${repo}" \
    --role "roles/iam.workloadIdentityUser"
gcloud iam workload-identity-pools providers describe "idp-gitlab" \
    --workload-identity-pool "idpool-cicd" --location "global" \
    --format "value(name)"

GitLab プロジェクトの CI/CD > Variables に以下の値を設定します。

  • GOOGLE_CLOUD_PROJECT: プロジェクト ID
  • GOOGLE_CLOUD_WORKLOAD_IDP: Workload Identity の IdP ID

.gitlab-ci.yml を以下の通り用意したら git push を試しましょう!

.gitlab-ci.yml
image:
  name: google/cloud-sdk:417.0.0-alpine

variables:
  GOOGLE_CLOUD_REGION: asia-northeast1

stages:
  - test
  - deploy

test:
  stage: test
  image: python:3.11
  script:
    - pip install poetry
    - poetry install --no-interaction --no-ansi --no-root
    - ./test.sh
  except:
    - tags

.gcloud_auth: &gcloud_auth
  before_script:
    - echo ${CI_JOB_JWT_V2} > .ci_jwt
    - gcloud iam workload-identity-pools create-cred-config ${GOOGLE_CLOUD_WORKLOAD_IDP} --service-account "sa-cicd@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" --credential-source-file .ci_jwt --output-file .cred.json
    - gcloud auth login --cred-file "$(pwd)/.cred.json"
    - gcloud config set project ${GOOGLE_CLOUD_PROJECT}

deploy-to-dev:
  stage: deploy
  <<: *gcloud_auth
  script:
    - gcloud builds submit --tag ${GOOGLE_CLOUD_REGION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/my-apps/streamlit:${CI_COMMIT_SHORT_SHA} .
    - gcloud run deploy my-app --image ${GOOGLE_CLOUD_REGION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/my-apps/streamlit:${CI_COMMIT_SHORT_SHA} --region "${GOOGLE_CLOUD_REGION}" --service-account "sa-app@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com"
  only:
    - main

さいごに

最終的なファイル群はこちらにおいてあります。
https://github.com/pottava/streamlit-on-googlecloud

Streamlit アプリケーションを Google Cloud 上で動かすイメージは湧きましたでしょうか?今回は触れていませんが、いずれ他のデータソースを扱ったり、より高度なアクセス制御を設定したりしてみたいと思います。長文にお付き合いいただき感謝です。みなさんに、少しでも参考にしていただけるトピックがあったことを祈りつつ。ではまた〜

Google Cloud Japan

Discussion