Kubernetes 内置的 Secret 资源虽然解决了配置与镜像解耦的问题,但本质上只是将数据以 base64 编码后写入 etcd。base64 是编码不是加密,任何人只要拿到 Secret 对象,一条 echo 命令即可还原明文。对于生产系统来说,这显然不够。外部密钥存储正是为了弥补这一短板:将敏感数据的权威来源放在集群之外,由专门的密钥管理系统负责加密、轮换、审计和访问控制。对接后,Pod 仍然可以通过原生方式使用 Secret 或文件挂载,而真正的密钥生命周期管理则交由外部系统完成。

目前主流的对接思路有两种:一是通过操作器把外部密钥同步成 Kubernetes 原生 Secret;二是通过 CSI 驱动把外部密钥直接挂载为 Pod 内的文件。两种方式各有优劣,下面会分别展开。
为什么需要外部密钥存储
原生 Secret 的最大问题是缺乏真正的加密保护。数据写入 etcd 时默认是明文存储,即便开启 etcd 的静态加密,也只是加密了 etcd 层的数据文件,对能够读取 Secret 的 RBAC 授权并无任何影响。只要一个用户拥有 get secrets 权限,他就能拿到 base64 字符串并轻易解码。这一点常常被新手忽略,很多人以为 base64 就是加密,实际它只是一种可逆的编码方式。
另一个痛点是多集群管理。当应用部署在多个 Kubernetes 集群中时,如果每个集群都手工维护一套 Secret,不仅操作繁琐,而且无法保证密钥版本一致。外部密钥存储可以作为唯一的凭据源,各个集群通过控制器按需同步,管理员只需要在外部系统中维护一份数据。这样既减少了人为复制粘贴带来的泄露风险,也方便统一执行轮换策略。
外部密钥存储还提供了完善的审计日志、动态密钥生成、租户隔离等企业级能力。例如 HashiCorp Vault 可以基于 Kubernetes ServiceAccount 进行身份认证,并在每次读取时生成短暂有效的动态数据库账号;云厂商的 Secret Manager 则能与 IAM 深度集成,实现细粒度的访问控制。这些能力都是原生 Secret 无法做到的。
主流对接方案对比
第一类方案以 External Secrets Operator 为代表,简称 ESO。它运行在集群内部,通过自定义资源 SecretStore 和 ExternalSecret 描述外部密钥源和同步目标。控制器会定期从外部系统拉取密钥数据,并在集群中创建或更新对应的原生 Secret。这种方式的优点是应用完全无感知,仍然通过环境变量或挂载原生 Secret 获取密钥,迁移成本极低。缺点是同步存在一定延迟,且原生 Secret 依然会写入 etcd,安全性提升有限,只是把密钥生命周期管理交给了外部系统。
第二类方案是 CSI Secrets Store Driver。它利用 Kubernetes CSI 卷机制,将外部密钥直接挂载为 Pod 内的文件。应用读取 /mnt/secrets 路径下的文件即可获得敏感数据,整个过程不会在 etcd 中持久化明文。这种方式的安全性更高,因为密钥只在目标 Pod 的内存和挂载点中存在,且可以配合 SecretProviderClass 的 secretObjects 字段按需创建原生 Secret。它的缺点是对应用有侵入性,应用需要改为读取文件,而且 CSI 驱动本身需要安装在每个节点上。
| 维度 | External Secrets Operator | CSI Secrets Store Driver |
|---|---|---|
| 同步对象 | 原生 Secret | Pod 内文件或原生 Secret |
| 应用改动 | 无,继续使用 Secret | 需改为读取挂载文件 |
| 数据是否写入 etcd | 是 | 默认否,可选创建 Secret |
| 同步延迟 | 取决于 refreshInterval | 读取时实时获取 |
| 多集群支持 | 天然支持 | 天然支持 |
| 运维复杂度 | 较低 | 偏高,需管理 CSI 驱动 |
从实际经验来看,如果团队已经在使用 Vault 或云厂商 Secret Manager,且应用已经通过原生 Secret 方式管理配置,那么 ESO 是落地成本最低的选择。如果对安全要求极高,不能容忍明文 Secret 出现在 etcd 中,则应优先考虑 CSI Secrets Store Driver。
使用 External Secrets Operator 对接 Vault
先安装 ESO。可以使用 Helm 快速部署,默认会创建独立的命名空间和必要的 RBAC 资源。
helm repo add external-secrets https://charts.external-secrets.io helm repo update helm install external-secrets external-secrets/external-secrets \ -n external-secrets --create-namespace
安装完成后,需要先配置 SecretStore 资源,告诉 ESO 外部密钥源在哪里以及如何认证。下面的示例连接到一个 Vault 实例,使用 Kubernetes 认证方式,让 ESO 以指定 ServiceAccount 的身份向 Vault 发起请求。这里要注意 Vault 的 path 字段,如果使用 KV v2 引擎,路径中需要包含 data 前缀,这是最容易写错的地方。
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: default
spec:
provider:
vault:
server: "https://vault.ipipp.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "demo-role"
serviceAccountRef:
name: "external-secrets"
接下来创建 ExternalSecret 资源,定义需要同步哪些键值对。下面的示例将 Vault 中 secret/data/demo/app 路径下的两个属性同步为集群内的一个原生 Secret,并设置每小时刷新一次。如果外部密钥发生轮换,ESO 会自动更新目标 Secret。
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: default
spec:
refreshInterval: "1h"
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: db-password
remoteRef:
key: secret/data/demo/app
property: password
- secretKey: api-key
remoteRef:
key: secret/data/demo/app
property: api_key
应用这段配置后,可以通过 kubectl get externalsecret 查看状态,STATUS 列显示 SecretSynced 即代表同步成功。如果状态异常,检查 Vault 的认证角色、ServiceAccount 注释以及 SecretStore 中 server 地址是否与集群内可访问的地址一致。ESO 支持多种外部提供方,包括 AWS Secrets Manager、GCP Secret Manager、Azure Key Vault、GitLab 等,配置模式完全一致,只需替换 provider 段即可。
使用 CSI Secrets Store Driver 挂载外部密钥
CSI Secrets Store Driver 的部署相对复杂一些,需要为集群中的每个节点安装驱动,并且根据不同的外部密钥系统安装对应的 provider 插件。例如对接 Vault 需要安装 secrets-store-csi-driver-provider-vault,对接 AWS 则需要安装 AWS provider。通常使用 Helm 或官方 manifest 进行部署,部署完成后会在每个节点上注册一个 CSI 驱动。
先在目标命名空间中创建 SecretProviderClass,它定义了外部密钥源以及要挂载哪些对象。下面仍以 Vault 为例,同时通过 secretObjects 指定同步为一个原生 Secret,方便那些无法改动的旧应用继续使用环境变量注入。
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-db-creds
namespace: default
spec:
provider: vault
parameters:
vaultAddress: "https://vault.ipipp.com"
roleName: "app-role"
objects: |
- objectName: "db-password"
secretPath: "secret/data/demo/app"
secretKey: "password"
secretObjects:
- secretName: db-secret
type: Opaque
data:
- objectName: db-password
key: password
创建 Pod 时,需要声明一个 CSI 卷,并在 volumeAttributes 中指定 secretProviderClass 的名称。容器挂载该卷后,就能在指定路径下读取到密钥文件。下方示例中,Pod 启动后会在 /mnt/secrets/db-password 文件中看到密码内容。
apiVersion: v1
kind: Pod
metadata:
name: app
namespace: default
spec:
serviceAccountName: app-sa
containers:
- name: app
image: nginx
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets"
readOnly: true
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "vault-db-creds"
CSI 方案的最大优势是密钥不会默认写入 etcd。Pod 启动时,kubelet 调用 CSI 驱动从外部系统实时获取密钥并挂载为文件;Pod 删除后,挂载内容随之消失。如果同时配置了 secretObjects,驱动会在密钥获取成功后创建或更新对应的原生 Secret,但这个 Secret 的内容仍然来自外部系统,而非手工维护。需要注意的是,这种方式下外部密钥的轮换可能需要 Pod 重启才能重新触发获取,除非应用支持文件监听或使用 CSI 驱动的自动轮换特性。
安全加固与常见问题
无论选择哪种方案,权限最小化都是第一原则。ESO 使用的 ServiceAccount 应当只授予等待同步的命名空间中的 Secret 管理权限,不应使用 cluster-admin。在 Vault 侧,为每个集群或命名空间创建独立的 role 和 policy,限制其只能读取特定路径。对于 CSI 驱动,Pod 的 ServiceAccount 也要经过外部系统认证,必须确保只有目标工作负载才能通过认证获取密钥。
另一个常见问题是同步延迟。ESO 默认刷新间隔可能较长,如果外部密钥刚刚轮换,集群内 Secret 仍保留旧值。生产环境建议将 refreshInterval 设置在 5 到 15 分钟之间,同时结合外部系统的通知机制触发即时同步。对于 CSI 方案,由于读取发生在 Pod 创建阶段,轮换后需要滚动重启相关工作负载。团队可以借助 Reloader 等工具自动检测 Secret 变化并触发滚动更新,避免人工遗漏。
最后要强调日志和审计。外部密钥存储本身带有完整的访问日志,但集群内部的同步控制器同样会产生大量操作记录。建议将 ESO 或 CSI provider 的日志接入集中式日志系统,并设置告警,当同步失败、Secret 冲突或权限异常时能第一时间发现。对于不再使用的 Secret,应及时清理,避免残留的敏感数据长期无人问津。
Kubernetes外部密钥存储Secret管理修改时间:2026-09-24 02:57:42