Vault · 密钥管理
HashiCorp Vault 是整个集群的密钥真相来源。它以单副本 Deployment 跑在 workload: infra 节点上,使用 storage "file" 本地文件存储、Shamir 封印(seal),并通过 ExternalSecrets 把密钥分发给各个应用。集群里所有 Secret 都不进 git,统一从 Vault 拉取。
file 存储,没有 HA。不要把副本数加到 2 以上 —— file 后端不支持多实例并发,多副本只会互相争抢同一份数据文件。要做 HA 必须先换掉 file 存储后端。概览
- Name
- 镜像
- Description
hashicorp/vault:2.0.2
- Name
- namespace
- Description
vault
- Name
- 副本数
- Description
- 1(单副本,无 HA)
- Name
- 存储后端
- Description
storage "file",路径/vault/data,由名为vault-data的 PVC 提供(nfs-clientstorageClass,10Gi,ReadWriteMany)
- Name
- 封印方式
- Description
- Shamir seal,需 unseal key 解封
- Name
- 调度
- Description
nodeSelector: workload=infra
- Name
- 端口
- Description
- 8200(http / API + UI)、8201(internal / 集群通信)
- Name
- VPA
- Description
- 有,
vault-vpa,updateModeRecreate,CPU 50m–1、内存 64Mi–1Gi
- Name
- HPA / PDB
- Description
- 无(单副本不适用)
资源 requests/limits 为 CPU 250m/500m、内存 512Mi/1Gi。配置(vault.hcl)从 vault-config ConfigMap 挂到 /vault/config,开启了 ui = true 和监听器 telemetry 的无认证 metrics 访问。
部署形态
Deployment 以 server 参数启动,容器内通过环境变量定位自己:
VAULT_ADDR = http://127.0.0.1:8200 # postStart hook 本地访问
VAULT_API_ADDR = http://vault.vault.svc.cluster.local:8200 # 对外宣告地址
监听器配置为 tls_disable = 1(TLS 在 ingress 层终止,集群内走明文)。Service 是 ClusterIP 类型,暴露 8200/8201 两个端口。Pod 挂载三个卷:vault-config ConfigMap(配置)、vault-data PVC(数据)、vault-unseal-key Secret(解封密钥,只读)。
自动解封机制
Vault 经过 Shamir 封印后,每次重启都需要重新 unseal 才能服务请求。这套部署没有 init 容器,而是用 Deployment 的 postStart 生命周期钩子在容器启动后自动解封:
sleep 10
UNSEAL_KEY=$(cat /vault/unseal/key)
vault operator unseal $UNSEAL_KEY || true
unseal key 来自挂载在 /vault/unseal 的 vault-unseal-key Secret,该 Secret 由 ExternalSecret vault-unseal-key 从 Vault 自身的路径 vault/vault-unseal-key 拉取(refreshInterval 1h)。这样重启的 Pod 会自动解封,无需人工介入。
/v1/sys/health?standbyok=true,这个端点只有在 Vault 已解封后才返回 200。所以 Pod 显示 1/1 Ready 本身就是它已经成功解封的证明。liveness 探针打同一端点,initialDelaySeconds 60s。作为 External Secrets 后端
Vault 是 External Secrets 的后端,通过 ClusterSecretStore vault-backend 接入:
provider:
vault:
server: 'http://vault.vault.svc.cluster.local:8200'
path: 'secret' # KV v2 mount
version: 'v2'
约定上每个应用的密钥放在 yldm/production/<app>,外加共享路径如 yldm/database。注意 ESO 的 remoteRef.key 省略 secret/ mount 前缀(store 会自动加上),而 vault CLI 不省略 —— 写入时要用 vault kv put secret/...。
另外,集群里 Vault 的 Kubernetes auth 方法依赖一条 ClusterRoleBinding vault-auth-delegator:它把 system:auth-delegator 授给 vault/default ServiceAccount,让 Vault 能用自己的 Pod SA 调 TokenReview API 校验登录 JWT。缺这条绑定时所有 Kubernetes-auth 登录都会 403(曾经全集群范围内中断)—— 不要把它当孤儿资源删掉。
升级 2.0.0+ 镜像的坑
hashicorp/vault 2.0.x 镜像以非 root 用户运行。非 root 下,即使授予了 IPC_LOCK capability,mlock 仍会因容器的 memlock rlimit 失败,Vault 拒绝启动并 crash-loop,报错 Error initializing core: Failed to lock memory: cannot allocate memory。因此一次纯镜像 tag 的 bump(argocd-image-updater / renovate 产出的那种)并不够:必须同时在 configmaps-vault-config.yaml(vault.hcl)里设置 disable_mlock = true。节点上已禁用 swap,所以这样做是安全的。这个坑在 1.21 → 2.0.2 升级时踩过:镜像在 #983,配置修复在 #1019。Deployment 里保留的 IPC_LOCK capability 现在已是 no-op,但留着无害。
当前配置已设好 disable_mlock = true,并保留 telemetry 段(Prometheus retention 30s,disable_hostname)。
访问入口与监控
Vault UI / API 通过 Traefik ingress 暴露在 vault.yldm.tech(ingressClassName: traefik,经 Cloudflare Tunnel,app-root 重定向到 /ui/,后端指向 8200 端口)。
监控方面有 ServiceMonitor vault,每 30s 从 http 端口抓 /v1/sys/metrics?format=prometheus。配置里 listener 的 telemetry 开了 unauthenticated_metrics_access = true,所以 Prometheus 无需认证即可抓取。
相关页面:External Secrets · 集群架构 · GitOps 工作流