【GitOps·进阶篇】密钥管理:Sealed Secrets、SOPS 与 External Secrets

前言

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 模型下实现渐进式发布,让部署更安全、回滚更快速。

相关推荐
heimeiyingwang16 小时前
【GitOps·Flux篇】通知与告警:Webhook 集成与事件通知
flux·gitops
heimeiyingwang18 小时前
【GitOps·Flux篇】多集群与多租户:Tenant 模型与权限隔离
flux·gitops
heimeiyingwang2 天前
【GitOps·Flux篇】核心概念:Source、Kustomization与HelmRelease
helm·flux·gitops
heimeiyingwang6 天前
【GitOps·ArgoCD篇】RBAC与多租户:团队权限隔离实战
argocd·gitops
heimeiyingwang7 天前
【GitOps·ArgoCD篇】健康检查与资源钩子:自定义健康状态
argocd·gitops
heimeiyingwang8 天前
【GitOps·ArgoCD篇】与 Kustomize 集成:多环境配置管理
kustomize·argocd·gitops
heimeiyingwang9 天前
【GitOps·ArgoCD篇】同步策略:自动同步、手动同步与同步钩子
argocd·gitops
heimeiyingwang13 天前
【GitOps·入门篇】工具生态:ArgoCD、Flux、Jenkins X 对比选型
jenkins·flux·argocd·gitops
递归尽头是星辰3 个月前
云原生部署架构演进:基于 Helm 的 GitOps 落地选型
helm·gitops·云原生部署·ci/cd 架构·k8s 配置管理