把Kubernetes Secret加密成可安全提交到Git的SealedSecret
Sealed Secrets 是一个「单向加密」的控制器,解决的是一个非常具体的问题:如何把 Secret 安全地放进 Git。
它与 secret 页讲的 Kubernetes 原生 Secret 对象是两回事:原生 Secret 是集群里真实存在的资源,任何有读取权限的人都能 base64 解出明文;Sealed Secrets 是在原生 Secret 之外套的一层加密方案 —— 明文永远不进 Git,进 Git 的是只有目标集群能解开的密文。
整个方案由两部分组成:
sealed-secrets-controller 跑在集群里,持有私钥,负责解密
kubeseal CLI,用控制器的公钥在本地加密
工作流程:
1. kubeseal 从控制器取公钥(证书)
2. 在本地把 Secret 加密成 SealedSecret 清单
3. 把 SealedSecret 提交进 Git
4. 集群里的控制器用私钥解密,还原成普通 Secret 供 Pod 使用
加密在本地完成,私钥始终不出集群;密文只能由持有对应私钥的那个集群解开 —— 换个集群,或者在没备份私钥的情况下重建控制器,都无法解密。
与相近方案的分工:
| 方案 | 密钥存放位置 | 适用场景 |
|---|---|---|
| Sealed Secrets | 密文进 Git,私钥在集群内 | 单集群、纯 GitOps 流程 |
| External Secrets | 明文放在外部(如 Vault、云 Secrets Manager) | 多集群、已有统一密钥库 |
| SOPS | 加密文件进 Git,密钥在 KMS 或 age | 与 CI 流程结合紧密 |
Sealed Secrets 的定位是轻量、无外部依赖。它不做密钥值轮换、不做审批流,也不解决「谁来保管密钥明文」的问题。
helm repo add sealed-secrets https://bitnami.github.io/sealed-secrets
helm repo update
helm install sealed-secrets -n kube-system \
--set-string fullnameOverride=sealed-secrets-controller \
sealed-secrets/sealed-secrets
fullnameOverride 不是必须的,但强烈建议照抄:chart 默认把控制器命名为 sealed-secrets,而 kubeseal 默认去找名为 sealed-secrets-controller 的 Service,不覆盖名字就得每次加 --controller-name sealed-secrets。
安装 CLI:
# macOS
brew install kubeseal
# Linux:从 GitHub Releases 下载(以 v0.40.0 为例)
curl -LO https://github.com/bitnami/sealed-secrets/releases/download/v0.40.0/kubeseal-0.40.0-linux-amd64.tar.gz
tar -xzf kubeseal-0.40.0-linux-amd64.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
kubeseal --version
确认控制器就绪:
kubectl get pods -n kube-system -l app.kubernetes.io/name=sealed-secrets
kubectl get svc -n kube-system sealed-secrets-controller
# 1. 生成普通 Secret 清单(不要落库)
kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password='S3cr3t!' \
--dry-run=client -o yaml > db-secret.yaml
# 2. 加密成 SealedSecret
kubeseal -f db-secret.yaml -w db-sealed.yaml
# 3. 应用并提交
kubectl apply -f db-sealed.yaml
git add db-sealed.yaml && git commit -m "add db secret"
# 4. 控制器解密后会产生同名的普通 Secret
kubectl get secret db-secret
也可以直接管道,不落中间文件:
kubectl create secret generic db-secret \
--from-literal=password='S3cr3t!' \
--dry-run=client -o yaml | kubeseal -o yaml > db-sealed.yaml
生成的清单长这样:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-secret
namespace: default
spec:
encryptedData:
password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...
username: AgCtr9F3kZ0...
template:
metadata:
name: db-secret
namespace: default
type: Opaque
spec.template 里的元数据会被带到解密出的 Secret 上,但不能携带明文数据。
# strict(默认):名字与命名空间都参与加密,换名字或换命名空间都解不开
kubeseal --scope strict -f db-secret.yaml -w db-sealed.yaml
# namespace-wide:可在同一命名空间内自由改名字
kubeseal --scope namespace-wide -f db-secret.yaml -w db-sealed.yaml
# cluster-wide:可在任意命名空间、任意名字下解密
kubeseal --scope cluster-wide -f db-secret.yaml -w db-sealed.yaml
也支持注解形式,两者同时存在时 cluster-wide 优先:
metadata:
annotations:
sealedsecrets.bitnami.com/namespace-wide: "true"
CI 环境通常连不上集群,可以先取证书再离线加密:
# 取公钥证书(公开信息,可以进 Git)
kubeseal --fetch-cert > mycert.pem
# 离线加密
kubeseal --cert mycert.pem -f db-secret.yaml -w db-sealed.yaml
# 也可以从文件读取证书路径
export SEALED_SECRETS_CERT=mycert.pem
密钥轮换之后,建议把存量密文重新加密一遍:
kubeseal --re-encrypt -f db-sealed.yaml -w db-sealed-new.yaml
控制器每 30 天自动生成一把新的加密密钥。新密钥用于加密,旧密钥保留用于解密,因此轮换本身不会让已有 SealedSecret 失效。
# 查看密钥 Secret(私钥就在这里)
kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key
# 修改轮换周期:编辑 Deployment,给容器加 --key-renew-period=720h
# 值设为 0 表示关闭自动轮换
kubectl edit deployment/sealed-secrets-controller --namespace=kube-system
# 怀疑私钥泄露时,强制提前换钥(在 Deployment 上加参数)
# --key-cutoff-time="2026-01-01T00:00:00Z"
备份与恢复 —— 这一步不做,整个方案就是一次性的:
# 备份
kubectl get secret -n kube-system \
-l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > main.key
# 恢复:先停控制器,导入密钥,再让它重新读取
kubectl apply -f main.key
kubectl delete pod -n kube-system -l app.kubernetes.io/name=sealed-secrets
# 用清单方式安装时标签不同
kubectl delete pod -n kube-system -l name=sealed-secrets-controller
main.key 文件里是未加密的私钥,必须放到集群之外的安全位置(密码管理器、离线保险柜),并且每次密钥轮换后重新备份一次。
sealed-secrets-key Secret,都会让所有密文永久不可解。上线第一件事就是备份私钥并放到集群之外,且每次轮换后重新备份。strict 作用域下,命名空间与名字是密文的一部分,把同一个 SealedSecret 复制到别的命名空间解密必然失败;要跨命名空间复用只能用 cluster-wide,但那等于放弃了隔离边界,慎用。kubeseal 默认要能连上集群才能取公钥。CI 里通常先用 kubeseal --fetch-cert 取好证书,再用 --cert 离线加密;证书是公开信息,可以放心进 Git。kubeseal 侧看不出任何异常。template 段可以带元数据,不能带数据。spec.template.metadata.labels、annotations 会被传递到解密出的 Secret;试图在 template 里直接写 data 会被控制器拒绝。kubeseal --re-encrypt 重新生成密文。bitnamilegacy;但官方公告明确把 Sealed Secrets、charts-syncer 与 minideb 列为不受影响的项目,镜像仍照常发布在 docker.io/bitnami。secret — Kubernetes原生Secret对象本身的用法kubectl — Kubernetes集群管理工具helm — 安装控制器的主要方式kustomize — 与密文清单配合的声明式管理argocd — 常见的GitOps落地工具