痛点
GitOps 工作流要求「一切皆代码、一切进 Git」,但 Kubernetes Secret、数据库密码、API Token 这类敏感信息怎么办?直接明文提交是安全事故的温床;用 Vault 全托管又引入额外基础设施和运维成本。
现实中最常见的反模式:
.env文件写入.gitignore,部署时手动拷贝 —— 无法审计变更历史- Secret 经 CI 变量注入,YAML 里留占位符 —— 本地调试困难、CI 平台成为单点
- 直接把 base64 编码(≠加密)后的 Secret 推进仓库 —— 等于裸奔
SOPS(Secrets OPerationS)+ age 组合恰好解决这个问题:在 Git 中存储加密后的密钥文件,解密权限通过 age 私钥控制,零外部依赖、纯文件级操作。
方案概览
| 组件 | 作用 | 特点 |
|---|---|---|
| SOPS | 加密/解密引擎 | 仅加密 value 不加密 key,保留 YAML/JSON 结构可读性 |
| age | 密钥体系 | 现代替代 PGP,无需 keyserver,单文件密钥,X25519 算法 |
| Flux/ArgoCD | GitOps 控制器 | 原生支持 SOPS 解密,部署时自动还原明文 |
整体流程:开发者用 age 公钥加密 → 密文推入 Git → GitOps 控制器用私钥解密 → 注入 Kubernetes 集群。
实操步骤
第 1 步:安装 SOPS + age 并生成密钥对
# 安装 age(macOS / Linux)
# macOS
brew install age sops
# Linux amd64
curl -LO https://github.com/FiloSottile/age/releases/download/v1.2.0/age-v1.2.0-linux-amd64.tar.gz
tar xf age-v1.2.0-linux-amd64.tar.gz && sudo mv age/age* /usr/local/bin/
# 安装 SOPS
curl -LO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64
chmod +x sops-v3.9.4.linux.amd64 && sudo mv sops-v3.9.4.linux.amd64 /usr/local/bin/sops
# 生成 age 密钥对
age-keygen -o ~/.config/sops/age/keys.txt
# 输出示例:
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
记录公钥(age1...),私钥文件保存在本地,绝不提交到 Git。
第 2 步:配置 .sops.yaml 并加密 Secret
在仓库根目录创建 .sops.yaml,声明加密规则:
# .sops.yaml
creation_rules:
# 匹配路径中含 secret 的文件
- path_regex: .*secret.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1second-recipient-public-key-for-ci-server
# 可为不同环境指定不同密钥
- path_regex: envs/prod/.*\.yaml$
age: age1prod-only-key...
加密 Kubernetes Secret 文件:
# 原始明文文件 db-secret.yaml
cat <<EOF > db-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: admin
password: "S3cur3P@ss!"
connection_string: "postgresql://admin:S3cur3P@ss!@db.internal:5432/app"
EOF
# 加密(仅 stringData 下的 value 被加密,key 保留可读)
sops --encrypt --in-place db-secret.yaml
加密后文件结构(注意 key 名称仍然可读):
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: ENC[AES256_GCM,data:kDj2fw==,iv:...,tag:...]
password: ENC[AES256_GCM,data:r8K2mNqz...,iv:...,tag:...]
connection_string: ENC[AES256_GCM,data:bH7xY2...,iv:...,tag:...]
sops:
age:
- recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
...
现在可以安全地 git add db-secret.yaml && git push。
第 3 步:GitOps 控制器集成解密
Flux 方案(推荐):
# 将 age 私钥作为 Secret 导入集群
cat ~/.config/sops/age/keys.txt | kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/dev/stdin
# 在 Flux Kustomization 中启用解密
cat <<EOF | kubectl apply -f -
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app-secrets
namespace: flux-system
spec:
interval: 5m
path: ./k8s/secrets
prune: true
sourceRef:
kind: GitRepository
name: main-repo
decryption:
provider: sops
secretRef:
name: sops-age
EOF
Flux 会自动解密 Git 中的 SOPS 加密文件,将明文 Secret 应用到集群。
避坑指南
坑 1:age 私钥丢失 = 所有密文永久不可恢复
解决: 私钥备份至少两个独立安全位置(如硬件密钥 + 密码管理器)。生产环境建议配置多 recipient(多把公钥均可解密),避免单点故障:
# .sops.yaml 中配置多个 recipient
age: >-
age1-primary-key,
age1-backup-key,
age1-ci-server-key
坑 2:sops --encrypt 误加密整个文件导致不可读
解决: 使用 encrypted_regex 限定只加密特定字段:
creation_rules:
- path_regex: .*secret.*\.yaml$
age: age1...
encrypted_regex: "^(data|stringData)$"
这样 metadata、apiVersion 等字段保持明文,PR Review 时仍能看到 Secret 名称和命名空间变更。
坑 3:本地编辑加密文件时忘记用 sops 命令
解决: 永远用 sops db-secret.yaml 打开(自动解密到编辑器,保存时自动重加密)。可配合 Git hook 检查:
# .git/hooks/pre-commit
#!/bin/bash
for file in $(git diff --cached --name-only | grep secret); do
if ! grep -q "^sops:" "$file"; then
echo "ERROR: $file 未加密!请用 sops --encrypt 处理"
exit 1
fi
done
总结
| 维度 | SOPS + age | Vault | Sealed Secrets |
|---|---|---|---|
| 外部依赖 | 无 | 需 Vault Server | 需集群内 Controller |
| 学习成本 | 低(3 条命令) | 高 | 中 |
| 密钥轮换 | 手动 sops updatekeys |
自动 | 重新 seal |
| 审计追踪 | Git History | Vault Audit Log | Git History |
| 适用规模 | 小~中团队 | 中~大企业 | 单集群 |
核心结论: 对于 10 人以下 DevOps 团队、不想引入额外基础设施的场景,SOPS + age 是投入产出比最高的 GitOps 密钥管理方案。3 条命令上手,Git 原生审计,Flux/ArgoCD 原生支持,零运维成本。
密钥管理的黄金法则:加密成本永远低于泄露成本。 今天就把仓库里的明文 Secret 迁移到 SOPS 吧。