🌐

【DevOps実践】RustでAPIサーバーを構築してみた④: CICD編

に公開

前置き

【DevOps実践】RustでAPIサーバーを構築してみた①: 全体紹介編
【DevOps実践】RustでAPIサーバーを構築してみた②: Docker構築編
【DevOps実践】RustでAPIサーバーを構築してみた③: クラウド構築編

なにやってるかが分からない方は読んでみてください。

構築

いよいよCICD構築していきます。
ディレクトリ構造はこんな感じ

.github
    └─ workflows
           └─ deploy.yaml (ファイル名は自由)
name: Deploy to Cloud Run

# GithubActions動作するトリガー
# 今回はmainブランチにマージ・プッシュしたときに動作させます。
on:
  push:
    branches:
      - main

# Google Artifact Registryにプッシュする
# DockerImageのURLを定義します
env:
  IMAGE_NAME: ${{ secrets.GCP_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.ARTIFACT_REPOSITORY }}/${{ secrets.IMAGE_NAME }}

jobs:
  build-deploy:
    runs-on: ubuntu-latest

    # Actix-webでSQLxを使ってます。
    # リリース用にビルドする際、DBに接続し、SQLが実行できるかの確認が入りますので
    # それ用のコンテナを一個用意します。
    services:
      db:
        image: postgres:17
        ports:
          - 5432:5432
        env:
          POSTGRES_DB: my_db
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd="pg_isready -U postgres"
          --health-interval=5s
          --health-timeout=3s
          --health-retries=3

    # GCPにアクセスする時、Githubの一部の情報を取得する必要があります。
    # それを取得できる権限をここで渡します。
    permissions:
      contents: read # コードのチェックアウト用
      id-token: write # Google Auth に必要

    steps:
        # ソースコード取得
        - name: Checkout source code
         uses: actions/checkout@v4

        # Rustインストール
        - name: Install Rust
         uses: dtolnay/rust-toolchain@stable
         with:
            toolchain: 1.88.0 # アプリケーション側と合わせます

        # 依存関係キャッシュ
        - name: Cache Rust dependencies
         uses: actions/cache@v4
         with:
            path: |
                ~/.cargo/registry
                ~/.cargo/git
                target/
            key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
            restore-keys: |
                ${{ runner.os }}-cargo-

        # SQLxインストール
        - name: Install SQLx CLI
         run: |
            if ! command -v sqlx &> /dev/null; then
                cargo install sqlx-cli --no-default-features --features postgres
            else
                echo "SQLx CLI already cached"
            fi

        # DockerImageをビルドするときも、SQLxのチェックが入るので、ネットワークの関係で
        # 上で定義したDBに接続しようとすると、いろいろと設定が面倒です。
        # オフライン機能を利用して、静的型ファイルを生成します。
        # マイグレーション実行・オフライン用ファイルを生成
        - name: Run migrations & prepare SQLx (offline data)
         env:
            DATABASE_URL: postgres://postgres:postgres@localhost:5432/my_db
         run: |
            sqlx migrate run
            cargo sqlx prepare --workspace -- --bin app

        # テスト実行
        - name: Run App Test
         run: |
            cargo test --all --locked

        # GoogleCloud認証情報をセット
        - name: Authenticate to Google Cloud
         uses: google-github-actions/auth@v2
         with:
             token_format: "access_token"
             # クラウド編で作成したPoolとSAを指定する
             workload_identity_provider: "projects/${{ secrets.GCP_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/${{ secrets.GCP_WORKLOAD_IDENTITY_POOL_ID }}/providers/${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER_ID }}"
             service_account: "${{ secrets.GCP_GITHUB_SA }}@${{ secrets.GCP_PROJECT_ID }}.iam.gserviceaccount.com"

        # GoogleCloud SDKインストール
        - name: Set up gcloud SDK
         uses: google-github-actions/setup-gcloud@v2

        # Artifact Registryにアクセス時はgcloudの認証情報を使用する
        - name: Authenticate Docker to Artifact Registory
         run: gcloud auth configure-docker ${{ secrets.GCP_REGION }}-docker.pkg.dev

        # アプリケーションのバージョンを取得
        - name: Get version from Cargo.toml
         id: cargo_version
         run: |
            VERSION=$(grep -m 1 '^version' Cargo.toml | sed 's/version = "\(.*\)"/\1/')
            echo "VERSION=$VERSION" >> $GITHUB_ENV

        # アプリケーションのバージョンと同じタグ&Latestバージョンのイメージを生成
        # ArtifactRegistryでlatestを含め、最大で三つのバージョンを保有します。
        # 万が一機能追加や修正ででかいバグを残したとしても、コンソール側で一個前のバージョンに切り替えれば大丈夫
        - name: Build and push image
         uses: docker/build-push-action@v5
         with:
            context: .
            file: docker/rust/Dockerfile
            push: true
            tags: |
                ${{ env.IMAGE_NAME }}:latest
                ${{ env.IMAGE_NAME }}:${{ env.VERSION }}
            # ほんとはキャッシュしたかったですが、キャッシュのサイズがあまりにも大き過ぎて
            # ArtifactRegistryの無料枠を超えてしまうので、あきらめました。

            # cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache
            # cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max

            # オフラインでビルドすることを忘れずに
            build-args: |
                SQLX_OFFLINE=true

        # ArtifactRegistryで保有してるイメージ数を確認し
        # 最新3件(latest含む)以外のイメージを削除
        # どんどんたまっていくと無料枠超えちゃうので・・・
        - name: Delete old Artifact Registry images (Keep last 3 including latest)
         run: |
            gcloud artifacts docker images list \
                ${{ env.IMAGE_NAME }} \
                --format="value(version)" \
                --sort-by=~UPDATE_TIME \
                | tail -n +4 \
                | while IFS= read -r version; do
                    if [ -n "$version" ]; then
                    echo "Deleting version: $version"
                    gcloud artifacts docker images delete \
                        "${{ env.IMAGE_NAME }}:${version}" \
                        --delete-tags --quiet || echo "Failed to delete $version (may already be removed)"
                    fi
                done

        # CloudRunにデプロイ
        # service-accountでCloudRunが使用するサービスアカウントを指定するの忘れずに
        - name: Deploy to Cloud Run
         run: |
            gcloud run deploy ${{ secrets.CLOUD_RUN_SERVICE }} \
                --image ${{ env.IMAGE_NAME }}:latest \
                --region ${{ secrets.GCP_REGION }} \
                --platform managed \
                --allow-unauthenticated \
                --service-account ${{ secrets.GLOUD_RUN_SA }}@${{ secrets.GCP_PROJECT_ID }}.iam.gserviceaccount.com

        # 古いリビジョンを削除(最新のみ保持)
        - name: Delete old Cloud Run revisions (Keep latest only)
         run: |
            gcloud run revisions list \
                --platform managed \
                --service=${{ secrets.CLOUD_RUN_SERVICE }} \
                --region=${{ secrets.GCP_REGION }} \
                --format="value(name)" \
                --sort-by=~metadata.creationTimestamp \
                | tail -n +2 \
                | while IFS= read -r revision; do
                    if [ -n "$revision" ]; then
                    echo "Deleting revision: $revision"
                    gcloud run revisions delete "$revision" \
                        --region=${{ secrets.GCP_REGION }} \
                        --quiet || echo "Failed to delete $revision"
                    fi
                done

お疲れ様でした。
これでCICDの8~9割くらいは完成したんじゃないでしょうか
ブラシュアップとして、デプロイ完了したらChatに通知送るなど、ご自由に設計してください。

最後

APIサーバ―構築から、クラウドの構築まで、一通りできるようになりました。
デプロイ作業はmainブランチにマージしたとき、自動で行われます。
CloudRunの性質上、アプリケーション止めることなく、差分を更新してくれるので、
エンドユーザーには変更されたとすら感じさせない仕組みは出来たかなと感じてます。

これで完璧だー!なんてことはないので、一通りの流れをつかみたい方の参考になればうれしいです。

最後まで見ていただき、ありがとうございました。
ではまた!

Discussion