0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GKEのSecret管理を1元化したい

0
Last updated at Posted at 2026-05-31

はじめに

GKEでアプリケーションを運用していると、こんな状況に陥りがちです。

  • Kubernetes SecretがマニフェストにベタBookきされていてGitにコミットできない
  • 複数のNamespaceやコンポーネントで同じSecretを使い回していて、変更のたびに複数箇所を修正している

この記事では、Google Secret ManagerをSingle Source of Truthとして、Kubernetes Secretを安全に管理・同期するまでの試行錯誤を紹介します。

筆者が辿った道のりはこうです。

  1. 方法①:SPC + SecretSync を試したが、運用上の課題があった
  2. 方法②:CDパイプライン(Cloud Deploy)上のshスクリプト で現在運用中

また、他の選択肢として External Secrets Operator(ESO) も簡単に紹介します。

同じ課題を抱えている方の参考になれば幸いです。

対象読者

  • KubernetesとGCPの基本的な操作経験がある方
  • Kubernetes SecretのGit管理に課題を感じている方
  • GKEでのシークレット管理のベストプラクティスを探している方

なぜKubernetes SecretをGitに直書きしてはいけないのか

Kubernetes Secretはbase64エンコードされた状態でマニフェストに記述されます。しかし、base64はエンコードであって暗号化ではありません。以下のコマンドで誰でも即座に復元できます。

$ echo "cGFzc3dvcmQ=" | base64 -d
password

Gitリポジトリにアクセスできる全員が、秘密情報を平文で読める状態になります。さらに、複数のマイクロサービスやNamespaceで同じSecretを利用している場合、以下の問題が生じます。

  • 変更時に複数のマニフェストを修正しなければならない
  • 管理が属人化し、どこで何が使われているか把握しにくくなる

これらの課題を解決するために、Google Secret Managerで一元管理し、Kubernetes Secretへ同期するアプローチを検討しました。


構成の全体像

どの方法でも、基本的な考え方は同じです。

Google Secret Manager(一元管理)
    ├── secret/db-password
    ├── secret/api-key
    └── secret/db-host
           ↓ 何らかの方法で同期
    Kubernetes Secret
        ├── DB_PASSWORD
        ├── API_KEY
        └── DB_HOST

「何らかの方法」の部分が、以降で紹介する3つのアプローチの違いです。


方法①:SecretProviderClass(SPC)+ SecretSyncを使う

SPC + SecretSyncとは

この方法はGKEネイティブの機能を使います。2つのリソースをセットで利用します。

  • SecretProviderClass(SPC)Secret Store CSI Driver が提供するカスタムリソース。「どのGoogle SecretをKubernetes Secretに同期するか」を定義します
  • SecretSync:GKEが提供するリソース。SPCをベースにKubernetes Secretへの同期を制御します

GKEクラスタへの機能有効化

SecretSyncを利用するには、GKEクラスタレベルで機能を有効化する必要があります。デフォルトではOFFです。

Terraformで設定する場合

resource "google_container_cluster" "main" {
  name     = "my-cluster"
  location = "asia-northeast1"

  # Secret Sync機能を有効化
  secret_sync_config {
    enabled = true
  }
}

gcloud CLIで既存クラスタに設定する場合

gcloud container clusters update my-cluster \
  --location=asia-northeast1 \
  --enable-secret-sync

SPCのYAML定義

Google Secret Managerの各シークレットをどう参照するかを定義します。

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: my-app-secrets
  namespace: default
spec:
  provider: gcp
  parameters:
    secrets: |
      - resourceName: "projects/my-project/secrets/db-password/versions/latest"
        fileName: "db-password"
      - resourceName: "projects/my-project/secrets/api-key/versions/latest"
        fileName: "api-key"
      - resourceName: "projects/my-project/secrets/db-host/versions/latest"
        fileName: "db-host"

SecretSyncのYAML定義

SPCをベースに、Kubernetes Secretへの同期を定義します。

apiVersion: secretmanager.cnrm.cloud.google.com/v1beta1
kind: SecretSync
metadata:
  name: my-app-secret-sync
  namespace: default
spec:
  secretProviderClassName: my-app-secrets  # 上記SPCの名前
  serviceAccountName: my-ksa-name
  secretObjects:
    - secretName: my-app-secret
      type: Opaque
      data:
        - objectName: db-password
          key: DB_PASSWORD
        - objectName: api-key
          key: API_KEY
        - objectName: db-host
          key: DB_HOST

実装してみてわかった課題

① Google Secret Managerの1KVP制約

Google Secret Managerは 「1シークレット = 1つの値(文字列)」 という設計です。Kubernetes Secretは複数のKVP(DB_HOSTDB_PASSAPI_KEY など)を1リソースに持てますが、Google Secret Manager側はそれができません。

そのため、Kubernetes Secretに複数のKVPを持たせたい場合は、Google Secret Manager側に同じ数だけシークレットを作成し、SPCでまとめて束ねる必要があります。

Google Secret Manager          Kubernetes Secret
secret/db-password     ──┐
secret/api-key         ──┼──→  my-app-secret
secret/db-host         ──┘       ├── DB_PASSWORD
                                  ├── API_KEY
                                  └── DB_HOST

KVPが増えるほどGoogle Secret Manager側のシークレット数も増え、対応関係の管理が煩雑になります。

② 管理リソースの増加

SPC・SecretSyncというリソースがクラスタに追加されます。シークレットの種類やNamespaceが増えるにつれ、管理するリソースも比例して増えていきます。

③ Workload Identityの設定が必要

GKEのPodがGoogle Secret Managerにアクセスするには、Workload Identityの設定が別途必要です。KubernetesのServiceAccountとGCPのService Accountを紐付け、roles/secretmanager.secretAccessor 権限を付与します。


方法②:CDパイプライン(Cloud Deploy)でshスクリプト同期

アプローチの概要

SPC+SecretSyncの運用コストを考慮した結果、よりシンプルな方法に切り替えました。デプロイ時に、シェルスクリプトでGoogle Secret ManagerからシークレットをPullし、kubectl でKubernetes Secretを作成・更新します。

Cloud Deploy パイプライン
    │
    ├── [事前ステップ] sync-secrets.sh 実行
    │       ↓
    │   gcloud secrets versions access → kubectl apply
    │
    └── [本番ステップ] マニフェストのデプロイ(kubectl apply)

常駐リソースを持たず、デプロイのたびに最新のシークレットが適用されるシンプルな方式です。

Google Secret Manager側のシークレット設計

この方法では、Google Secret Managerのシークレット値を**env形式(KEY=VALUE)**で保存します。1つのシークレットに複数のKVPをまとめられるため、SPC+SecretSyncで問題になった1KVP制約を回避できます。

secret/my-app-config の値:
DB_PASSWORD=xxx
API_KEY=yyy
DB_HOST=zzz

シェルスクリプト例

Google Secret Managerからenv形式のシークレットを取得し、パースしてKubernetes Secretを作成するスクリプト例です。

#!/bin/bash
# sync-secrets.sh
# Google Secret ManagerからシークレットをPullしてKubernetes Secretを作成する

set -euo pipefail

PROJECT_ID="${GCP_PROJECT_ID}"
NAMESPACE="${K8S_NAMESPACE:-default}"
SECRET_NAME="${K8S_SECRET_NAME:-my-app-secret}"

echo "Fetching secrets from Google Secret Manager..."

# env形式(KEY=VALUE)で保存されたシークレットを取得
ENV_VARS=$(gcloud secrets versions access latest \
  --secret="my-app-config" \
  --project="${PROJECT_ID}")

# env形式をパースして --from-literal の引数を組み立てる
FROM_LITERAL_ARGS=""
while IFS= read -r line; do
  [[ -z "$line" || "$line" == \#* ]] && continue
  FROM_LITERAL_ARGS="${FROM_LITERAL_ARGS} --from-literal=${line}"
done <<< "${ENV_VARS}"

# --dry-run=client + kubectl apply で冪等に実行
echo "Applying Kubernetes Secret..."

kubectl create secret generic "${SECRET_NAME}" \
  --namespace="${NAMESPACE}" \
  ${FROM_LITERAL_ARGS} \
  --dry-run=client -o yaml | kubectl apply -f -

echo "Secret sync completed: ${SECRET_NAME}"

--dry-run=client -o yaml | kubectl apply -f - のパターンを使うことで、SecretがすでにExistしていても冪等に実行できます。

この方法を選んだ理由

  • シンプルさ:シェルスクリプト1本で完結し、クラスタへの追加リソースが不要
  • CDパイプラインへの自然な統合:Cloud Deployの仕組みを壊さずに組み込める
  • 1KVP制約を回避できる:env形式で保存することで、1つのGoogle Secretに複数のKVPをまとめられる
  • デプロイサイクルで十分:常時自動同期は不要で、デプロイ時に最新が反映されれば問題なかった

補足:External Secrets Operator(ESO)という選択肢もある

External Secrets Operator はKubernetes上にOperatorとしてデプロイし、ExternalSecretリソースでGoogle Secret ManagerのシークレットをKubernetes Secretへ同期する仕組みです。筆者はまだ試していないので参考程度に。

この方法であれば複数のKVPを1つのシークレットにまとめられ、同期自体もマニフェストで管理できます。

Google Secret Manager
  secret/my-app-config
  値: {"DB_PASSWORD":"xxx","API_KEY":"yyy","DB_HOST":"zzz"}
          ↓ ESOが自動展開
  Kubernetes Secret
    ├── DB_PASSWORD: xxx
    ├── API_KEY: yyy
    └── DB_HOST: zzz

また、AWS Secrets ManagerやAzure Key Vaultなど他のバックエンドにも対応しており、マルチクラウド環境への拡張性も高いです。KVP数が多い構成や長期的な運用を見据える場合は、有力な選択肢の1つです。


まとめ

  • Kubernetes SecretのGit直書きは、base64エンコードのみで誰でも復元できるため危険
  • Google Secret Managerで一元管理し、Kubernetes Secretへ同期することで安全に管理できる
  • SPC + SecretSync:GKEネイティブで自動同期できるが、管理リソースが増える。1KVP制約あり
  • CDスクリプト方式:シンプルで導入コストが低い。env形式で保存することで1KVP制約も回避できる
  • ESO:VP数が多い場合や長期運用に向く。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?