都已在本地 minikube(profile gitops-secrets,k8s v1.31.1)上实跑通过。
两者解决的是同一个问题:让密钥能安全地提交进 Git 仓库。 区别只有一句话:
kubeseal 在集群内解密,sops 在集群外解密。
其余所有差异都是这一条推导出来的。
./00-setup.sh # 建 namespace,检查 sealed-secrets controller
./kubeseal/10-seal.sh # 拉公钥 -> 封 SealedSecret
./kubeseal/11-apply.sh # apply 密文 -> controller 自动解出 Secret
./sops/20-encrypt.sh # 生成 age 密钥 -> 写 .sops.yaml -> 加密
./sops/21-apply.sh # 本地解密 -> apply
./30-compare.sh # 4 组对照实验(关键)
./40-disaster.sh # kubeseal 私钥丢失/恢复演练(关键)
./99-cleanup.shenv.sh 里统一了 context / namespace / age key 路径,还把 minikube 节点 IP
加进了 NO_PROXY(本机开了 127.0.0.1:7890 代理,不加会连不上 apiserver)。
明文 Secret ──kubeseal + 集群公钥──> SealedSecret(CR) ──git──> ArgoCD apply
│
controller 用集群内私钥解密
↓
普通 Secret
- 加密:只需要公钥,
kubeseal --fetch-cert就能拿到,可以放心发给任何人 - 解密:私钥在集群里(
kube-system/sealed-secrets-keyXXXXX),没人能在本地解开密文 - ArgoCD/Flux 原生支持:它们只是 apply 一个 CR,不需要任何插件
明文 Secret ──sops + age/KMS 公钥──> secret.enc.yaml ──git──> sops decrypt | kubectl apply
↑
需要私钥(本地/CI/插件)
- 加密/解密用同一套 age 或 KMS 密钥,私钥在集群外
- ArgoCD 要落地必须装插件:
ksops、helm-secrets,或改用 Flux 的原生.spec.decryption - 好处:这套东西不绑定 k8s,同样能加密 terraform.tfvars、.env、CI 配置
把 demo 的 SealedSecret 原样 apply 到 demo-sops:
Synced=False no key could decrypt secret (API_KEY, DB_PASSWORD)
这是特性不是 bug:别人偷到你的 SealedSecret,也没法换个 namespace 部署出来偷看。
代价是多环境(dev/staging/prod)每个环境都要各封一遍,
文件不能复制粘贴。(可用 --scope namespace-wide / cluster-wide 放宽,但也就放弃了这层保护。)
sops 的密文没有这个绑定,同一个文件可以在任意 namespace/集群解开。
| 做法 | sops | kubeseal |
|---|---|---|
| 整个文件重新加密(别这么干) | 18 行 | 4 行(两个值都变) |
| 增量更新一条 | sops set → 6 行 |
--merge-into → 2 行 |
# sops 增量
sops set sops/secret.enc.yaml '["stringData"]["DB_PASSWORD"]' '"新密码"'
# kubeseal 增量
echo '{"apiVersion":"v1","kind":"Secret","metadata":{"name":"app-secret","namespace":"demo"},
"stringData":{"DB_PASSWORD":"新密码"}}' \
| kubeseal --format yaml --cert kubeseal/pub-cert.pem --merge-into kubeseal/sealedsecret.yamlkubeseal 每次加密都用新的随机 session key,所以整体重封会让所有值的密文全变,
review 的时候看不出到底改了哪条 —— 一定要用 --merge-into。
| kubeseal | sops | |
|---|---|---|
| 私钥位置 | 集群内 kube-system 的 Secret |
集群外:开发者笔记本 / CI Secret / KMS |
| 必须备份 | 是,且是硬性要求 | 是(用 KMS 则由云厂商托管) |
| 私钥泄露后果 | 能解开这个集群的所有 SealedSecret | 能解开 Git 里所有历史密文 |
| 集群重建 | 不恢复 key = 所有密文变废纸 | 完全不受影响 |
./40-disaster.sh 真跑了一遍这个场景:
步骤 2:删掉私钥并重建 controller -> 新生成的 key: sealed-secrets-keyj9g8r(换了一把)
步骤 3:Git 文件一字未改,重新同步 -> no key could decrypt secret
demo/app-secret 不存在 —— 应用起不来了
步骤 4:恢复备份的私钥 -> Synced=True,值全部解出
kubeseal 的备份命令(加进你的运维 checklist):
kubectl -n kube-system get secret \
-l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > master.key两边最终产物都是一模一样的普通 Secret,deployment 里 envFrom.secretRef 不用改一个字:
=== demo (kubeseal) 应用读到的值 ===
[app] DB_PASSWORD=s3cr3t-pg-pa55w0rd API_KEY=sk-demo-1234567890abcdef
=== demo-sops (sops) 应用读到的值 ===
[app] DB_PASSWORD=s3cr3t-pg-pa55w0rd API_KEY=sk-demo-1234567890abcdef
所以选型只影响运维流程,不影响应用,将来换方案的迁移成本也不高。
选 kubeseal,如果:
- 用 ArgoCD 且不想装插件(原生支持,这是最大的实际优势)
- 团队只有 k8s 密钥这一类需求
- 能接受「必须备份 controller key」这条硬性运维要求
- 多集群不多;每多一个集群就要重封一遍所有密钥
选 sops,如果:
- 要加密的不只是 k8s Secret(tfvars、.env、CI 配置一套搞定)
- 已经在用云 KMS —— 这时私钥托管、审计、轮换、按人授权全是现成的, 也就绕开了 kubeseal「私钥躺在集群里」的问题
- 多集群/多环境,希望同一份密文到处能解
- 用 Flux(
.spec.decryption原生支持 sops,体验和 kubeseal 之于 ArgoCD 一样好)
一句话建议: ArgoCD + 单集群 → kubeseal; 有云 KMS 或密钥不止 k8s 一处用 → sops + KMS(别用裸 age 私钥)。
这个 demo 搭的时候真的踩到了。最初的 .gitignore 写成:
secrets-local/ # sops 私钥 + 明文 Secret
*.plain.yaml # 明文 Secret
*.agekey
*.key结果 git status 显示明文 Secret 会被提交:前两条带行尾注释的规则整条失效
(git 把 secrets-local/ # sops 私钥... 当成一个字面模式),
只有不带注释的 *.agekey / *.key 生效了。注释必须单独占一行。
所以不管选哪种方案,都要有一步机器验证,别靠肉眼看 .gitignore:
# 1. 确认没有敏感文件进入暂存区
git status --porcelain --ignored | grep '^!!'
# 2. 在会被提交的文件里搜明文(CI 里跑,或用 gitleaks / trufflehog)
grep -rn "你的密钥特征" $(git ls-files)前面所有步骤都是手敲 kubectl apply。装上 Flux 之后,这一步被自动化掉。
已在本仓库 + 本地 minikube (k8s 1.35.1, Flux 2.9.5) 上实跑通过。
brew install fluxcd/tap/flux
flux check --pre --context gitops-secrets # ✔ Kubernetes 1.35.1 >=1.33.0-0
flux install --context gitops-secrets \
--components=source-controller,kustomize-controller装进去 2 个 Pod(flux-system 命名空间)+ 7 个 CRD。
注意 每条命令都带 --context —— 不加就装进 kubeconfig 当前那个集群,
如果当前是生产环境就出事了。
# 生成专用密钥对,私钥直接存成集群里的 Secret
flux create secret git flux-git-auth --namespace flux-system \
--url=ssh://git@github.com/<owner>/<repo> --ssh-key-algorithm=ed25519
# 输出的公钥加到仓库的 Deploy keys(只读即可)
gh api repos/<owner>/<repo>/keys -X POST -f title=flux -f key="<公钥>" -F read_only=true用专用 deploy key,不要用个人 SSH 私钥。
flux/gitrepository.yaml —— 拉哪个仓库、哪个分支:
spec:
url: ssh://git@github.com/<owner>/<repo>
ref: { branch: main }
interval: 1m
secretRef: { name: flux-git-auth }flux/kustomization-kubeseal.yaml —— 不需要任何解密配置:
spec:
sourceRef: { kind: GitRepository, name: gitops-secrets-demo }
path: ./kubeseal
prune: trueflux/kustomization-sops.yaml —— 多了 decryption 一段:
spec:
path: ./sops
decryption:
provider: sops
secretRef: { name: sops-age } # 存 age 私钥的 Secretsops 那侧还要先把私钥塞进集群:
kubectl -n flux-system create secret generic sops-age --from-file=age.agekey=<私钥路径>
⚠️ 这一步很关键:age 私钥现在躺在集群里了, 「私钥不在集群内」这条 sops 相对 kubeseal 的核心优势就没了。 生产上应该用云 KMS + Workload Identity(decryption不给secretRef), 那样集群里不存在任何密钥材料。
$ flux get sources git
NAME REVISION READY MESSAGE
gitops-secrets-demo main@sha1:5e9a92bd True stored artifact for revision 'main@sha1:5e9a92bd'
$ flux get kustomizations
NAME REVISION READY MESSAGE
kubeseal-secrets main@sha1:5e9a92bd True Applied revision: main@sha1:5e9a92bd
sops-secrets main@sha1:5e9a92bd True Applied revision: main@sha1:5e9a92bd
删光手动创建的资源后,Flux 自动把两侧都重建了出来,
对象上带着 kustomize.toolkit.fluxcd.io/name 标签标明是谁创建的:
SealedSecret <- kubeseal-secrets
Secret <- sops-secrets
改一个值 push 到 GitHub,全程不敲 kubectl,55 秒后集群自动更新(interval: 1m):
50s DB_PASSWORD=s3cr3t-pg-pa55w0rd
55s DB_PASSWORD=changed-via-git-push ← Flux 自动同步完成
重建集群(k8s 1.31 → 1.35)后,直接拿 Git 里原封不动的旧文件试:
kubeseal: apply 旧 sealedsecret.yaml
→ no key could decrypt secret ❌ 旧密文作废,必须重新封
sops: apply 旧 secret.enc.yaml
→ API_KEY / DB_PASSWORD 全部解出 ✅ 完全不受影响
这不是设计的实验,是升级集群时自然撞上的。换集群在真实工作里很常见 (灾备演练、版本升级、多环境扩容),kubeseal 每次都要重封全部密钥。
gitops-secrets-demo/
├── .sops.yaml ✅ 进 Git(只含公钥和加密规则)
├── .gitignore
├── env.sh
├── 00-setup.sh
├── 30-compare.sh 4 组对照实验
├── 40-disaster.sh 私钥丢失/恢复演练
├── 99-cleanup.sh
├── app/deployment.yaml ✅ 消费 Secret 的示例应用
├── kubeseal/
│ ├── pub-cert.pem ✅ 进 Git(公钥)
│ ├── sealedsecret.yaml ✅ 进 Git(密文)
│ ├── 10-seal.sh
│ └── 11-apply.sh
├── sops/
│ ├── secret.enc.yaml ✅ 进 Git(密文)
│ ├── 20-encrypt.sh
│ └── 21-apply.sh
└── secrets-local/ ❌ 已 gitignore
├── *.plain.yaml 明文 Secret
├── age.agekey sops 私钥
└── sealed-secrets-master.key kubeseal 私钥备份