一般社団法人 全国個人事業主支援協会

COLUMN コラム

  • Google Cloud Runで実現するコンテナベースのマイクロサービス

Cloud Runとは:コンテナとサーバーレスの融合

Google Cloud Runは、コンテナイメージをデプロイするだけでスケーラブルなサービスを実行できるフルマネージドプラットフォームです。Knativeをベースとしており、コンテナの柔軟性とサーバーレスのスケーラビリティを兼ね備えています。

AWS Lambdaと比較されることが多いですが、Cloud Runの最大の差別化ポイントは任意のコンテナイメージを実行できます点です。言語やフレームワークの制約がなく、既存のDockerイメージをほぼそのままデプロイできます。筆者はPythonのFastAPIやGoのAPIサーバーなど、複数のマイクロサービスをCloud Runで運用しています。

最初のサービスをデプロイします

Cloud Runへのデプロイは驚くほどシンプルです。Dockerfileを用意し、数コマンドで完了します。

Dockerfileの作成

# Dockerfile - マルチステージビルドでイメージサイズを最小化
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server

FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]

Cloud RunではPORT環境変数で指定されたポートをリッスンする必要があります。デフォルトは8080ですが、アプリケーション内で環境変数から読み取るようにしておくのがベストプラクティスです。

アプリケーションコード

// cmd/server/main.go
package main

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

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)

func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)

r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "ok")
})

r.Route("/api/v1", func(r chi.Router) {
r.Get("/products", listProducts)
r.Post("/products", createProduct)
r.Get("/products/{id}", getProduct)
})

log.Printf("Server starting on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, r))
}

デプロイコマンド

# Artifact Registryにイメージをプッシュしてデプロイ
gcloud builds submit --tag asia-northeast1-docker.pkg.dev/my-project/my-repo/api-server:latest

gcloud run deploy api-server \
--image asia-northeast1-docker.pkg.dev/my-project/my-repo/api-server:latest \
--region asia-northeast1 \
--platform managed \
--allow-unauthenticated \
--memory 512Mi \
--cpu 1 \
--min-instances 0 \
--max-instances 10 \
--set-env-vars "DB_HOST=10.0.0.1,DB_NAME=mydb"

本番運用のための設定

開発環境でのデプロイは簡単ですが、本番運用ではいくつかの追加設定が必要になります。

最小インスタンス数とコールドスタート

Cloud Runはリクエストがない場合にインスタンスを0にスケールダウンします。これはコスト効率に優れますが、次のリクエストでコールドスタートが発生します。本番環境では最小インスタンス数を1以上に設定することを推奨します。

# Terraformでの設定例
resource "google_cloud_run_v2_service" "api" {
name = "api-server"
location = "asia-northeast1"

template {
scaling {
min_instance_count = 1
max_instance_count = 20
}

containers {
image = "asia-northeast1-docker.pkg.dev/my-project/my-repo/api-server:latest"

resources {
limits = {
cpu = "2"
memory = "1Gi"
}
cpu_idle = true # リクエスト間でCPUをスロットルする
}

startup_probe {
http_get {
path = "/health"
}
initial_delay_seconds = 0
period_seconds = 3
failure_threshold = 3
}

liveness_probe {
http_get {
path = "/health"
}
period_seconds = 30
}

env {
name = "DB_PASSWORD"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.db_password.secret_id
version = "latest"
}
}
}
}
}
}

cpu_idle = trueは重要な設定です。これを有効にすると、リクエストを処理していない間はCPUが割り当てられず、コストを大幅に削減できます。ただし、バックグラウンド処理が必要なサービスでは無効にする必要があります。

CI/CDパイプラインの構築

Cloud Buildを使った自動デプロイパイプラインを構築することで、安全で迅速なリリースサイクルを実現できます。

# cloudbuild.yaml
steps:
# テスト実行
- name: 'golang:1.22'
entrypoint: 'go'
args: ['test', './...']

# コンテナイメージのビルド
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-repo/api-server:$COMMIT_SHA'
- '.'

# イメージのプッシュ
- name: 'gcr.io/cloud-builders/docker'
args:
- 'push'
- 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-repo/api-server:$COMMIT_SHA'

# Cloud Runへデプロイ
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'run'
- 'deploy'
- 'api-server'
- '--image'
- 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-repo/api-server:$COMMIT_SHA'
- '--region'
- 'asia-northeast1'

images:
- 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-repo/api-server:$COMMIT_SHA'

マイクロサービス間通信とサービスメッシュ

複数のCloud Runサービスが連携する場合、サービス間通信の設計が重要になります。Cloud Runは内部通信用のURLを自動的に提供しますが、認証の設定に注意が必要です。

  • サービス間認証:IAMベースの認証を使い、サービスアカウントに適切な権限を付与します
  • リトライとタイムアウト:ネットワーク障害を想定し、指数バックオフ付きのリトライを実装します
  • サーキットブレーカー:下流サービスの障害が上流に波及するのを防ぐ
  • 非同期通信:Cloud Pub/Subを活用して、サービス間の疎結合を実現します

特にPub/Subとの連携は強力です。Cloud RunのPushサブスクリプションを使えば、メッセージ駆動のイベント処理を簡単に実現できます。

コスト最適化のポイント

Cloud Runのコストは、CPU時間、メモリ、リクエスト数で決まります。以下の点を意識することでコストを大幅に削減できます。

  • cpu_idleの活用:リクエスト処理時のみCPUを割り当てるモードを使う
  • 最小インスタンス数の適正化:本番では1、開発環境では0に設定します
  • 同時実行数の最適化:1インスタンスあたりの同時リクエスト数を適切に設定し、インスタンス数を抑えます
  • コンテナイメージの軽量化:マルチステージビルドやdistrolessイメージを活用します

まとめ:Cloud Runが最適な選択肢となるケース

Cloud Runは、コンテナベースのマイクロサービスを最小の運用負荷でデプロイしたい場合に最適です。Kubernetesほどの柔軟性は不要だが、Lambdaのような制約も受けたくないという中間的なニーズに応えてくれます。特にGo、Python、JavaなどでバックエンドAPIを構築するチームにとって、学習コストと運用効率のバランスに優れた選択肢と言えるでしょう。まずは小さなサービスからCloud Runを試し、マイクロサービスアーキテクチャへの第一歩を踏み出してみてください。

この記事をシェアする

  • Twitterでシェア
  • Facebookでシェア
  • LINEでシェア