前言
在現代雲原生架構中,機密資料(Secrets)的管理是一個關鍵的安全議題。雖然 Kubernetes 原生提供了 Secret 資源來儲存敏感資訊,但這些 Secret 僅以 Base64 編碼儲存,並非真正的加密。此外,在多叢集環境中同步和管理 Secret 也是一大挑戰。
External Secrets Operator (ESO) 是一個 Kubernetes Operator,它可以將外部機密管理系統(如 AWS Secrets Manager、HashiCorp Vault、Azure Key Vault 等)中的機密資料同步到 Kubernetes Secret。這讓我們能夠利用專業的機密管理平台來集中管理敏感資訊,同時保持 Kubernetes 應用程式的相容性。
External Secrets Operator 概述
核心概念
External Secrets Operator 提供了以下核心自訂資源(Custom Resources):
| 資源類型 | 說明 |
|---|
SecretStore | 定義如何連接到外部機密管理系統(Namespace 範圍) |
ClusterSecretStore | 定義如何連接到外部機密管理系統(叢集範圍) |
ExternalSecret | 定義要從外部系統同步哪些機密資料 |
ClusterExternalSecret | 跨多個 Namespace 建立 ExternalSecret |
PushSecret | 將 Kubernetes Secret 推送到外部系統(反向同步) |
工作流程
1
2
3
4
5
| ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ External Secret │ │ External Secrets │ │ Kubernetes Secret │
│ Provider │ ──▶ │ Operator │ ──▶ │ │
│ (Vault/AWS/Azure) │ │ │ │ │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
- 管理員在 SecretStore 中設定外部機密系統的連線資訊
- 開發者建立 ExternalSecret 定義需要同步的機密
- ESO 定期從外部系統取得機密資料
- ESO 自動建立或更新對應的 Kubernetes Secret
支援的後端系統
External Secrets Operator 支援多種機密管理後端:
- AWS Secrets Manager
- AWS Systems Manager Parameter Store
- HashiCorp Vault
- Azure Key Vault
- Google Cloud Secret Manager
- IBM Cloud Secrets Manager
- Kubernetes Secret(用於叢集間同步)
- CyberArk Conjur
- 1Password
- Doppler
- Keeper Security
安裝與設定
使用 Helm 安裝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 新增 External Secrets Operator Helm 倉庫
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
# 建立專用的 Namespace
kubectl create namespace external-secrets
# 安裝 External Secrets Operator
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--set installCRDs=true \
--set webhook.port=9443
# 驗證安裝
kubectl get pods -n external-secrets
|
進階安裝選項
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 使用自訂配置安裝
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--set installCRDs=true \
--set replicaCount=2 \
--set leaderElect=true \
--set serviceMonitor.enabled=true \
--set webhook.certManager.enabled=true \
--set resources.requests.cpu=100m \
--set resources.requests.memory=128Mi \
--set resources.limits.cpu=500m \
--set resources.limits.memory=512Mi
|
驗證安裝狀態
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 確認所有 CRD 已安裝
kubectl get crd | grep external-secrets
# 預期輸出:
# clusterexternalsecrets.external-secrets.io
# clustersecretstores.external-secrets.io
# externalsecrets.external-secrets.io
# secretstores.external-secrets.io
# pushsecrets.external-secrets.io
# 確認 Operator Pod 正常運行
kubectl get pods -n external-secrets
# 查看 Operator 日誌
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets
|
SecretStore 與 ClusterSecretStore
SecretStore(Namespace 範圍)
SecretStore 定義了如何連接到外部機密管理系統,它僅在特定 Namespace 內有效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets"
serviceAccountRef:
name: "vault-auth"
|
ClusterSecretStore(叢集範圍)
ClusterSecretStore 可被所有 Namespace 中的 ExternalSecret 引用,適合用於共用的機密來源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: global-vault-store
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
namespace: "admin" # Vault Enterprise namespace
auth:
tokenSecretRef:
name: "vault-token"
namespace: "external-secrets"
key: "token"
|
驗證 SecretStore 狀態
1
2
3
4
5
6
7
8
| # 查看 SecretStore 狀態
kubectl get secretstore -n production
# 查看 ClusterSecretStore 狀態
kubectl get clustersecretstore
# 詳細狀態資訊
kubectl describe secretstore vault-backend -n production
|
正常運作的 SecretStore 應顯示 Valid 狀態:
1
2
3
4
5
6
7
| Status:
Conditions:
Last Transition Time: 2025-04-29T10:00:00Z
Message: store validated
Reason: Valid
Status: True
Type: Ready
|
AWS Secrets Manager 整合
IAM 權限設定
首先建立具有適當權限的 IAM Policy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecrets"
],
"Resource": [
"arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:production/*"
]
}
]
}
|
使用 IRSA(IAM Roles for Service Accounts)
這是在 EKS 上推薦的認證方式:
1
2
3
4
5
6
7
| # 建立 IRSA 關聯的 Service Account
eksctl create iamserviceaccount \
--cluster=my-cluster \
--namespace=external-secrets \
--name=external-secrets-sa \
--attach-policy-arn=arn:aws:iam::123456789012:policy/ExternalSecretsPolicy \
--approve
|
設定 ClusterSecretStore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
namespace: external-secrets
|
使用 Access Key 認證(替代方案)
如果無法使用 IRSA,可以使用 Access Key:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # 建立包含 AWS 認證的 Secret
apiVersion: v1
kind: Secret
metadata:
name: aws-credentials
namespace: external-secrets
type: Opaque
stringData:
access-key-id: "AKIAIOSFODNN7EXAMPLE"
secret-access-key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
namespace: external-secrets
key: access-key-id
secretAccessKeySecretRef:
name: aws-credentials
namespace: external-secrets
key: secret-access-key
|
ExternalSecret 範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-secret
creationPolicy: Owner
template:
type: Opaque
data:
# 使用 Go template 格式化
connection-string: "postgresql://{{ .username }}:{{ .password }}@db.example.com:5432/mydb"
data:
- secretKey: username
remoteRef:
key: production/database
property: username
- secretKey: password
remoteRef:
key: production/database
property: password
|
HashiCorp Vault 整合
Vault 設定準備
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 啟用 Kubernetes 認證方法
vault auth enable kubernetes
# 設定 Kubernetes 認證
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc:443" \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# 建立 Policy
vault policy write external-secrets - <<EOF
path "secret/data/*" {
capabilities = ["read", "list"]
}
path "secret/metadata/*" {
capabilities = ["read", "list"]
}
EOF
# 建立 Role
vault write auth/kubernetes/role/external-secrets \
bound_service_account_names=external-secrets-vault \
bound_service_account_namespaces=external-secrets \
policies=external-secrets \
ttl=24h
|
Kubernetes ServiceAccount
1
2
3
4
5
| apiVersion: v1
kind: ServiceAccount
metadata:
name: external-secrets-vault
namespace: external-secrets
|
ClusterSecretStore 設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
# 可選:跳過 TLS 驗證(僅用於測試)
# caProvider:
# type: "Secret"
# name: "vault-ca"
# namespace: "external-secrets"
# key: "ca.crt"
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets"
serviceAccountRef:
name: "external-secrets-vault"
namespace: "external-secrets"
|
使用 AppRole 認證
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-approle
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
auth:
appRole:
path: "approle"
roleId: "db02de05-fa39-4855-059b-67221c5c2f63"
secretRef:
name: "vault-approle-secret"
namespace: "external-secrets"
key: "secret-id"
|
ExternalSecret 範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: vault-secret
namespace: production
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
# 取得單一屬性
- secretKey: api-key
remoteRef:
key: apps/myapp/config
property: api_key
# 取得整個 Secret
- secretKey: config
remoteRef:
key: apps/myapp/config
# 或使用 dataFrom 取得所有欄位
dataFrom:
- extract:
key: apps/myapp/credentials
|
Azure Key Vault 整合
Azure 設定準備
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 建立 Service Principal
az ad sp create-for-rbac --name external-secrets-sp --skip-assignment
# 授予 Key Vault 存取權限
az keyvault set-policy \
--name my-keyvault \
--spn <app-id> \
--secret-permissions get list
# 或使用 Azure RBAC
az role assignment create \
--role "Key Vault Secrets User" \
--assignee <app-id> \
--scope /subscriptions/<subscription-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>
|
使用 Service Principal 認證
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # 建立包含 Azure 認證的 Secret
apiVersion: v1
kind: Secret
metadata:
name: azure-sp-credentials
namespace: external-secrets
type: Opaque
stringData:
client-id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client-secret: "your-client-secret"
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: azure-keyvault
spec:
provider:
azurekv:
tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
vaultUrl: "https://my-keyvault.vault.azure.net"
authSecretRef:
clientId:
name: azure-sp-credentials
namespace: external-secrets
key: client-id
clientSecret:
name: azure-sp-credentials
namespace: external-secrets
key: client-secret
|
使用 Workload Identity(AKS 推薦方式)
1
2
3
4
5
6
7
8
9
10
11
12
13
| apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: azure-keyvault-wi
spec:
provider:
azurekv:
tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
vaultUrl: "https://my-keyvault.vault.azure.net"
authType: WorkloadIdentity
serviceAccountRef:
name: external-secrets-sa
namespace: external-secrets
|
ExternalSecret 範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: azure-secret
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: azure-keyvault
kind: ClusterSecretStore
target:
name: app-credentials
creationPolicy: Owner
data:
- secretKey: database-password
remoteRef:
key: database-password
- secretKey: api-key
remoteRef:
key: api-key
# 取得特定版本
- secretKey: old-password
remoteRef:
key: database-password
version: "abc123def456"
|
ExternalSecret 資源定義
基本結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-external-secret
namespace: default
spec:
# 同步間隔
refreshInterval: 1h
# 參照的 SecretStore
secretStoreRef:
name: my-secret-store
kind: SecretStore # 或 ClusterSecretStore
# 目標 Kubernetes Secret 設定
target:
name: my-secret
creationPolicy: Owner
deletionPolicy: Retain
template:
type: kubernetes.io/tls
metadata:
labels:
app: myapp
annotations:
description: "Synced from external store"
data:
tls.crt: "{{ .certificate }}"
tls.key: "{{ .private_key }}"
# 資料映射
data:
- secretKey: certificate
remoteRef:
key: path/to/cert
property: cert
- secretKey: private_key
remoteRef:
key: path/to/cert
property: key
# 或使用 dataFrom 批量取得
dataFrom:
- extract:
key: path/to/secrets
- find:
name:
regexp: "^app-.*"
|
creationPolicy 選項
| Policy | 說明 |
|---|
Owner | ESO 建立並擁有 Secret,ExternalSecret 刪除時 Secret 也會被刪除 |
Orphan | ESO 建立 Secret,但 ExternalSecret 刪除時 Secret 會保留 |
Merge | 合併到現有 Secret,不會覆蓋其他欄位 |
None | 不建立 Secret,僅更新現有 Secret |
deletionPolicy 選項
| Policy | 說明 |
|---|
Delete | ExternalSecret 刪除時,刪除目標 Secret |
Merge | ExternalSecret 刪除時,僅移除由 ESO 管理的欄位 |
Retain | ExternalSecret 刪除時,保留目標 Secret |
使用 Template 轉換資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: templated-secret
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: database-config
template:
engineVersion: v2
data:
# 使用 Go template 語法
config.yaml: |
database:
host: {{ .db_host }}
port: {{ .db_port | default "5432" }}
username: {{ .db_user }}
password: {{ .db_pass }}
ssl_mode: {{ .ssl_mode | default "require" }}
# 條件判斷
connection-string: |
{{- if eq .db_type "postgres" -}}
postgresql://{{ .db_user }}:{{ .db_pass }}@{{ .db_host }}:{{ .db_port }}/{{ .db_name }}
{{- else if eq .db_type "mysql" -}}
mysql://{{ .db_user }}:{{ .db_pass }}@{{ .db_host }}:{{ .db_port }}/{{ .db_name }}
{{- end -}}
data:
- secretKey: db_host
remoteRef:
key: database/config
property: host
- secretKey: db_port
remoteRef:
key: database/config
property: port
- secretKey: db_user
remoteRef:
key: database/credentials
property: username
- secretKey: db_pass
remoteRef:
key: database/credentials
property: password
- secretKey: db_type
remoteRef:
key: database/config
property: type
- secretKey: db_name
remoteRef:
key: database/config
property: name
|
使用 dataFrom 批量同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: bulk-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: all-app-secrets
dataFrom:
# 取得單一路徑下的所有 key
- extract:
key: apps/myapp/config
# 使用正則表達式搜尋多個 Secret
- find:
path: apps/myapp
name:
regexp: "^(db|cache|api)-.*"
# 使用標籤過濾
- find:
tags:
environment: production
team: backend
|
ClusterExternalSecret(跨 Namespace)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| apiVersion: external-secrets.io/v1beta1
kind: ClusterExternalSecret
metadata:
name: shared-tls-cert
spec:
# 選擇目標 Namespace
namespaceSelector:
matchLabels:
needs-tls: "true"
# 或使用 matchExpressions
# namespaceSelector:
# matchExpressions:
# - key: environment
# operator: In
# values: ["staging", "production"]
# ExternalSecret 模板
externalSecretSpec:
refreshInterval: 24h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: wildcard-tls
creationPolicy: Owner
template:
type: kubernetes.io/tls
data:
- secretKey: tls.crt
remoteRef:
key: certificates/wildcard
property: certificate
- secretKey: tls.key
remoteRef:
key: certificates/wildcard
property: private_key
|
自動輪換與同步策略
設定 refreshInterval
refreshInterval 決定 ESO 多久從外部系統同步一次機密資料:
1
2
3
4
5
6
7
8
| apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: high-frequency-sync
spec:
# 每 5 分鐘同步一次
refreshInterval: 5m
# ...
|
常見的設定建議:
| 使用情境 | 建議間隔 |
|---|
| 開發/測試環境 | 1m - 5m |
| 一般生產環境 | 15m - 1h |
| 變動頻率低的機密 | 1h - 24h |
| 憑證(接近到期時) | 5m - 15m |
強制立即同步
1
2
3
4
5
6
7
| # 透過重新套用 ExternalSecret 觸發同步
kubectl annotate externalsecret my-secret \
force-sync=$(date +%s) --overwrite
# 或刪除並重建
kubectl delete externalsecret my-secret
kubectl apply -f externalsecret.yaml
|
監控同步狀態
1
2
3
4
5
6
7
8
| # 查看 ExternalSecret 狀態
kubectl get externalsecret -A
# 詳細狀態資訊
kubectl describe externalsecret my-secret
# 查看事件
kubectl get events --field-selector involvedObject.name=my-secret
|
正常同步的狀態:
1
2
3
4
5
6
7
8
9
10
11
| Status:
Binding:
Name: my-secret
Conditions:
- Last Transition Time: 2025-04-29T10:00:00Z
Message: Secret was synced
Reason: SecretSynced
Status: "True"
Type: Ready
Refresh Time: 2025-04-29T10:00:00Z
Synced Resource Version: 1-abc123
|
處理同步失敗
設定重試策略:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: resilient-secret
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: app-secret
creationPolicy: Owner
# 資料取得失敗時的處理策略
dataFrom:
- extract:
key: apps/config
conversionStrategy: Default
decodingStrategy: None
|
搭配 Reloader 自動重啟應用
當 Secret 更新時,應用程式需要重新載入配置。可以使用 Reloader 自動化這個過程:
1
2
3
| # 安裝 Reloader
helm repo add stakater https://stakater.github.io/stakater-charts
helm install reloader stakater/reloader -n kube-system
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
annotations:
# Reloader 會監控此 Secret 的變更
reloader.stakater.com/auto: "true"
# 或指定特定 Secret
# secret.reloader.stakater.com/reload: "my-secret"
spec:
template:
spec:
containers:
- name: app
envFrom:
- secretRef:
name: my-secret
|
進階輪換策略
結合外部系統的機密輪換功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # AWS Secrets Manager 自動輪換設定
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: rotating-db-creds
spec:
# 配合 AWS 輪換週期
refreshInterval: 4h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
template:
data:
# AWS Secrets Manager 會在輪換期間提供 AWSCURRENT 和 AWSPREVIOUS
current-password: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: production/database
property: password
# 使用 version stage 取得目前版本
version: AWSCURRENT
|
PushSecret(反向同步)
將 Kubernetes Secret 推送到外部系統:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| apiVersion: external-secrets.io/v1beta1
kind: PushSecret
metadata:
name: push-to-vault
namespace: production
spec:
# 同步間隔
refreshInterval: 10s
# 來源 Secret
selector:
secret:
name: generated-cert
# 目標 SecretStore
secretStoreRefs:
- name: vault-backend
kind: ClusterSecretStore
# 推送設定
data:
- match:
secretKey: tls.crt
remoteRef:
remoteKey: certificates/app-cert
property: certificate
- match:
secretKey: tls.key
remoteRef:
remoteKey: certificates/app-cert
property: private_key
|
最佳實務
1. 安全性考量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # 使用 RBAC 限制 SecretStore 存取
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: external-secrets-user
namespace: production
rules:
- apiGroups: ["external-secrets.io"]
resources: ["externalsecrets"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: ["external-secrets.io"]
resources: ["secretstores"]
verbs: ["get", "list"] # 唯讀
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: external-secrets-user-binding
namespace: production
subjects:
- kind: ServiceAccount
name: developer-sa
namespace: production
roleRef:
kind: Role
name: external-secrets-user
apiGroup: rbac.authorization.k8s.io
|
2. 使用 ClusterSecretStore 共享配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 管理員維護的 ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: production-vault
spec:
# 限制可使用此 Store 的 Namespace
conditions:
- namespaces:
- production
- staging
provider:
vault:
server: "https://vault.example.com:8200"
# ...
|
3. 監控與告警
1
2
3
4
5
6
7
8
9
10
11
12
| # ServiceMonitor for Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: external-secrets
namespace: external-secrets
spec:
selector:
matchLabels:
app.kubernetes.io/name: external-secrets
endpoints:
- port: metrics
|
關鍵指標:
externalsecret_sync_calls_total - 同步呼叫次數externalsecret_sync_calls_error - 同步錯誤次數externalsecret_status_condition - 目前狀態
4. 災難復原
1
2
3
4
5
| # 匯出所有 ExternalSecret 定義
kubectl get externalsecrets -A -o yaml > externalsecrets-backup.yaml
# 匯出 ClusterSecretStore 定義(不含敏感認證)
kubectl get clustersecretstores -o yaml > clustersecretstores-backup.yaml
|
5. 版本控制 ExternalSecret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 使用 GitOps 管理 ExternalSecret
# externalsecrets/production/database.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: production
labels:
app.kubernetes.io/managed-by: argocd
environment: production
spec:
refreshInterval: 1h
secretStoreRef:
name: production-vault
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: production/database
property: password
|
常見問題排解
SecretStore 連線失敗
1
2
3
4
5
6
7
| # 檢查 SecretStore 狀態
kubectl describe secretstore my-store
# 常見錯誤:
# - 網路連線問題
# - 認證失敗
# - 權限不足
|
ExternalSecret 同步失敗
1
2
3
4
5
6
7
8
9
10
| # 查看詳細錯誤訊息
kubectl describe externalsecret my-secret
# 查看 Operator 日誌
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets -f
# 常見錯誤:
# - Secret 路徑不存在
# - 欄位名稱錯誤
# - 權限不足
|
除錯模式
1
2
3
4
| # 啟用詳細日誌
helm upgrade external-secrets external-secrets/external-secrets \
-n external-secrets \
--set extraArgs[0]="--loglevel=debug"
|
總結
本文介紹了 Kubernetes External Secrets Operator 的完整使用方式:
- 概述:ESO 可將外部機密管理系統的機密同步到 Kubernetes Secret
- 安裝:使用 Helm 進行安裝和配置
- SecretStore:設定與外部系統的連線方式
- 雲端整合:AWS Secrets Manager、HashiCorp Vault、Azure Key Vault 的詳細設定
- ExternalSecret:定義機密同步規則和轉換邏輯
- 自動輪換:設定同步策略和應用程式重載
External Secrets Operator 提供了一個優雅的方式來整合企業級機密管理系統與 Kubernetes,實現機密的集中管理、自動同步和安全存取。建議根據組織需求選擇適合的機密管理後端,並遵循最佳實務來確保系統安全性。
參考資源