Secret Managerと連携させてCloud RunにFastAPIサーバをデプロイしてみた
今回は先日公開した以下のSecret Managerの使い方を応用して、Cloud Runにそのシークレットを読ませてサーバを公開してみました。
システム構成
今回の構成は以下のようになっています。
- Secret Managerにてシークレットを管理
- Artifact RegistryにてCloud RunサーバんじょDockerイメージ管理
- Cloud RunにてFastAPIサーバをデプロイ
実装開始
IaCの実装
IaCのフォルダ構成は以下のようになっています。
main.tf
variables.tf
secret.txt
modules/
artifact_registry/
main.tf
variables.tf
outputs.tf
secret_manager/
main.tf
variables.tf
outputs.tf
cloud_run/
main.tf
variables.tf
outputs.tf
Artifact Registryのリソース定義
Artifact Registryのリソース定義は以下のようになっています。変数としてはregion
とレポジトリ名を指定するためのartifact_registry_repo_name
を設定し、出力としてはレポジトリのIDをCloud Runに伝えるためにリポジトリのIDを返すようにしています。
modules/artifact_registry/variables.tf
variable "region" {
description = "The Google Cloud region"
type = string
}
variable "artifact_registry_repo_name" {
description = "Artifact Registry repo's name"
type = string
}
modules/artifact_registry/main.tf
resource "google_artifact_registry_repository" "fastapi_repo" {
location = var.region
repository_id = var.artifact_registry_repo_name
description = "FastAPI Application"
format = "DOCKER"
}
modules/artifact_registry/outputs.tf
output "repository_id" {
description = "Artifact Registory ID"
value = google_artifact_registry_repository.fastapi_repo.repository_id
}
Secret Managerのリソース定義
次はSecret Managerでシークレットを作成します。基本的な構成は最初に添付した先日の記事と同じです。出力としてCloud Runにシークレットを指定するためにsecret_id
を設定しています。
modules/secret_manager/variables.tf
variable "region" {
description = "The Google Cloud region"
type = string
}
variable "secret_file" {
description = "secret file"
type = string
}
modules/secret_manager/main.tf
data "google_project" "project" {
}
resource "google_secret_manager_secret" "fastapi_app_secret" {
secret_id = "fastapi-application-secret"
replication {
user_managed {
replicas {
location = var.region
}
}
}
}
resource "google_secret_manager_secret_version" "fastapi_app_secret_version" {
secret = google_secret_manager_secret.fastapi_app_secret.id
secret_data = file(var.secret_file)
deletion_policy = "DISABLE"
}
resource "google_secret_manager_secret_iam_member" "secretaccess" {
secret_id = google_secret_manager_secret.fastapi_app_secret.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com"
}
modules/secret_manager/outputs.tf
output "secret_id" {
description = "Secret ID"
value = google_secret_manager_secret.fastapi_app_secret.id
}
Cloud Runのリソース定義
次のリソース定義はCloud Runになります。Artifact RegistryとSecret Managerのリソース定義にて作成したリソースを元に、サーバの設定を実施します。リソース定義において今回は公開アクセスにするためにgoogle_cloud_run_v2_service_iam_member
をallUsers
で権限設定しています。あと、Cloud RunのリソースはArtifact RegistryにDockerイメージがないと作成できません。そこで、deploy_cloud_run
フラグを利用し、最初のリソース作成はCloud Run以外を作成してその後Artifact Registryにイメージをpushし、次のdeployにてdeploy_cloud_run=true
にてリソースを作成します。
modules/cloud_run/variables.tf
variable "region" {
description = "The Google Cloud region"
type = string
}
variable "secret_id" {
description = "Secret ID"
type = string
}
variable "repository_id" {
description = "Artifact Registry Id"
type = string
}
variable "image_name" {
description = "Image name"
type = string
}
variable "deploy_cloud_run" {
description = "Whether to deploy Cloud Run service (set to true after pushing Docker image)"
type = bool
}
modules/cloud_run/main.tf
data "google_project" "project" {
}
resource "google_cloud_run_v2_service" "fastapi_app" {
count = var.deploy_cloud_run ? 1 : 0
name = "fastapi-app"
location = var.region
deletion_protection = false
ingress = "INGRESS_TRAFFIC_ALL"
scaling {
max_instance_count = 2
}
template {
containers {
image = "${var.region}-docker.pkg.dev/${data.google_project.project.project_id}/${var.repository_id}/${var.image_name}:latest"
env {
name = "SECRET_KEY"
value_source {
secret_key_ref {
secret = var.secret_id
version = "latest"
}
}
}
}
}
}
resource "google_cloud_run_v2_service_iam_member" "all_users_invoker" {
count = var.deploy_cloud_run ? 1 : 0
location = google_cloud_run_v2_service.fastapi_app[0].location
name = google_cloud_run_v2_service.fastapi_app[0].name
role = "roles/run.invoker"
member = "allUsers"
}
modules/cloud_run/outputs.tf
output "cloud_run_url" {
description = "Cloud Run's server URL"
value = length(google_cloud_run_v2_service.fastapi_app) > 0 ? google_cloud_run_v2_service.fastapi_app[0].uri : ""
}
ルートファイルの設定
それでは先ほど作成したモジュールをよみこむルートファイルを作成します。3つのモジュールを読み込み、outputの連携などを設定しています。
SECRET VALUE
variables.tf
variable "project_id" {
description = "The Google Cloud project ID"
type = string
}
variable "region" {
description = "The Google Cloud region"
type = string
default = "asia-northeast1"
}
variable "secret_file" {
description = "Secret file"
type = string
}
variable "artifact_registry_repo_name" {
description = "Artifact Registry repo's name"
type = string
default = "fastapi-app"
}
variable "image_name" {
description = "Docker image name"
type = string
default = "fastapi-app"
}
variable "deploy_cloud_run" {
description = "Whether to deploy Cloud Run service (set to true after pushing Docker image)"
type = bool
default = false
}
main.tf
provider "google" {
project = var.project_id
region = var.region
}
module "secret_manager" {
source = "./modules/secret_manager"
region = var.region
secret_file = var.secret_file
}
module "artifact_registry" {
source = "./modules/artifact_registry/"
region = var.region
artifact_registry_repo_name = var.artifact_registry_repo_name
}
module "cloud_run" {
source = "./modules/cloud_run"
region = var.region
secret_id = module.secret_manager.secret_id
image_name = var.image_name
repository_id = module.artifact_registry.repository_id
deploy_cloud_run = var.deploy_cloud_run
}
この状態でterraform plan
を実行すると以下のようになります。
terraform plan
module.secret_manager.data.google_project.project: Reading...
module.cloud_run.data.google_project.project: Reading...
module.secret_manager.data.google_project.project: Read complete after 2s [id=projects/project_id]
module.cloud_run.data.google_project.project: Read complete after 2s [id=projects/project_id]
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.artifact_registry.google_artifact_registry_repository.fastapi_repo will be created
+ resource "google_artifact_registry_repository" "fastapi_repo" {
+ create_time = (known after apply)
+ description = "FastAPI Application"
+ effective_labels = {
+ "goog-terraform-provisioned" = "true"
}
+ format = "DOCKER"
+ id = (known after apply)
+ location = "asia-northeast1"
+ mode = "STANDARD_REPOSITORY"
+ name = (known after apply)
+ project = "project_id"
+ registry_uri = (known after apply)
+ repository_id = "fastapi-app"
+ terraform_labels = {
+ "goog-terraform-provisioned" = "true"
}
+ update_time = (known after apply)
+ vulnerability_scanning_config (known after apply)
}
# module.secret_manager.google_secret_manager_secret.fastapi_app_secret will be created
+ resource "google_secret_manager_secret" "fastapi_app_secret" {
+ create_time = (known after apply)
+ deletion_protection = false
+ effective_annotations = (known after apply)
+ effective_labels = {
+ "goog-terraform-provisioned" = "true"
}
+ expire_time = (known after apply)
+ id = (known after apply)
+ name = (known after apply)
+ project = "project_id"
+ secret_id = "fastapi-application-secret"
+ terraform_labels = {
+ "goog-terraform-provisioned" = "true"
}
+ replication {
+ user_managed {
+ replicas {
+ location = "asia-northeast1"
}
}
}
}
# module.secret_manager.google_secret_manager_secret_iam_member.secretaccess will be created
+ resource "google_secret_manager_secret_iam_member" "secretaccess" {
+ etag = (known after apply)
+ id = (known after apply)
+ member = "serviceAccount:795031300012-compute@developer.gserviceaccount.com"
+ project = (known after apply)
+ role = "roles/secretmanager.secretAccessor"
+ secret_id = (known after apply)
}
# module.secret_manager.google_secret_manager_secret_version.fastapi_app_secret_version will be created
+ resource "google_secret_manager_secret_version" "fastapi_app_secret_version" {
+ create_time = (known after apply)
+ deletion_policy = "DISABLE"
+ destroy_time = (known after apply)
+ enabled = true
+ id = (known after apply)
+ is_secret_data_base64 = false
+ name = (known after apply)
+ secret = (known after apply)
+ secret_data = (sensitive value)
+ secret_data_wo_version = 0
+ version = (known after apply)
}
Plan: 4 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
FastAPIサーバの実装
次にシークレットの値を参照してそれを表示するFastAPサーバを実装します。
環境構築
uv init fastapi_app -p 3.12
cd fastapi_app
uv add fastapi uvicorn
FastAPIサーバの実装
FastAPIサーバを実装します。シンプルに環境変数を読み込んで返せすAPIになります。
from fastapi import FastAPI
import os
app = FastAPI()
SECRET_VALUE = os.environ["SECRET_KEY"]
@app.get("/secret")
def index():
return {"secret_value": SECRET_VALUE}
Dockerfileの作成
Dockerfileは以下のようになります。
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
COPY . /app
EXPOSE 8080
RUN uv sync
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
ビルドとpush
今回は以下のshell scriptを利用してArtifact Registryにpushします。
#!/bin/zsh
gcloud auth configure-docker asia-norhteast1-docker.pkg.dev
docker build -t asia-northeast1-docker.pkg.dev/project_id/fastapi-app/fastapi-app:latest --platform linux/amd64 .
docker push asia-northeast1-docker.pkg.dev/project_id/fastapi-app/fastapi-app:latest
環境構築
それでは実際に実行してみましょう。手順は以下になります。
- Artifact RegistryとSecret Managerリソースを作成する(
deploy_cloud_run=false
) - FastAPIサーバをビルドしてpushする
- Cloud Runリソースを作成する(
deploy_cloud_run=true
)
この手順で作成されたCloud Runリソースは以下のようになっています。環境変数を見るとSECRET_KEY
という名前でキーが設定されており、その値はSecret Managerから参照されていることが確認できます。
Cloud Runで作成された公開URIにアクセスすると、以下のようにSwagger UIが表示できました。試しに実行すると今回指定したSECRET VALUE
という値が参照できることが確認できました。
動作確認が終了したのでterraform destroy
でリソースは削除します。
まとめ
今回はSecret Managerで作成したシークレットをCloud Runサーバの環境変数として登録し、APIで利用できることを確認しました。以前個別に試していたサービスを実際に連携して利用できるところまで試すことができ、順調にインフラ構築に慣れてきていることを実感できました。今後はもっと複雑なアーキテクチャにもチャレンジしてみようと思います。
Discussion