核心架构概览
该方案的核心思想是在应用 Pod 启动前,通过一个临时的 InitContainer动态解密并创建 Kubernetes Secrets。
• SOPS 加密文件: 将包含敏感数据的 values.yaml文件(例如 secret-values.yaml)使用 SOPS 和 Age (或 PGP) 进行加密。加密后的文件可以安全地提交到 Git 仓库。
• Helm Chart: 一个标准的 Helm Chart,但经过特殊设计,能够接收加密文件和相关配置。
• 解密 Init 容器 (The "Magic Container"): 这是一个自定义的 Docker 镜像,其中打包了 SOPS和 kubectl两个关键工具以及一个解密脚本。它作为 InitContainer在主应用容器之前运行。
• 解密密钥: 用于解密的私钥(如 Age key)被预先存储在目标命名空间的一个标准 Kubernetes Secret 中。
• RBAC 权限: Chart 中包含一个 ServiceAccount、Role和 RoleBinding,授予 Init 容器足够的权限(主要是创建和管理 Secret对象)来执行解密和创建操作。
工作流程揭秘
1. 部署触发: 开发者通过 ArgoCD 发起部署。ArgoCD 从 Git 仓库拉取 Helm Chart 和相关的配置文件。
2. 传递加密文件: ArgoCD 使用 Helm 的 --set-file参数,将 SOPS 加密后的文件内容传递给 Helm Chart。这个加密内容被临时存储在一个名为 -encrypted-secret的 Secret 中。
3. Init 容器启动: 当应用的 Pod 开始创建时,解密 InitContainer首先启动。
4. 解密与创建:
• InitContainer从 -age-keysSecret 中读取解密私钥。
• 它再读取 -encrypted-secret中的加密数据。
• 利用内置的 SOPS 工具和私钥,在容器内解密数据。
• 最后,使用内置的 kubectl工具,将解密后的明文数据动态创建一个新的、标准的 Kubernetes Secret,例如 -secret。
5. 主容器启动: InitContainer成功退出后,主应用容器启动。此时,它可以像往常一样挂载和使用刚刚由 Init 容器创建的 -secret。
实战指南:具体步骤与代码
以下我们将通过一个具体场景,演示如何从零开始配置和部署。
1. 前提准备 (Prerequisites)
确保你已安装并配置好以下工具:
• SOPS
• Helm
• kubectl
• Docker
• ArgoCD
2. 生成加密密钥 (Age)
首先,我们使用 age生成一对公私钥。私钥将用于解密。
# 生成一个名为 age-key.txt 的密钥文件
age-keygen -o age-key.txt
注意: age-key.txt包含了你的私钥,绝对不能将其提交到 Git 仓库。请务必将其添加到 .gitignore文件中。
3. 准备并加密敏感数据文件
创建一个用于存放敏感信息的文件,例如 secret-values.yaml:
# secret-values.yaml
data: "some secret you want to protect"
another_secret: "password123"
然后,使用 SOPS 和之前生成的公钥(可以从 age-key.txt文件中查看,以 age1开头)来加密此文件。
# 使用 -e (encrypt) 参数进行加密
# --age 后面跟你的公钥
sops --encrypt --age 'age1...' secret-values.yaml > secret-values.enc.yaml
现在你可以安全地将 secret-values.enc.yaml提交到 Git。
4. 在 Kubernetes 中创建解密密钥 Secret
将 age-key.txt的全部内容存入一个 Kubernetes Secret 中。这个 Secret 需要和你的应用部署在同一个 namespace。
# age-key-secret.yaml
apiVersion:v1
kind:Secret
metadata:
# Secret 名称需遵循 <chart-name>-age-keys 的格式
# 如果你的 chart 最终叫 myapp,这里就是 myapp-age-keys
name:myapp-age-keys
namespace:your-app-namespace# 替换成你的命名空间
type:Opaque
stringData:
# 键名必须是 age-key.txt
age-key.txt: |
# Created: 2024-08-09T10:00:00Z
# public key: age1...
AGE-SECRET-KEY-1... # 从 age-key.txt 文件粘贴全部内容
应用它:kubectl apply -f age-key-secret.yaml
5. 配置 Helm Chart 和 ArgoCD
在你的 Helm Chart 的 values.yaml(或一个自定义的 custom-values.yaml) 文件中,配置 initContainer和其他参数。
# values.yaml
# 使用 nameOverride 来统一所有资源的名称前缀
nameOverride:"myapp"
# 主应用的配置...
image:
repository:nginx
tag:latest
# 解密 Init Container 的配置
initContainer:
image:mrupnikm/olm-chart-sops-decryption:latest# 使用作者提供的或自建的镜像
k8s_args:"-n your-app-namespace"# 指定操作的命名空间
encrypted_secret:
# 如果使用 age,只需提供一个非空值即可
age:"x"
# 这个字段留空,ArgoCD 会通过 fileParameters 填充它
extraSecretFile: ""
在你的 ArgoCD Application argo.yaml中,你需要使用 helm.fileParameters来指定加密文件。
# argo.yaml
apiVersion:argoproj.io/v1alpha1
kind:Application
metadata:
name:my-web-app
namespace:argocd
spec:
project:default
source:
repoURL:'https://github.com/your/repo.git'# 你的 Git 仓库地址
targetRevision:HEAD
path:'path/to/your/helm-chart'# Helm Chart 所在的路径
helm:
valueFiles:
-values.yaml
# 关键部分:使用 fileParameters 将加密文件内容传递给 --set-file
fileParameters:
-name:extraSecretFile# 对应 values.yaml 中的 extraSecretFile
path:secret-values.enc.yaml# 加密文件在 Git 仓库中的路径
destination:
server:'https://kubernetes.default.svc'
namespace:your-app-namespace# 应用部署的目标命名空间
syncPolicy:
automated:
prune:true
selfHeal: true
6. 部署应用
最后,将 ArgoCD 应用清单提交到集群:
argocd app create -f argo.yaml
ArgoCD 将会自动同步应用,执行上述工作流,最终你的应用将能成功挂载并使用解密后的 Secret。
总结与思考
本文介绍的方法巧妙地利用了 Kubernetes 的 InitContainer和 Helm 的 --set-file特性,构建了一个无需 ArgoCD 插件即可自动化处理 SOPS 加密文件的 GitOps 流程。
• 优点:
原生兼容: 无需为 ArgoCD 安装或配置任何插件,降低了维护复杂性。
安全: 敏感信息以加密形式存储在 Git 中,符合 GitOps 最佳实践。
灵活: 可以通过修改 InitContainer中的脚本来适应不同的 Secret 结构或解密逻辑。
• 注意事项:
• 部署完成后,手动创建的解密密钥 Secret (myapp-age-keys) 不受 ArgoCD 管理,如果项目清理,需要手动删除。
• 该方案引入了一个自定义的 InitContainer镜像,需要确保该镜像的来源可靠并进行妥善维护。
总而言之,这并非管理 Kubernetes Secrets 的唯一方案(其他方案如 External Secrets Operator, HashiCorp Vault 等也十分优秀),但它为特定场景——尤其是追求环境简洁、希望避免引入过多外部依赖的团队——提供了一个极具参考价值的、轻量级的实现范本。