Cloud Run から Secret Manager の機密情報を利用する 2 つの方法

Cloud Run から Secret Manager の機密情報を利用する 2 つの方法

2026.06.22

はじめに

こんにちは。
クラウド事業本部コンサルティング部の渡邉です。

Cloud Run でアプリケーションを開発する際、データベースのパスワードや API キーなどの機密情報をどのように管理するか悩んだことはないでしょうか。コンテナイメージや環境変数にそのまま埋め込むと、セキュリティリスクが生じます。

Google Cloud では、Secret Manager にこれらの機密情報を集中管理し、Cloud Run から安全に参照する仕組みが提供されています。

本記事では、Cloud Run から Secret Manager の機密情報を利用する 2 つの方法である

  1. 環境変数として参照する
  2. ボリュームとしてマウントする

について、それぞれの特徴と gcloud CLI を使ったハンズオン手順をご紹介します。

Secret Manager とは

Secret Manager は、API キー・パスワード・証明書などの機密情報を安全に保存・管理するためのマネージドサービスです。バージョン管理や IAM による細かなアクセス制御が可能で、ローテーションにも対応しています。

Cloud Run と Secret Manager を組み合わせることで、以下のメリットが得られます。

  • 機密情報をコードやコンテナイメージに含めない
  • IAM を使ってアクセス制御を一元管理できる
  • Secret のバージョン管理・ローテーションが容易になる

2 つの参照方法の比較

環境変数として参照する方法と、ボリュームとしてマウントする方法それぞれの項目ごとの比較です。

環境変数の場合、値はインスタンス起動時に取得されます。Secret をローテーションした場合、新しい値を反映させるには再デプロイ(または新しいリビジョンの起動)が必要です。そのため、公式ドキュメントでは特定のバージョンへの固定を推奨しています。

ボリュームマウントの場合、Cloud Run はファイル読み取り時に常に Secret Manager から最新の値を取得します。ローテーションとの相性が良い方法です。

項目 環境変数 ボリュームマウント
参照タイミング インスタンス起動時 ファイル読み取り時(常に最新版取得)
ローテーション対応 △(特定バージョンへの固定推奨) ○(自動で最新版を取得)
アクセス方法 os.environ["VAR"] などの環境変数 ファイル読み取り
推奨バージョン指定 特定バージョンへの固定推奨 latest でも問題なし

実際に試してみる

前提条件

  • Google Cloud プロジェクトが作成済みであること
  • gcloud CLI がインストール・認証済みであること
  • Cloud Run サービスをデプロイするための権限があること

ステップ 1: API の有効化

Secret Manager API と Cloud Run API を有効化します。

gcloud services enable secretmanager.googleapis.com \
  --project=YOUR_PROJECT_ID

gcloud services enable run.googleapis.com \
  --project=YOUR_PROJECT_ID

ステップ 2: Secret の作成

今回は例として my-db-password という名前で Secret を作成します。

echo -n "my-super-secret-password" | gcloud secrets create my-db-password \
  --replication-policy="automatic" \
  --data-file=- \
  --project=YOUR_PROJECT_ID

Created version [1] of the secret [my-db-password].

作成された Secret を確認します。

作成された Secret
作成された Secret

ステップ 3: サービスアカウントの作成と権限付与

Cloud Run 専用のサービスアカウントを作成し、Secret Manager へのアクセス権を付与します。

まず、サービスアカウントを作成します。

gcloud iam service-accounts create cloud-run-secret-demo \
  --display-name="Cloud Run Secret Demo" \
  --project=YOUR_PROJECT_ID

Created service account [cloud-run-secret-demo].

作成したサービスアカウント
作成したサービスアカウント

次に、作成したサービスアカウントに対して、特定の Secret へのアクセス権(roles/secretmanager.secretAccessor)を付与します。

gcloud secrets add-iam-policy-binding my-db-password \
  --member="serviceAccount:cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor" \
  --project=YOUR_PROJECT_ID

Updated IAM policy for secret [my-db-password].
bindings:
- members:
  - serviceAccount:cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com
  role: roles/secretmanager.secretAccessor
etag: BwZUy8VAZNQ=
version: 1

シークレットへの権限付与
シークレットへの権限付与

ステップ 4: サンプルアプリの準備

Secret の読み取りを確認するためのサンプルアプリを作成します。以下の 3 ファイルを同じディレクトリに作成してください。

app.py

import os
from flask import Flask, jsonify

app = Flask(__name__)


@app.route("/")
def index():
    return jsonify({"status": "ok"})


@app.route("/env-secret")
def env_secret():
    secret = os.environ.get("DB_PASSWORD", "")
    if not secret:
        return jsonify({"method": "environment_variable", "loaded": False}), 500
    return jsonify({"method": "environment_variable", "loaded": True, "value": secret})


@app.route("/volume-secret")
def volume_secret():
    try:
        with open("/etc/secrets/db/password", "r") as f:
            secret = f.read()
        return jsonify({"method": "volume_mount", "loaded": True, "value": secret})
    except Exception as e:
        return jsonify({"method": "volume_mount", "loaded": False, "error": str(e)}), 500


if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8080))
    app.run(host="0.0.0.0", port=port, debug=False)

requirements.txt

Flask==3.1.0

Dockerfile

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

ENV PORT=8080
EXPOSE 8080

CMD ["python", "app.py"]

次に、Artifact Registry にリポジトリを作成します。

gcloud artifacts repositories create secret-demo-repo \
  --repository-format=docker \
  --location=asia-northeast1 \
  --project=YOUR_PROJECT_ID

Create request issued for: [secret-demo-repo]
Waiting for operation [projects/YOUR_PROJECT_ID/locations/asia-northeast1/operations/e2583df1-2c70-42f4-b1df-b55364552a54] to
 complete...done.                                                                                                            
Created repository [secret-demo-repo].

app.pyrequirements.txtDockerfile を配置したディレクトリで以下のコマンドを実行すると、cloudbuild.yaml などの設定ファイルは不要で、カレントディレクトリの Dockerfile を使ったビルドからイメージのプッシュまでを Cloud Build が自動で実行します。

# app.py / requirements.txt / Dockerfile を配置したディレクトリで実行
gcloud builds submit \
  --tag asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest \
  --project=YOUR_PROJECT_ID

Artifact Registryのビルドイメージ
Artifact Registryのビルドイメージ

ステップ 5-A: 方法① 環境変数として参照する

--update-secrets フラグを使い、ENV_VAR_NAME=SECRET_NAME:VERSION の形式でSecret Managerを環境変数に指定します。

gcloud run deploy my-service \
  --image asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest \
  --update-secrets=DB_PASSWORD=my-db-password:1 \
  --service-account=cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com \
  --region=asia-northeast1 \
  --project=YOUR_PROJECT_ID
  • DB_PASSWORD: コンテナ内で参照する想定の環境変数名
  • my-db-password: Secret Manager の Secret 名
  • 1: バージョン番号(環境変数の場合は特定バージョンへの固定を推奨)

デプロイ後、サービスの設定で環境変数が正しく設定されていることを確認します。

gcloud run services describe my-service \
  --region=asia-northeast1 \
  --project=YOUR_PROJECT_ID

 Service my-service in region asia-northeast1
 
URL:     https://my-service-XXXXXXXXXXXX.asia-northeast1.run.app
Ingress: all
Traffic:
  100% LATEST (currently my-service-00001-5t7)
 
Scaling: Auto (Min: 0, Max: 100)
 
Last updated on 2026-06-21T23:31:35.741366Z by YOUR_EMAIL@example.com:
  Revision my-service-00001-5t7
  Container None
    Image:           asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest
    Port:            8080
    Memory:          512Mi
    CPU:             1000m
    Secrets:
      DB_PASSWORD    my-db-password:1
    Startup Probe:
      TCP every 240s
      Port:          8080
      Initial delay: 0s
      Timeout:       240s
      Failure threshold: 1
      Type:          Default
  Service account:   cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com
  Concurrency:       80
  Timeout:           300s

デプロイしたCloud Runサービス
デプロイしたCloud Runサービス

アプリケーション側では、通常の環境変数として参照できます。

作成されたCloud Runの /env-secret エンドポイントにリクエストして、Secret が正しく読み取れているか確認します。

# サービス URL を取得
SERVICE_URL=$(gcloud run services describe my-service \
  --region=asia-northeast1 \
  --project=YOUR_PROJECT_ID \
  --format="value(status.url)")

# 環境変数で読み取った Secret を確認
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
  "${SERVICE_URL}/env-secret"

以下のレスポンスが返りました。環境変数として Secret が正しく参照できています。

{
  "loaded": true,
  "method": "environment_variable",
  "value": "my-super-secret-password"
}

ステップ 5-B: 方法② ボリュームとしてマウントする

--update-secrets フラグで /PATH=SECRET_NAME:VERSION の形式(パスがスラッシュで始まる)でボリュームマウントを指定します。

gcloud run deploy my-service \
  --image asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest \
  --update-secrets=/etc/secrets/db/password=my-db-password:latest \
  --service-account=cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com \
  --region=asia-northeast1 \
  --project=YOUR_PROJECT_ID

Deploying container to Cloud Run service [my-service] in project [YOUR_PROJECT_ID] region [asia-northeast1]
 Deploying... Done.                                                                                                         
 Creating Revision...                                                                                                     
 Routing traffic...                                                                                                       
Done.                                                                                                                        
Service [my-service] revision [my-service-00003-56t] has been deployed and is serving 100 percent of traffic.
Service URL: https://my-service-XXXXXXXXXXXX.asia-northeast1.run.app
  • /etc/secrets/db/password: コンテナ内のファイルパス(/etc/secrets/db/ がマウントパス、password がファイル名)
  • my-db-password: Secret Manager の Secret 名
  • latest: バージョン(ボリュームマウントは latest の使用が可能)

アプリケーション側では、ファイルとして読み取ります。

デプロイ後、設定を確認します。

gcloud run services describe my-service \
  --region=asia-northeast1 \
  --project=YOUR_PROJECT_ID

 Service my-service in region asia-northeast1
 
URL:     https://my-service-XXXXXXXXXXXX.asia-northeast1.run.app
Ingress: all
Traffic:
  100% LATEST (currently my-service-00003-56t)
 
Scaling: Auto (Min: 0, Max: 100)
 
Last updated on 2026-06-21T23:40:27.103632Z by YOUR_EMAIL@example.com:
  Revision my-service-00003-56t
  Container None
    Image:           asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest
    Port:            8080
    Memory:          512Mi
    CPU:             1000m
    Secrets:
      /etc/secrets/db my-db-password:latest
      DB_PASSWORD    my-db-password:1
    Startup Probe:
      TCP every 240s
      Port:          8080
      Initial delay: 0s
      Timeout:       240s
      Failure threshold: 1
      Type:          Default
  Service account:   cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com
  Concurrency:       80
  Scaling:
    Max instances:   100
  Timeout:           300s

再デプロイしたCloud Runサービス
再デプロイしたCloud Runサービス

更新したCloud Runの /volume-secret エンドポイントにリクエストして、Secret が正しく読み取れているか確認します。

# サービス URL を取得(ステップ 5-A で取得済みの場合は不要)
SERVICE_URL=$(gcloud run services describe my-service \
  --region=asia-northeast1 \
  --project=YOUR_PROJECT_ID \
  --format="value(status.url)")

# ボリュームマウントで読み取った Secret を確認
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
  "${SERVICE_URL}/volume-secret"

以下のようなレスポンスが返れば、ボリュームマウントとして Secret が正しく参照できています。

{
  "loaded": true,
  "method": "volume_mount",
  "value": "my-super-secret-password"
}

ローテーション後の自動反映を確認する

ボリュームマウント方式の特徴である「再デプロイなしで最新バージョンを自動取得する」動作を確認します。

Secret の新しいバージョンを追加します。

echo -n "my-rotated-password" | gcloud secrets versions add my-db-password \
  --data-file=- \
  --project=YOUR_PROJECT_ID

Created version [2] of the secret [my-db-password].

更新したSercet
更新したSercet

再デプロイは行わず、そのまま /volume-secret エンドポイントに再リクエストします。

curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
  "${SERVICE_URL}/volume-secret"

新しいバージョンの値が返ってきました。再デプロイなしで最新の Secret が反映されていることを確認できました。

{
  "loaded": true,
  "method": "volume_mount",
  "value": "my-rotated-password"
}

まとめ

本記事では、Cloud Run から Secret Manager の機密情報を参照する 2 つの方法をご紹介しました。

環境変数方式はデプロイ設定だけで完結するためシンプルで、既存のコードをほぼ変更せずに利用できます。一方で、値はインスタンス起動時に一度だけ解決されるため、Secret をローテーションした場合は新しいリビジョンの起動が必要です。意図せず最新バージョンが読み込まれるリスクを避けるため、特定のバージョン番号への固定が推奨されています。

ボリュームマウント方式はファイルを読み取るたびに Secret Manager から最新の値を取得するため、ローテーションとの相性が良く、証明書ファイルのような複数行データの管理にも適しています。ただし、ボリュームのマウント先として /dev/proc/sys は使用できず、既存ディレクトリをマウントパスに指定するとその配下のファイルがアクセス不能になる点に注意が必要です。また、Cloud Run はリージョナル Secret をサポートしていないため、自動レプリケーションポリシーの Secret を使用してください。

どちらの方式を選ぶ場合も、最小権限の原則に従い、Cloud Run のサービスアカウントには roles/secretmanager.secretAccessor をプロジェクト全体ではなく特定の Secret に対して付与することで、セキュリティリスクを最小限に抑えることができます。

この記事が誰かの助けになれば幸いです。

以上、クラウド事業本部コンサルティング部の渡邉でした!

この記事をシェアする

関連記事