用 Google Secret Manager 與 External Secret Operator 管理機敏資料
用 Google Secret Manager 與 External Secret Operator 管理機敏資料
TL;DR
- Google Secret Manager(GSM) 集中管理機敏資料
- Workload Identity(WI) 的方式賦予 External Secret Operator(ESO) 訪問 GSM 的權限
- ESO 同步 GKE & GSM 的 secret
背景
有些服務會使用到環境變數 .env
以 kind: secret 的方式部署,在管理上不便
GSM 機敏資料建立
在 Secret value 的部分,是以 json 的格式存放 key-value

WI 授予權限
WI 是透過 Google ServiceAccount(GSA) 與 Kubernetes ServiceAccount(KSA) 的綁定,讓 KSA 擁有 GSA 的權限同時,間接授予擁有該 KSA 的服務享有相同權限
👉 先賦予 GSA 權限同時,綁定 KSA 這邊是透過 IaC(terragrunt) 的方式,建立 GSA 賦予權限,並且綁定 KSA
# 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" # 視實際部署調整ESO 部署
這邊要指明 KSA 所對應的 GSA 權限來源
可以參考 ArtifactHub 與 Github 找到怎麼設定
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
CI/CD 流程優化案例:縮短 Build 時間與 Image Pull 加速
本文分享如何優化公司內部自建 runner 的 CI/CD 流程,從 image pull rate limit 問題切入,將大型容器鏡像從 GitHub Container Registry 轉移至 GCP GAR,成功將拉取時間從數十分鐘縮短到 3 分鐘,並分析後續 build-script cache 的可能性。
用 Google Secret Manager 搭配 External Secret Operator 管理 GKE 機敏資料
本文介紹如何在 GKE 環境使用 Google Secret Manager (GSM) 集中管理機敏資料,並透過 Workload Identity (WI) 與 External Secret Operator (ESO) 實現 K8s Secret 自動同步,附上 Terraform 與 YAML 實作範例。