4. Values文件设计哲学:单一Values vs 分层Values、默认值、覆盖策略、敏感信息分离

在多集群环境中,Values 文件的设计直接决定了 Chart 的可维护性、可复用性和安全性。一个糟糕的 Values 设计会导致配置爆炸、覆盖混乱、敏感信息泄露。本章将深入剖析 Values 文件的设计哲学,帮助你在单一与分层、默认与覆盖、明文与密文之间找到最佳平衡点。

4.1 单一 Values vs 分层 Values:从扁平到结构化

Values 文件的设计首先面临一个核心选择:将所有配置塞入一个文件,还是按维度拆分为多个文件?

维度 单一 Values (values.yaml) 分层 Values (多文件/多层级)
文件数量 1 个主文件 多个文件:base + 环境 + 集群 + 组件
适用场景 单集群、简单应用、PoC 多集群、复杂应用、企业级
覆盖逻辑 手动合并或 --set 覆盖 Helm 原生 -f 合并,后加载覆盖前加载
可读性 文件短时好,长时灾难 按职责分离,易于定位
维护成本 低(初期)→ 高(后期) 高(初期)→ 低(后期)
实践建议: 对于多集群环境,强制使用分层 Values。单一 Values 在超过 200 行后,任何修改都需要全局理解,极易引入错误。分层设计让每个文件只关注一个维度,降低认知负载。

分层 Values 的典型结构如下:

values/
├── base.yaml                  # 全局默认值,所有集群共享
├── production/
│   ├── common.yaml            # 生产环境通用覆盖
│   ├── cluster-aws-us-east.yaml  # AWS us-east-1 集群覆盖
│   └── cluster-aws-eu-west.yaml  # AWS eu-west-1 集群覆盖
└── staging/
    ├── common.yaml
    └── cluster-gcp-us-central.yaml

部署时通过 -f 参数按顺序加载,后加载的文件会合并并覆盖先加载的相同键:

helm upgrade --install my-app ./my-chart \
  -f values/base.yaml \
  -f values/production/common.yaml \
  -f values/production/cluster-aws-us-east.yaml

4.2 默认值设计:为覆盖而生

默认值不是“随便填一个值”,而是整个覆盖策略的锚点。好的默认值设计遵循以下原则:

  • 安全优先: 默认值应选择最保守、最安全的选项(如资源限制、副本数=1、TLS 开启)。
  • 可覆盖性: 每个默认值都应在模板中被引用,且允许被覆盖。不要硬编码在模板中。
  • 自文档化: 默认值文件本身就是最好的文档。每个键都应有注释说明用途、类型、可选值。
  • 最小化: 只包含必须的默认值。不要将 Chart 内部实现细节暴露为 Values 键。
默认值文件示例(base.yaml):
# 全局配置
global:
  environment: production
  clusterName: ""

# 应用副本数 - 默认 2 保证高可用
replicaCount: 2

# 镜像配置
image:
  repository: myregistry.io/my-app
  tag: latest
  pullPolicy: IfNotPresent

# 资源限制 - 默认保守值
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

# 服务配置
service:
  type: ClusterIP
  port: 8080

# 是否启用 TLS - 默认开启
tls:
  enabled: true
  certManager:
    issuer: letsencrypt-prod

4.3 覆盖策略:分层合并与优先级

Helm 的 Values 覆盖遵循明确的优先级规则。理解这个规则是避免“为什么我的配置没生效”的关键。

覆盖优先级(从低到高):

  1. Chart 内嵌的 values.yaml — 最低优先级,Chart 作者提供的出厂默认值。
  2. 父 Chart 的 values.yaml — 当 Chart 作为子 Chart 使用时,父 Chart 的默认值。
  3. -f 参数指定的文件 — 按命令行顺序,后加载的覆盖先加载的。
  4. --set 参数 — 显式指定,覆盖所有文件中的同名键。
  5. --set-string / --set-json — 强制类型覆盖,避免 --set 的类型推断问题。
常见陷阱: 使用 --set 覆盖嵌套对象时,Helm 会执行浅合并。例如 --set service.ports[0].port=9090 会替换整个 service.ports 数组,而不是只修改第一个元素。对于复杂结构,优先使用 -f 加载完整文件。

下图展示了分层 Values 的合并流程:

分层 Values 合并流程 Chart values.yaml base.yaml env/common.yaml 第1次合并:base 覆盖 Chart 默认 第2次合并:env 覆盖 base cluster-aws.yaml cluster-gcp.yaml --set 参数 最终渲染 Values

4.4 敏感信息分离:Secrets 与 Values 的边界

将数据库密码、、TLS 私钥等敏感信息直接写入 Values 文件是严重的安全反模式。Values 文件通常被提交到 Git 仓库,一旦泄露,后果不堪设想。

敏感信息分离的核心原则:

  • Values 中只存引用,不存明文。 例如 database.passwordRef: "secret://db-pass"。
  • 运行时注入,而非构建时嵌入。 敏感信息在 Pod 启动时从外部系统获取。
  • 使用专门的密钥管理工具。 如 Kubernetes Secrets、External Secrets Operator、Vault、AWS Secrets Manager。
推荐方案:External Secrets Operator + Helm

Chart 模板中不直接引用敏感值,而是引用一个 ExternalSecret 资源:

# templates/external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: {{ .Release.Name }}-db-secret
spec:
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: {{ .Release.Name }}-db-credentials
  data:
  - secretKey: password
    remoteRef:
      key: secret/data/{{ .Values.global.environment }}/database
      property: password

Values 文件中只声明引用路径:

# values/production/cluster-aws.yaml
global:
  environment: production
database:
  # 不存密码,只存引用路径
  secretName: my-app-db-credentials
  secretKey: password

Pod 通过 volume 或 envFrom 挂载 Secret:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: app
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: {{ .Values.database.secretName }}
              key: {{ .Values.database.secretKey }}

4.5 设计哲学总结:一个 Values 文件的“好”与“坏”

维度 ✅ 好的设计 ❌ 坏的设计
结构 分层、按职责拆分、文件数 3-8 个 单一文件超过 500 行、无注释
默认值 安全保守、自文档化、可覆盖 硬编码、无注释、暴露内部实现
覆盖 明确优先级、使用 -f 文件覆盖 大量使用 --set、嵌套对象浅合并踩坑
敏感信息 引用外部密钥、运行时注入 明文写在 values.yaml 中、提交到 Git
可维护性 修改一个集群不影响其他集群 修改全局值需要检查所有集群
一句话总结: Values 文件的设计哲学就是“为覆盖而生,为安全而分,为可读而层”。好的设计让多集群管理从“混乱”变为“有序”,从“手动”变为“自动化”。