Google Cloud Runは、コンテナイメージをデプロイするだけでスケーラブルなサービスを実行できるフルマネージドプラットフォームです。Knativeをベースとしており、コンテナの柔軟性とサーバーレスのスケーラビリティを兼ね備えています。
AWS Lambdaと比較されることが多いですが、Cloud Runの最大の差別化ポイントは任意のコンテナイメージを実行できます点です。言語やフレームワークの制約がなく、既存のDockerイメージをほぼそのままデプロイできます。筆者はPythonのFastAPIやGoのAPIサーバーなど、複数のマイクロサービスをCloud Runで運用しています。
Cloud Runへのデプロイは驚くほどシンプルです。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が割り当てられず、コストを大幅に削減できます。ただし、バックグラウンド処理が必要なサービスでは無効にする必要があります。
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を自動的に提供しますが、認証の設定に注意が必要です。
特にPub/Subとの連携は強力です。Cloud RunのPushサブスクリプションを使えば、メッセージ駆動のイベント処理を簡単に実現できます。
Cloud Runのコストは、CPU時間、メモリ、リクエスト数で決まります。以下の点を意識することでコストを大幅に削減できます。
Cloud Runは、コンテナベースのマイクロサービスを最小の運用負荷でデプロイしたい場合に最適です。Kubernetesほどの柔軟性は不要だが、Lambdaのような制約も受けたくないという中間的なニーズに応えてくれます。特にGo、Python、JavaなどでバックエンドAPIを構築するチームにとって、学習コストと運用効率のバランスに優れた選択肢と言えるでしょう。まずは小さなサービスからCloud Runを試し、マイクロサービスアーキテクチャへの第一歩を踏み出してみてください。