饮墨

子安饮墨馀三斗,留与卿儿作赋来

用 Vault + External Secrets Operator 实现 K8s 密钥零硬编码:3 步告别 Secret YAML 明文

痛点:密钥管理是 K8s 安全最大的短板

生产集群里跑着几十个微服务,数据库密码、API Token、TLS 证书散落在各个 Namespace 的 Secret 对象里。这些 Secret 本质上只是 Base64 编码——不是加密。一旦有人 kubectl get secret -o yaml,所有凭据一览无余。

更致命的问题:

  • Secret YAML 被提交到 Git,泄露面不可控
  • 密钥轮换需要逐个 Deployment 重启,漏一个就是事故
  • 审计困难——谁在什么时候读取了哪个密钥,没有任何记录

如果你的集群还在用 kubectl create secret generic 手动管理凭据,是时候引入集中式 Secrets 管理了。

方案:Vault 做密钥源,ESO 做同步桥梁

架构一句话概括:

HashiCorp Vault(密钥仓库)→ External Secrets Operator(同步控制器)→ Kubernetes Secret(Pod 消费)

Vault 负责存储、加密、审计、轮换;ESO 负责把 Vault 里的密钥自动同步为 K8s 原生 Secret 对象,Pod 无需任何代码改动即可消费。

核心优势:

维度 传统方式 Vault + ESO
存储安全 Base64 明文 AES-256-GCM 加密存储
轮换 手动改 YAML + 重启 Vault 自动轮换 + ESO 定时同步
审计 完整 Audit Log,精确到路径和时间
Git 安全 Secret 泄露风险高 Git 里只有 ExternalSecret CRD 引用

实操步骤

Step 1:部署 Vault 并启用 KV 引擎

用 Helm 部署 Vault 到 K8s:

# 添加 Helm repo
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

# 部署 Vault(开发模式仅用于测试,生产请用 HA 模式)
helm install vault hashicorp/vault \
  --namespace vault \
  --create-namespace \
  --set "server.dev.enabled=true"

# 等待 Pod Ready
kubectl -n vault wait --for=condition=Ready pod/vault-0 --timeout=120s

启用 KV v2 引擎并写入测试密钥:

# 进入 Vault Pod 执行
kubectl -n vault exec -it vault-0 -- sh

# 启用 KV 引擎
vault secrets enable -path=kv kv-v2

# 写入一组数据库凭据
vault kv put kv/prod/db-credentials \
  username="app_rw" \
  password="S3cUr3!P@ssw0rd#2026" \
  host="pg-primary.internal:5432"

# 验证读取
vault kv get kv/prod/db-credentials

Step 2:配置 Kubernetes Auth 并创建策略

让 ESO 能以 ServiceAccount 身份向 Vault 认证:

# 还是在 Vault Pod 内操作

# 启用 Kubernetes Auth
vault auth enable kubernetes

# 配置 K8s Auth(自动检测集群内信息)
vault write auth/kubernetes/config \
  kubernetes_host="https://${KUBERNETES_PORT_443_TCP_ADDR}:443"

# 创建读取策略
vault policy write eso-reader - <<EOF
path "kv/data/prod/*" {
  capabilities = ["read"]
}
path "kv/metadata/prod/*" {
  capabilities = ["read", "list"]
}
EOF

# 绑定角色到 ServiceAccount
vault write auth/kubernetes/role/eso-role \
  bound_service_account_names=external-secrets \
  bound_service_account_namespaces=external-secrets \
  policies=eso-reader \
  ttl=1h

Step 3:部署 ESO 并创建 ExternalSecret

安装 External Secrets Operator:

helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets external-secrets/external-secrets \
  --namespace external-secrets \
  --create-namespace \
  --set installCRDs=true

创建 ClusterSecretStore(集群级密钥源配置):

# cluster-secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "http://vault.vault.svc:8200"
      path: "kv"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "eso-role"
          serviceAccountRef:
            name: "external-secrets"
            namespace: "external-secrets"

创建 ExternalSecret(这个文件可以安全提交到 Git):

# external-secret-db.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: app-prod
spec:
  refreshInterval: 5m  # 每 5 分钟同步一次,密钥轮换自动生效
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-credentials        # 生成的 K8s Secret 名称
    creationPolicy: Owner
  data:
    - secretKey: DB_USERNAME
      remoteRef:
        key: prod/db-credentials
        property: username
    - secretKey: DB_PASSWORD
      remoteRef:
        key: prod/db-credentials
        property: password
    - secretKey: DB_HOST
      remoteRef:
        key: prod/db-credentials
        property: host

应用并验证:

kubectl apply -f cluster-secret-store.yaml
kubectl apply -f external-secret-db.yaml

# 检查同步状态
kubectl -n app-prod get externalsecret db-credentials
# 应显示 STATUS=SecretSynced

# 确认 K8s Secret 已自动生成
kubectl -n app-prod get secret db-credentials -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

Pod 直接通过 envFromvolumeMount 消费这个 Secret,零代码改动:

# deployment 片段
envFrom:
  - secretRef:
      name: db-credentials

避坑指南

1. 生产环境不要用 Dev 模式

Vault Dev 模式数据存内存,重启即丢失。生产部署必须: - 使用 Raft 或 Consul 作为存储后端 - 配置 Auto Unseal(推荐 AWS KMS / GCP Cloud KMS) - 至少 3 节点 HA

# 生产 Helm values 示例
helm install vault hashicorp/vault \
  --set "server.ha.enabled=true" \
  --set "server.ha.replicas=3" \
  --set "server.ha.raft.enabled=true"

2. refreshInterval 不要设太短

ESO 每次同步都会调用 Vault API。如果有 500 个 ExternalSecret 且 refreshInterval=30s,意味着每分钟 1000 次请求打到 Vault。建议:

  • 常规密钥:15m ~ 1h
  • 高频轮换的临时凭据(如 AWS STS):5m
  • 配合 Vault 的 max_lease_ttl 统一规划

3. RBAC 最小权限原则

不要给 ESO 的 ServiceAccount 赋予 Vault root policy。按 Namespace 划分路径:

kv/data/prod/*   → prod 集群 ESO
kv/data/staging/* → staging 集群 ESO

每个环境的 Role 只能读自己路径下的密钥,互不越权。

总结

步骤 动作 耗时
1 部署 Vault + 启用 KV 引擎 10 min
2 配置 K8s Auth + 策略 5 min
3 部署 ESO + 创建 ExternalSecret 5 min

全程 20 分钟即可跑通。之后团队只需关注两件事:

  1. 密钥写到 Vault — 通过 Vault CLI/API/UI,或 Terraform vault provider 自动化
  2. ExternalSecret CRD 提交到 Git — 安全、可审计、可回滚

从此 Git 仓库里再也不会出现明文密钥,密钥轮换不再需要逐个重启 Pod,所有访问都有审计日志。这才是云原生环境下密钥管理该有的样子。

您还没有登录,请登录后发表评论。