把数据库密码、API密钥、TLS证书私钥直接硬编码在Helm Chart的values.yaml里,或者明文推送到Git仓库,是云原生交付中最危险的坏习惯。一旦仓库权限配置失误,或者某位离职员工的本地副本泄露,整个集群的凭据就完全暴露了。解决这个问题的标准方案不是“小心一点”,而是引入Helm Secrets插件,结合成熟的加密后端,让敏感信息以密文形态随Chart流转,仅在部署时由集群侧解密。
Helm Secrets解决的核心痛点Helm本身对敏感数据的管理能力非常有限。原生的values文件是纯文本,模板渲染后生成的ConfigMap和Secret默认也只是Base64编码,根本不是加密。Helm Secrets在Helm的渲染流水线中插入一个解密环节,让你可以把整个values文件或者其中某些字段用密文存储。它不改变你编写Chart模板的方式,只是在helm install或helm upgrade执行时,自动识别加密文件,调用后端解密后把明文注入渲染引擎。最终部署到Kubernetes的Secret资源依然是Base64格式,但你的Git仓库里永远只有密文。
插件生态与后端选择Helm Secrets本身是一个Helm插件,安装命令很简单:
helm plugin install https://github.com/jkroepke/helm-secrets
它不自己实现加密算法,而是作为编排层,把加密解密工作委托给后端。目前最主流、最推荐的后端是SOPS,SOPS又支持多种密钥管理服务作为加密密钥的存储和访问控制层。实际生产中常见的组合有三种:SOPS配合云厂商KMS,SOPS配合GPG,以及SOPS配合age。如果你运行在云上,优先使用对应云厂商的KMS,因为密钥权限天然和IAM角色绑定,Pod通过IRSA或实例角色就能获取解密权限,不需要传递任何私钥文件。如果是本地开发或混合架构,age因为密钥短小、无配置、操作简单,正在快速取代GPG成为新项目的首选。
SOPS配合云KMS的实战配置以AWS KMS为例,先创建一个对称加密的KMS Key,记下Key ID和ARN。然后在Chart目录下创建.sops.yaml配置文件,指定使用哪个KMS Key:
creation_rules: - kms: arn:aws:kms:us-east-1:123456789012:key/abcd-1234-efgh-5678
接下来把包含敏感信息的values文件加密。假设你有一个secrets.yaml文件,里面写着数据库密码和Redis连接串:
db: password: "SuperSecret123" redis: url: "redis://:auth_token@redis-host:6379"
执行加密命令:
helm secrets encrypt secrets.yaml
文件会变成SOPS的密文格式,所有值都被加密,只保留键名结构。把这个密文文件提交到Git是安全的。部署时使用helm secrets代替helm执行安装或升级:
helm secrets upgrade --install my-release . -f secrets.yaml
Helm Secrets会检测到secrets.yaml是SOPS加密文件,调用SOPS通过AWS KMS解密,然后把明文值传给Helm。整个过程对模板文件完全透明,你在模板里依然写{{ .Values.db.password }},完全不用改动。
使用age作为轻量级加密后端如果你不想依赖云厂商KMS,或者需要在CI/CD流水线里更容易地传递解密密钥,age是当下体验最好的选择。首先安装age命令行工具,然后生成密钥对:
age-keygen -o key.txt
公钥会打印在终端,形如age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx。在.sops.yaml里配置使用这个公钥:
creation_rules: - age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
加密和解密流程与KMS一致,区别在于解密时需要提供私钥。你可以把私钥文件放在CI/CD系统的安全变量里,或者挂载到执行部署的Pod中。Helm Secrets通过环境变量SOPS_AGE_KEY_FILE或SOPS_AGE_KEY来接收私钥。在流水线里通常是这样的:
export SOPS_AGE_KEY_FILE=/tmp/age-key.txt helm secrets upgrade --install my-release . -f secrets.yaml
age的优势在于密钥本身就是一个短文本,不需要任何网络调用,解密速度极快,非常适合离线环境或频繁部署的场景。
部分加密与文件级策略你不必把整个values文件全部加密。很多团队的做法是保留一个values.yaml存放非敏感配置,把敏感部分拆分到独立的secrets.yaml并整体加密。Helm支持多个-f参数,后面的文件会覆盖前面的同名键,所以部署命令可以写成:
helm secrets upgrade --install my-release . -f values.yaml -f secrets.yaml
values.yaml明文提交,secrets.yaml加密提交,各得其所。SOPS还支持对YAML文件中的特定字段加密,只加密值而保留键的明文结构,这样在代码审查时更容易看清配置结构的变化。要实现这一点,在.sops.yaml里配置encrypted_regex,例如只加密password和token结尾的字段:
creation_rules:
- encrypted_regex: '^(password|token)$'
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
执行加密后,文件里只有匹配正则的字段值被替换为密文,其他内容保持明文。这种方式在可读性和安全性之间取得了很好的平衡。
模板中的Secret资源正确写法Helm Secrets保护的是values文件的存储安全,但最终部署到Kubernetes的Secret资源,你仍然需要按规范定义。在模板文件里,务必显式声明Secret类型,并把敏感值填入data或stringData字段:
apiVersion: v1
kind: Secret
metadata:
name: {{ include "myapp.fullname" . }}-db
labels:
{{- include "myapp.labels" . | nindent 4 }}
type: Opaque
stringData:
DB_PASSWORD: {{ .Values.db.password | quote }}
REDIS_URL: {{ .Values.redis.url | quote }}
使用stringData而不是data,可以避免在模板里再做一次Base64编码,Kubernetes会自动处理。如果你的应用从环境变量读取配置,就在Deployment里引用这个Secret:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "myapp.fullname" . }}-db
key: DB_PASSWORD
这样从Git仓库到集群运行态的整条链路就打通了:仓库里是SOPS密文,CI/CD用Helm Secrets解密后渲染出Secret清单,Kubernetes存储Base64编码的Secret,Pod通过环境变量或卷挂载获取明文。
CI/CD流水线集成要点在持续集成环境中使用Helm Secrets,核心挑战是如何安全地把解密凭据注入流水线,同时不把凭据暴露在日志里。对于云KMS方案,这个问题天然解决:只要CI Runner所在的Pod或虚拟机有对应KMS的Decrypt权限,SOPS就能自动解密,无需任何额外凭据。在AWS上就是给Runner的Service Account绑定IRSA角色,在GCP上绑定Workload Identity,在Azure上使用托管标识。
对于age或GPG方案,需要把私钥作为CI/CD系统的受保护变量注入。GitHub Actions的例子如下:
- name: Decrypt and deploy
env:
SOPS_AGE_KEY: ${{ secrets.AGE_PRIVATE_KEY }}
run: |
helm secrets upgrade --install my-release ./chart -f ./chart/secrets.yaml
注意不要把私钥通过命令行参数传递,因为ps命令可能暴露进程参数。环境变量是相对安全的方式。另外务必在流水线脚本里关闭命令回显,避免密钥被打印到构建日志。
多环境与密钥轮换策略生产实践中,不同环境通常使用不同的加密密钥,即使密文泄露,也无法跨环境解密。你可以在.sops.yaml里为不同路径模式指定不同的创建规则:
creation_rules:
- path_regex: .*staging.*
kms: arn:aws:kms:us-east-1:123456789012:key/staging-key-id
- path_regex: .*production.*
kms: arn:aws:kms:us-east-1:123456789012:key/prod-key-id
密钥轮换方面,KMS方案最简单:云厂商的KMS支持自动轮换主密钥,SOPS加密的是数据密钥而非主密钥本身,所以密文无需重新加密即可继续使用。如果使用age,轮换意味着生成新密钥对,用新公钥重新加密所有secrets文件,然后更新CI/CD中的私钥变量。这个操作成本不高,建议每季度执行一次,并作为员工离职时的强制动作。
常见陷阱与排错指南第一个常见问题是.sops.yaml文件没有被Helm Secrets找到。SOPS默认在当前目录和用户主目录查找配置文件,如果你的Chart结构复杂,建议把.sops.yaml放在Chart根目录,并在执行helm secrets命令时确保工作目录正确。第二个问题是加密后的文件被错误地当成明文values传入普通helm命令,这会导致渲染出的Secret包含密文字符串。强制团队统一使用helm secrets子命令,或者在CI流水线里封装一个部署脚本,禁止直接调用helm处理敏感文件。
第三个问题是SOPS版本兼容性。Helm Secrets 4.x版本要求SOPS 3.7以上,如果你系统里残留了旧版SOPS,可能会出现加密格式不兼容的报错。用sops --version确认版本,保持插件和SOPS都更新到最新稳定版。第四个问题是部分加密时正则写错,导致本该加密的字段被明文提交。每次修改加密规则后,用helm secrets decrypt把文件解密到标准输出,人工检查一遍再提交。
最后,不要把加密后的Secret资源清单直接提交到Git。有些团队会先用helm template渲染出完整清单,再把其中的Secret对象用SOPS加密后提交,这完全走偏了。正确的做法永远是加密values源文件,让渲染过程在部署时发生,这样你保留的是单一事实来源,而不是两份需要同步的副本。
与External Secrets Operator的定位区别你可能会问,既然有了External Secrets Operator这类从外部凭据存储同步Secret的工具,为什么还需要Helm Secrets?两者的职责边界很清晰。ESO解决的是Secret内容从哪来的问题,它从Vault、AWS Secrets Manager、Azure Key Vault等外部系统拉取凭据,自动创建或更新Kubernetes Secret对象。Helm Secrets解决的是Chart里的敏感配置如何安全存储和版本化的问题。实际项目中两者经常配合使用:用Helm Secrets加密的values文件存放ESO的访问凭据和配置参数,ESO再根据这些配置去外部系统拉取业务凭据。这样你的Git仓库里只有加密后的ESO配置,而业务凭据完全不在Git中出现,安全层级更加分明。
团队落地建议从零开始引入Helm Secrets,建议分三步走。第一步,选一个最简单的Chart做试点,用age后端加密一个secrets.yaml,在本地跑通helm secrets install流程,感受整个操作链路。第二步,把试点Chart接入CI/CD,解决解密凭据的安全注入问题,让自动化部署跑通。第三步,制定团队规范:所有敏感值必须存放在独立的secrets.yaml文件里,该文件必须用SOPS加密后才能提交,CI/CD必须使用helm secrets命令部署,禁止明文values文件出现在任何Git仓库分支。同时把.sops.yaml也提交到仓库,确保每个开发者本地加密时使用相同的密钥规则。
对于已经在生产运行的Chart,迁移过程要格外小心。先在staging环境验证加密后的values渲染结果与原来完全一致,可以用helm secrets template命令对比输出。确认无误后再推广到生产环境。迁移期间新旧values文件会短暂共存,务必在合并代码前删除明文版本,避免有人误用。
Helm Secrets不是一个花哨的工具,它解决的是一个基础但致命的问题。把凭据加密存储进版本控制,配合正确的操作流程,可以让你在享受GitOps便利的同时,守住安全底线。这套方案已经在大量生产集群中验证过,稳定可靠,值得成为你Helm工作流的默认组成部分。
