HashiCorp密钥管理平台,在Kubernetes中安全下发与轮换敏感数据
Vault 是 HashiCorp 的密钥管理平台,把「谁在什么条件下能拿到哪份密钥」集中管理,并提供租期(lease)、动态凭据与完整的审计日志。
它与 secret 页讲的原生 Secret 是互补关系:原生 Secret 解决「集群内怎么用密钥」,Vault 解决「密钥从哪来、谁能取、多久换一次」。
在 Kubernetes 里,Vault 更常见的角色不是「存密钥」而是「发密钥」:
静态场景 业务把数据库密码存进 Vault,Pod 通过注解或 CRD 拉取
动态场景 Vault 按需向数据库申请一个临时账号,租期到期自动注销
两个必须先了解的项目背景:
许可 2023 年 8 月起从 MPL 2.0 改为 BSL 1.1,社区分支是 OpenBao(MPL 2.0)
归属 HashiCorp 已被 IBM 收购(2025 年 2 月完成),主版本号从 1.21 直接跳到 2.0
另外,托管服务 HCP Vault Secrets 已于 2026 年 7 月 1 日停止服务;自建或自管的 Vault 不受影响。
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# 生产推荐:HA + Raft 存储,不依赖 Consul
helm install vault hashicorp/vault -n vault --create-namespace \
--set='server.ha.enabled=true' \
--set='server.ha.raft.enabled=true'
# 单机模式(chart 默认,file 存储,不适合生产)
helm install vault hashicorp/vault -n vault --create-namespace
# 开发模式:自动初始化并解封,内存存储,重启即丢
helm install vault hashicorp/vault -n vault --create-namespace \
--set='server.dev.enabled=true'
Vault 启动后默认处于 Sealed(已封存) 状态,不完成 unseal 就无法读写任何密钥。这是自建 Vault 最容易漏掉的一步,表现为 Pod 明明 Running,但所有操作都报 Vault is sealed。
# 查看状态,Sealed 为 true 时不可用
kubectl exec -n vault vault-0 -- vault status
# 初始化:生成 5 个 unseal key 与 1 个 root token
kubectl exec -n vault vault-0 -- vault operator init
# 用其中 3 个 key 解封(默认阈值 3/5)
kubectl exec -n vault vault-0 -- vault operator unseal <key-1>
kubectl exec -n vault vault-0 -- vault operator unseal <key-2>
kubectl exec -n vault vault-0 -- vault operator unseal <key-3>
# 多副本时,每个 Pod 都要各自解封
kubectl exec -n vault vault-1 -- vault operator unseal <key-1>
kubectl exec -n vault vault-1 -- vault operator unseal <key-2>
kubectl exec -n vault vault-1 -- vault operator unseal <key-3>
init 输出的 unseal key 与 root token 只显示一次,必须当场离线保存。root token 权限过大,建议只用于初始配置,之后立即吊销,日常操作改用带具体策略的令牌。
手工 unseal 在生产里不可接受 —— 任何一次 Pod 重启都会让 Vault 回到封存状态。生产环境应配置自动解封,用云厂商 KMS 或另一套 Vault 的 Transit 引擎托管master key;Helm chart 里对应 server.seal.<类型> 一组值(如 awskms、azurekeyvault、gcpckms)。
让 Pod 用 ServiceAccount 换取 Vault 令牌,避免在集群里存长期凭据:
# 启用 Kubernetes 认证
kubectl exec -n vault vault-0 -- vault auth enable kubernetes
# 告诉 Vault 如何校验 ServiceAccount 令牌
kubectl exec -n vault vault-0 -- vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc:443"
# 写一条只读策略(policy.hcl 从标准输入传入)
kubectl exec -i -n vault vault-0 -- vault policy write app-ro - < policy.hcl
# 绑定 ServiceAccount 与策略
kubectl exec -n vault vault-0 -- vault write auth/kubernetes/role/app \
bound_service_account_names=app \
bound_service_account_namespaces=default \
policies=app-ro \
ttl=1h
策略文件 policy.hcl:
path "secret/data/app/*" {
capabilities = ["read"]
}
kubectl exec -n vault vault-0 -- vault kv put secret/app/config \
username=admin password='S3cr3t!'
kubectl exec -n vault vault-0 -- vault kv get secret/app/config
kubectl exec -n vault vault-0 -- vault kv metadata get secret/app/config
kubectl exec -n vault vault-0 -- vault kv list secret/app
# 删除最新版本(可恢复)
kubectl exec -n vault vault-0 -- vault kv delete secret/app/config
# 彻底销毁所有版本与元数据
kubectl exec -n vault vault-0 -- vault kv destroy -versions=1 secret/app/config
kubectl exec -n vault vault-0 -- vault kv metadata delete secret/app/config
注意 KV v2 的路径在 API 与策略里要多一层 data:secret/data/app/config,而 CLI 里写的是 secret/app/config。
| 方式 | 原理 | 特点 |
|---|---|---|
| Agent Injector | 准入 webhook 注入 sidecar,把密钥写进共享内存卷 | 支持模板渲染,密钥不落 etcd,但每个 Pod 多一个容器 |
| Secrets Store CSI | CSI 驱动把密钥挂载成文件 | 与 CSI 生态统一,密钥不落 etcd |
| Vault Secrets Operator | 控制器把密钥同步成原生 Secret | 无 sidecar、GitOps 友好,但密钥会进 etcd |
Agent Injector 用注解声明:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "app"
vault.hashicorp.com/agent-inject-secret-config.env: "secret/data/app/config"
vault.hashicorp.com/agent-inject-template-config.env: |
{{- with secret "secret/data/app/config" -}}
DB_USER={{ .Data.data.username }}
DB_PASS={{ .Data.data.password }}
{{- end }}
spec:
serviceAccountName: app
containers:
- name: app
image: example/app:1.0.0
command: ["sh", "-c", "source /vault/secrets/config.env && exec ./app"]
密钥被写到 /vault/secrets/ 下的 tmpfs 共享内存卷,不会落到 etcd。
Secrets Store CSI Driver:
# 1. 安装 CSI 驱动
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver \
-n kube-system
# 2. Vault Helm chart 内置了 CSI Provider,打开开关即可
helm upgrade vault hashicorp/vault -n vault --set='csi.enabled=true'
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-db
spec:
provider: vault
parameters:
vaultAddress: "http://vault.vault.svc:8200"
roleName: "app"
objects: |
- objectName: "password"
secretPath: "secret/data/app/config"
secretKey: "password"
Vault Secrets Operator(VSO):
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
-n vault-secrets-operator-system --create-namespace
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: app-config
namespace: default
spec:
vaultAuthRef: app-auth
mount: secret
type: kv-v2
path: app/config
destination:
name: app-config
create: true
refreshAfter: 60s
rolloutRestartTargets:
- kind: Deployment
name: app
# 状态与集群成员
kubectl exec -n vault vault-0 -- vault status
kubectl exec -n vault vault-0 -- vault operator members
# 查看已启用的引擎与认证方式
kubectl exec -n vault vault-0 -- vault secrets list
kubectl exec -n vault vault-0 -- vault auth list
# 开启审计日志(强烈建议,应作为上线必做项)
kubectl exec -n vault vault-0 -- vault audit enable file file_path=/vault/logs/audit.log
kubectl exec -n vault vault-0 -- vault audit list
# 查看租期
kubectl exec -n vault vault-0 -- vault list sys/leases/lookup/auth/kubernetes/role/app
vault operator init 的输出必须当场离线保存。丢了 unseal key 等于丢了整个 Vault;root token 丢失还能用 unseal key 重新生成,但密文本身无法恢复。server.dev.enabled=true 是内存存储、自动解封、固定 root token 的组合,重启即全部丢失;chart 默认的单机模式用 file 存储,也不具备高可用与故障转移。kubectl exec 进错容器、日志被 sidecar 淹没、Job 因为 sidecar 不退出而永远不完成,都是常见麻烦。VSO 用控制器集中同步,没有这些问题,官方文档也把 VSO 列为更适合多数 Kubernetes 场景的方式。kubernetes_host 必须填 Pod 内可达的地址。写 https://kubernetes.default.svc:443,不要照抄集群外部的 apiserver 地址;自建 CA 或关闭了 issuer 校验的集群,还需要显式提供 kubernetes_ca_cert。bound_service_account_namespaces 是安全边界。写成 * 意味着任意命名空间里同名的 ServiceAccount 都能拿到对应策略,等于把权限开放给整个集群;务必写死命名空间。secret/data/app/*,CLI 里写 secret/app/config(vault CLI 会自动补 data)。策略写错路径的表现是 permission denied 而不是 path not found,很容易误判成令牌问题。vault audit enable file 作为必做项,并把日志送到独立的收集端。secret — Kubernetes原生Secret对象serviceaccount — Kubernetes认证方式的身份来源rbac — 与之配合的集群内权限控制helm — 部署Vault与VSO的主要方式kubectl — Kubernetes集群管理工具