前言
GitOps 要求一切声明式配置都存入 Git,但 Secret 怎么办?把明文密钥提交到 Git 仓库是严重的安全事故。本文详解三种主流方案------Sealed Secrets、SOPS 和 External Secrets,帮你安全地将密钥纳入 GitOps 管理。
一、问题:Secret 在 GitOps 中的困境
传统方式的痛点
yaml
# ❌ 直接把 Secret 提交到 Git ------ 绝对禁止
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: admin
password: SuperSecret123!
风险 :
-
Git 历史永久保留,即使删除也能恢复
-
任何有仓库读权限的人都能看到明文
-
审计困难,无法追溯密钥使用
-
违反合规要求(PCI-DSS、等保)
三种解决方案对比
| 方案 | 原理 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| Sealed Secrets | 加密后存入 Git,Controller 解密 | 简单直接,Git 是唯一真相源 | 加密密钥需管理,轮换复杂 | 小中型团队 |
| SOPS | 部分加密 YAML/JSON,Git 存储 | 灵活,支持多种 KMS | 需额外工具集成 | 已有 KMS 基础设施 |
| External Secrets | 引用外部密钥存储(Vault/AWS SM) | 密钥不进 Git,集中管理 | 依赖外部服务 | 大型企业,已有 Vault |
二、Sealed Secrets:加密后存 Git
工作原理
开发者 Sealed Secrets Controller Kubernetes
| | |
| kubeseal 加密明文 Secret | |
| ─────────────────────────→ | |
| 返回 SealedSecret (CRD) | |
| | |
| git commit SealedSecret | |
| ArgoCD/Flux 同步到集群 | |
| | ← 同步 SealedSecret |
| | 用私钥解密 |
| | 生成原生 Secret |
| | ──────────────────────────────────→ |
| | Pod 使用 Secret |
安装
bash
# 安装 Controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.3/controller.yaml
# 安装 kubeseal CLI
# macOS
brew install kubeseal
# Linux
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.3/kubeseal-0.27.3-linux-amd64.tar.gz
tar xzfv kubeseal-*.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
加密 Secret
bash
# 1. 创建普通 Secret(不提交到 Git)
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=SuperSecret123 \
--dry-run=client -o yaml > secret.yaml
# 2. 用 kubeseal 加密
kubeseal --format=yaml < secret.yaml > sealed-secret.yaml
# 3. 查看加密结果
cat sealed-secret.yaml
yaml
# sealed-secret.yaml ------ 可以安全提交到 Git
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: default
spec:
encryptedData:
username: AgBhM...(加密密文)
password: AgCiN...(加密密文)
template:
metadata:
name: db-credentials
namespace: default
type: Opaque
作用域策略
bash
# strict(默认):密钥与名称+命名空间绑定,换环境需重新加密
kubeseal --format=yaml --scope=strict < secret.yaml > sealed.yaml
# namespace-wide:同一命名空间内可重命名
kubeseal --format=yaml --scope=namespace-wide < secret.yaml > sealed.yaml
# cluster-wide:可在任意命名空间使用(慎用)
kubeseal --format=yaml --scope=cluster-wide < secret.yaml > sealed.yaml
备份与恢复私钥
bash
# 备份私钥(关键!)
kubectl get secret -n kube-system \
-l sealedsecrets.bitnami.com/sealed-secrets-key=active \
-o yaml > sealed-secrets-master-key.yaml
# 在新集群恢复
kubectl apply -f sealed-secrets-master-key.yaml
# 重启 controller 使其加载私钥
kubectl rollout restart deployment -n kube-system sealed-secrets-controller
⚠️ 踩坑提示 :私钥丢失 = 所有 SealedSecret 无法解密。务必将
sealed-secrets-master-key.yaml存入密码管理器或 Vault,不要提交到 Git。
三、SOPS:Mozilla 的密钥加密工具
工作原理
SOPS(Secrets OPerationS)只加密 YAML/JSON 的值,保留键名可读,支持多种 KMS 后端。
yaml
# 原始 Secret
apiVersion: v1
kind: Secret
metadata:
name: api-keys
type: Opaque
stringData:
database_password: "MySecret123"
api_token: "tok_abc123xyz"
# SOPS 加密后(值加密,键保留)
apiVersion: v1
kind: Secret
metadata:
name: api-keys
type: Opaque
stringData:
database_password: ENC[AES256_GCM,data:abc...,key:KMS]
api_token: ENC[AES256_GCM,data:def...,key:KMS]
sops:
age:
- recipient: age1xy...
安装与配置
bash
# 安装 SOPS
# macOS
brew install sops
# Linux
wget https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64
sudo install sops-v3.8.1.linux.amd64 /usr/local/bin/sops
# 安装 age(轻量加密工具,推荐替代 GPG)
# macOS
brew install age
# Linux
wget https://github.com/FiloSottile/age/releases/download/v1.1.1/age-v1.1.1-linux-amd64.tar.gz
tar xzf age-v1.1.1-linux-amd64.tar.gz
sudo install age/age age/age-keygen /usr/local/bin/
生成 age 密钥对
bash
# 生成密钥对
age-keygen -o age-key.txt
# 输出:
# # created: 2024-01-15T10:00:00Z
# # public key: age1qzv...(公钥,可公开)
# # private key: AGE-SECRET-KEY-1...(私钥,务必安全保管)
# 配置 SOPS
cat > .sops.yaml << 'EOF'
creation_rules:
- path_regex: .*.yaml
encrypted_regex: ^(data|stringData)$
age: age1qzv... # 替换为你的公钥
EOF
加密与解密
bash
# 创建明文 Secret 文件
cat > secret.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
name: app-config
type: Opaque
stringData:
DB_PASSWORD: MySecret123
API_KEY: sk-abc123xyz
REDIS_URL: redis://prod:6379
EOF
# 加密
sops --encrypt --in-place secret.yaml
# 查看加密结果(键名可读,值加密)
cat secret.yaml
# stringData:
# DB_PASSWORD: ENC[AES256_GCM,data:xxx,key:KMS]
# API_KEY: ENC[AES256_GCM,data:yyy,key:KMS]
# REDIS_URL: ENC[AES256_GCM,data:zzz,key:KMS]
# 解密查看
sops --decrypt secret.yaml
# 在集群中解密部署(需配置 age 私钥)
sops --decrypt secret.yaml | kubectl apply -f -
与 ArgoCD 集成
yaml
# ArgoCD KSOPS 插件配置
# 在 argocd-cm ConfigMap 中启用
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
configManagementPlugins: |
- name: ksops
generate:
command: ["ksops"]
args: ["generate"]
bash
# 安装 ksops(SOPS 的 Kustomize 插件)
# 需要在 ArgoCD Server/Repo Server Pod 中安装
# 并配置 age 私钥
kubectl create secret generic sops-age-key \
--namespace argocd \
--from-file=age-key.txt=age-key.txt
四、External Secrets Operator:外部密钥引用
工作原理
Git 仓库 External Secrets Operator 外部密钥存储
| | |
| ExternalSecret (CRD) | |
| 引用外部密钥名称 | |
| ──────────────────────→ | |
| (读取 CRD) | |
| |── 调用 API 获取密钥 ──→ |
| | (返回密钥) |
| |←───────────────────────── |
| | 创建/更新原生 Secret |
| |──→ Kubernetes Secret |
| | Pod 使用 ←── |
安装
bash
# Helm 安装 External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--set installCRDs=true
与 HashiCorp Vault 集成
yaml
# 1. 创建 SecretStore ------ 连接 Vault
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: default
spec:
provider:
vault:
server: "https://vault.example.com"
path: "kv" # KV 引擎路径
version: "v2" # KV v2
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets" # Vault K8s auth role
serviceAccountRef:
name: external-secrets
namespace: default
yaml
# 2. 创建 ExternalSecret ------ 引用 Vault 中的密钥
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-database-credentials
namespace: default
spec:
refreshInterval: 1h # 每小时同步一次
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: db-credentials # 生成的原生 Secret 名称
creationPolicy: Owner
data:
- secretKey: username # K8s Secret 中的键
remoteRef:
key: kv/production/db # Vault 路径
property: username # Vault 中该路径下的属性
- secretKey: password
remoteRef:
key: kv/production/db
property: password
与 AWS Secrets Manager 集成
yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-irsa
yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: stripe-api-key
spec:
refreshInterval: 5m
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: stripe-secret
data:
- secretKey: api_key
remoteRef:
key: production/stripe # AWS SM 中的密钥名
PushSecret:反向同步
yaml
# 将 K8s Secret 推送到外部存储
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
name: push-to-vault
spec:
refreshInterval: 1h
secretStoreRefs:
- name: vault-backend
kind: SecretStore
selector:
secret:
name: generated-secret # K8s 中已有的 Secret
data:
- match:
secretKey: token # K8s Secret 中的键
remoteRef:
remoteKey: kv/generated/token # 推送到 Vault 的路径
property: token
五、方案选型与生产建议
选型决策树
是否已有 Vault / AWS Secrets Manager?
├── 是 → External Secrets Operator(密钥不进 Git,集中管理)
└── 否
├── 团队 < 50 人 → Sealed Secrets(简单直接)
└── 团队 ≥ 50 人 → SOPS + age(灵活,支持密钥轮换)
混合方案推荐
生产环境推荐架构:
Git 仓库
├── 普通配置 → 原生 K8s ConfigMap
├── 开发/测试密钥 → Sealed Secrets(方便独立开发)
└── 生产密钥 → External Secrets → Vault(集中管理+审计)
密钥轮换策略
yaml
# External Secrets 自动轮换
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 1h
target:
name: db-credentials
template:
engineVersion: v2
data:
# 动态生成密码
password: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: production/database
property: current_password
⚠️ 踩坑提示 :密钥轮换时,应用 Pod 不会自动重启。需配合 Reloader 或在 Deployment 中注入
secret.version注解触发滚动更新。
要点回顾
| 方案 | 加密位置 | 密钥存储 | Git 中存什么 | 复杂度 | 推荐场景 |
|---|---|---|---|---|---|
| Sealed Secrets | Controller 端 | 集群内私钥 | 加密后的 SealedSecret | 低 | 小中型团队 |
| SOPS | 开发者端 | KMS/age | 加密后的 YAML | 中 | 已有 KMS |
| External Secrets | 集群内 Operator | 外部存储 | 引用配置(无密钥) | 高 | 大型企业 |
- 三种方案可组合使用,不互斥
- Sealed Secrets 私钥必须备份
- SOPS 配合 age 比 GPG 更简单
- External Secrets + Vault 是企业最佳实践
下一篇预告
密钥安全解决了,但如果发布不顺利怎么办?下一篇 【GitOps·进阶篇】渐进式交付:Argo Rollouts 金丝雀与蓝绿发布 将讲解如何在 GitOps 模型下实现渐进式发布,让部署更安全、回滚更快速。