🌐
【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