用 Google Secret Manager 搭配 External Secret Operator 管理 GKE 機敏資料
本文介紹如何在 GKE 環境使用 Google Secret Manager (GSM) 集中管理機敏資料,並透過 Workload Identity (WI) 與 External Secret Operator (ESO) 實現 K8s Secret 自動同步,附上 Terraform 與 YAML 實作範例。
TL;DR
- 使用 Google Secret Manager (GSM) 集中管理機敏資料
- 透過 Workload Identity (WI) 授予 External Secret Operator (ESO) 存取 GSM 權限
- ESO 自動同步 GKE Secret 與 GSM Secret
背景
有些服務仍透過環境變數 .env 搭配 kind: secret 部署,管理不便且難以維護。
本文介紹如何使用 GSM + ESO + WI,將 Secret 管理自動化並統一管控。
建立 GSM 機敏資料
在 GSM 中以 JSON 格式存放 key-value。

Workload Identity (WI) 授予權限
WI 透過 Google ServiceAccount (GSA) 與 Kubernetes ServiceAccount (KSA) 綁定:
- GSA 擁有存取 GSM 的權限。
- KSA impersonate GSA,讓使用該 KSA 的 Pod 繼承相同權限。
Terraform IaC 範例
# main.tf
resource "google_service_account" "sa" {
project = var.project_id
account_id = var.name
display_name = var.display_name
description = var.description
}
resource "google_project_iam_member" "roles" {
for_each = toset(var.project_roles)
project = var.project_id
role = each.value
member = google_service_account.sa.member
}
# Workload Identity 綁定:允許 K8s SA impersonate GSA
resource "google_service_account_iam_member" "workload_identity" {
service_account_id = google_service_account.sa.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[${var.k8s_namespace}/${var.k8s_sa_name}]"
}# terragrunt.hcl
project_roles = [
"roles/secretmanager.secretAccessor" # 視實際需求調整
]
k8s_namespace = "external-secrets" # 視實際部署調整
k8s_sa_name = "external-secret-external-secrets" # 視實際部署調整部署 External Secret Operator (ESO)
在 Helm values 中指定 KSA 對應的 GSA:
可以參考 ArtifactHub 與 Github
找到相關設定
# serviceAccount.yaml
serviceAccount:
create: true
annotations:
iam.gke.io/gcp-service-account: "${GSA-name}@${GCP-project-id}.iam.gserviceaccount.com"👉 接著建立 ClusterSecretStore 讓 ESO 知道 secret 來源:
會需要設置
- project: GCP project-id
- clusterLocation: GKE location
- clusterName: GKE cluster name
# ClusterSecretStore.yaml
{{- with .Values.createClusterSecretStore }}
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: {{ .name }}
labels:
project: {{ .project }}
spec:
provider:
gcpsm:
projectID: {{ .project }}
auth:
workloadIdentity:
clusterLocation: {{ .clusterLocation }}
clusterName: {{ .clusterName }}
serviceAccountRef:
name: external-secret-external-secrets
namespace: external-secrets
{{- end }}到這邊,應該可以看到上面這個 ClusterSecretStore 的狀態要是 True
# 範例
kubectl get clustersecretstores.external-secrets.io
NAME AGE STATUS CAPABILITIES READY
gcp-secret-manager 117m Valid ReadWrite True服務取得 GSM secret
服務端則是建立好 ExternalSecret 後,剩下的 dirty work ESO 會幫我們處理好
在 ExternalSecret 中會有幾項需要 define:
- refreshInterval: 更新時間
- secretStoreRef: 參考的類型(文章使用 ClusterSecretStore),以及建立時的名字
- target: 建立的 secret 名稱
- dataFrom: 資料來源(GSM 的 secret name)
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: {{ .name }}
namespace: {{ .namespace }}
spec:
# 這將「每1分鐘」刷新一次金鑰。可以根據自己的要求保留時間。
refreshInterval: {{ .refreshInterval }}
secretStoreRef:
name: {{ .secretStoreRef.name }}
kind: {{ .secretStoreRef.kind }}
target:
name: {{ .target.name }}
creationPolicy: {{ .target.creationPolicy }}
dataFrom:
- extract:
key: {{ .target.name }}後續 ESO 會透過上述設定,到 GSM 取值,並依照 ExternalSecret 的設定,到指定 ns 建立 secret
# 範例
kubectl -n cosparks describe secret kk
Name: kk
Namespace: ${namespace}
Labels: app.kubernetes.io/managed-by=Helm
reconcile.external-secrets.io/managed=true
Annotations: meta.helm.sh/release-name: ${server}
meta.helm.sh/release-namespace: ${namespace}
Type: Opaque
Data
====
kk: 12 bytes
ping: 4 bytes👉 並確認服務有吃到參數

const dotenv = require('dotenv');
dotenv.config();
app.get('/', (req, res) => {
res.send(`Hello CoSparks, ${process.env.kk}`);
});同時 GSM 若更新, ESO 會依照 refreshInterval
更新 K8s 內的 secret
後續服務只需要重啟,就可以吃到新後的 secret
📌 關鍵字:Google Secret Manager、External Secret Operator、GSM、ESO、Kubernetes、Workload Identity、GCP Secret 管理、自動同步 Secret