--- tags: [helm, kubernetes, templating, kustomize, devops] create time: 2026-05-17 10:00 --- # Helm 模板管理 ## 概述 本文档系统讲解 Helm Chart 的编写与管理实践。内容覆盖:Chart 骨架、values.yaml 参数化、Go 模板语法、多环境值覆盖、Chart 依赖管理、Hooks 机制,以及与 ArgoCD 的集成方式。架构决策参见 [[../03-CICD与GitOps]]。 ## 为什么需要 Helm? > [!question] 每个服务几十个 YAML 文件,怎么管理? > > 一个典型的 `order-service` 部署包含:Deployment、Service、Ingress、ConfigMap、HPA、NetworkPolicy、PDB……当你有 50 个服务,每个都要维护这么一堆模板——**这是重复劳动的噩梦**。 Helm 提供了两个核心抽象: | 概念 | 类比 | 说明 | |------|------|------| | **Chart** | NPM 包 / Maven jar | 打包格式,定义"我要部署什么" | | **Release** | npm install 的实例 | 运行实例,同一个 Chart 可以有多个 Release(staging/prod) | ```bash # 创建 Chart 骨架 helm create order-service # 目录结构一览 charts/order-service/ ├── Chart.yaml # 元信息 (name, version, description) ├── values.yaml # 默认值 —— 你唯一需要经常改的文件 ├── templates/ # Go 模板文件 │ ├── deployment.yaml │ ├── service.yaml │ └── ingress.yaml └── .helmignore # 类似 .gitignore ``` ### Helm Release 生命周期 ```mermaid flowchart LR A["install"] --> B["running"] C["upgrade"] --> B D["rollback"] --> B E["uninstall"] --> B style A fill:#e1f5fe style C fill:#fff3e0 style D fill:#fce4ec style E fill:#f3e5f5 ``` ## 第一步:让配置全部参数化 **values.yaml** 中存放所有可变参数,遵循"一处定义、多处引用"原则: ```yaml # values.yaml replicaCount: 2 image: repository: registry.example.com/order-service tag: "v1.2.3" pullPolicy: IfNotPresent service: type: ClusterIP port: 8080 resources: requests: cpu: 200m memory: 256Mi limits: cpu: 500m memory: 512Mi autoscaling: enabled: true minReplicas: 3 maxReplicas: 20 targetCPUUtilizationPercentage: 75 ``` **templates/deployment.yaml** 中使用 `{{ }}` 引用: ```yaml # templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }} namespace: {{ .Release.Namespace }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Release.Name }} template: spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} resources: {{ toYaml .Values.resources | nindent 12 }} ``` > [!tip] 两个关键技巧 > > 1. **`toYaml .X | nindent 12`**:将嵌套的对象序列化为 YAML 并缩进。如果直接写 `{{ .Values.resources }}`,输出会压缩成一行,Kubernetes 无法解析。 > 2. **`-` 修剪空白**:`{{- if ...` 和 `{{- end }}` 中的 `-` 会消除模板前后多余的换行符,避免生成空行导致 YAML 格式错误。 ### Helm 内置对象速查 | Helm 对象 | 访问方式 | 用途 | |-----------|----------|------| | `.Release` | `.Release.Name`, `.Release.Namespace` | 当前发布实例的信息 | | `.Chart` | `.Chart.Name`, `.Chart.Version` | Chart 本身的元信息 | | `.Values` | `.Values.replicaCount` 等 | 用户传入的值(合并了 values.yaml + --set) | | `.Capabilities` | `.Capabilities.KubeVersion` | 集群 API 版本信息 | | `.Files` | `.Files.Get "config.ini"` | 读取同目录下的非模板文件 | | `.Notes` | 渲染后显示给用户的提示文本 | Post-install 使用说明 | ## 第二步:多环境值覆盖 通过 **values 文件叠加**实现不同环境的差异化: ```bash # Staging 使用命令行临时覆盖 helm upgrade --install order-service ./charts/order-service \ --namespace=staging \ --set image.tag=v1.2.4-dev # Production 叠加额外值文件 helm upgrade --install order-service ./charts/order-service \ --namespace=production \ --values charts/order-service/values.prod.yaml \ --set image.tag=v1.2.3 ``` 典型 `values.prod.yaml`(只写与默认值不同的部分): ```yaml replicaCount: 5 resources: requests: cpu: 500m memory: 512Mi limits: cpu: "2" memory: 2Gi autoscaling: minReplicas: 5 maxReplicas: 50 ``` > [!tip] Values 文件合并优先级(低 → 高) > > 1. `values.yaml` — 默认值 > 2. `values.prod.yaml` — 环境变量覆盖文件 > 3. `--set image.tag=xxx` — 命令行参数(最高优先级) ```mermaid flowchart TD A["values.yaml"] --> D["合并结果"] B["values.prod.yaml"] --> D C["--set image.tag=v1.2.3"] --> D D --> E[".Values"] ``` > [!warning] 常见陷阱:JSON Patch > > 当使用 `--set` 修改嵌套字段时,Helm 使用 JSON Patch,意味着它会**替换整个对象**而非合并。例如 `--set resources.limits.cpu="1"` 会将 `memory` 字段丢弃。对于复杂嵌套,优先使用独立的 values 文件。 ## 第三步:条件渲染与循环 ### 条件渲染 用 Helm 的条件语法处理可选资源: ```yaml # templates/ingress.yaml {{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Release.Name }} spec: rules: - host: {{ .Values.ingress.host | quote }} http: paths: - path: / pathType: Prefix backend: service: name: {{ .Release.Name }} port: number: {{ .Values.service.port }} {{- end }} ``` > [!note] `quote` 管道函数 > > 当值的类型不确定时(可能是字符串也可能是数字),加 `| quote` 确保输出带引号,避免 `true/false` 被 YAML 解析为布尔值。 ### Range 循环生成多个资源 当需要批量创建多个 ServiceMonitor、ConfigMap 等时,`range` 非常有用: ```yaml # templates/servicemonitor.yaml {{- range $key, $value := .Values.extraServices }} --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: {{ $.Release.Name }}-{{ $key }} spec: selector: matchLabels: app: {{ $.Release.Name }} endpoints: - port: {{ $value.port }} interval: {{ $value.interval | default "30s" }} {{- end }} ``` 对应的 values: ```yaml extraServices: grpc-exporter: port: grpc interval: 15s prometheus-path: port: metrics interval: 10s ``` > [!example] 生成结果 > > 上面这段模板会为每个 `extraServices` 条目生成一个独立的 ServiceMonitor。注意 `$key` 绑定到键名(`grpc-exporter`),而 `$value` 绑定到对应对象。使用 `$.Release.Name`(带美元前缀)是因为在 range 内部 `.` 已被重绑定。 ## 第四步:复用模板片段 随着 Chart 膨胀,`deployment.yaml` 和 `service.yaml` 之间会产生大量重复逻辑。Helm 提供 `_helpers.tpl` 来统一管理可复用的模板片段。 ### _helpers.tpl 标准写法 ```yaml {{/* Common labels — 所有资源统一标注 */}} {{- define "order.labels" -}} app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end -}} {{/* Selector labels — Deployment selector 必须使用固定值 */}} {{- define "order.selectorLabels" -}} app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} ``` ### 在模板中引用 ```yaml # templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }} labels: {{- include "order.labels" . | nindent 4 }} spec: selector: matchLabels: {{- include "order.selectorLabels" . | nindent 6 }} ``` > [!tip] include vs template > > - `include "name" .`:**返回值**,可通过 `| nindent` 继续管道处理 —— 适合注入 label、annotation > - `template "name" .`:**直接输出**,不返回结果 —— 很少用,通常只在特殊情况下需要 ## Chart 依赖与仓库管理 大型项目往往不会从零编写所有模板,而是复用社区已有的 Chart。 ### 声明子依赖 ```yaml # Chart.yaml apiVersion: v2 name: order-service type: application version: 1.2.0 appVersion: "1.2.3" dependencies: - name: postgresql version: "15.0.0" repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled - name: redis version: "18.0.0" repository: https://charts.bitnami.com/bitnami condition: redis.enabled ``` - `condition` 告诉 Helm:当 values 中对应路径为 `false` 时,跳过安装该子 Chart。 - 子 Chart 的 values 通过 `postgresql.xxx` / `redis.xxx` 命名空间传入。 ### 常用命令 ```bash helm dependency update ./charts/order-service # 拉取 / 更新子依赖 helm dependency build ./charts/order-service # 仅从 Chart.lock 构建(无网络变更时使用) helm repo add bitnami https://charts.bitnami.com/bitnami helm search repo prometheus # 搜索公开 Chart ``` > [!note] Chart.yaml 的 apiVersion > > - `apiVersion: v1`(Helm 2):依赖写在 `dependencies:` 数组里,语义不同 > - `apiVersion: v2`(Helm 3):引入 `requirements.yaml` 的概念整合到 Chart.yaml 自身,推荐使用 v2 ## 与 ArgoCD 集成 ArgoCD 原生支持将 Helm Chart 作为 Application source,自动解析 `values.yaml`: ```yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: order-service spec: source: repoURL: https://github.com/team/k8s-manifests.git targetRevision: main path: charts/order-service helm: parameters: - name: image.tag value: v1.2.3 - name: replicaCount value: "5" ``` > [!tip] ArgoCD + Helm 进阶用法 > > - **`helm.valueFrom`**:从 ConfigMap 或 Secret 取值,避免把敏感值暴露在 Git 中 > - **`helm.fileParameters`**:从文件加载大段配置值,适合 CI/CD 流水线场景 > - **`helm.ignoreMissingValueFiles: true`**:允许某些 values 文件按环境选择性存在 ## Hooks:在关键时机执行自定义逻辑 Helm Hooks 允许你在 Release 的生命周期事件中挂载自定义 Job 或 Pod: ```yaml # templates/migrate-db.yaml {{- if .Values.dbMigration.enabled }} apiVersion: batch/v1 kind: Job metadata: name: {{ .Release.Name }}-db-migrate annotations: "hooks.helm.sh/hook": pre-upgrade,pre-install "hooks.helm.sh/hook-delete-policy": hook-succeeded spec: template: spec: containers: - name: migrate image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" command: ["./migrate", "up"] restartPolicy: Never {{- end }} ``` | Hook 事件 | 触发时机 | |-----------|----------| | `pre-install` | 资源创建之前(如数据库迁移) | | `post-install` | 资源创建之后(如初始化数据) | | `pre-upgrade` | 升级操作之前 | | `post-upgrade` | 升级操作之后 | | `pre-delete` | 删除操作之前(如优雅停机通知) | | `post-delete` | 删除操作之后 | ## 调试与测试 ### 可视化渲染结果 ```bash # 查看渲染后的纯 YAML(不做实际部署) helm template my-release ./charts/order-service # 指定命名空间和 values 文件 helm template my-release ./charts/order-service \ --namespace=staging \ -f values.staging.yaml ``` > [!tip] 调试三件套 > > 1. **`helm lint ./charts/order-service`**:静态检查,快速定位语法问题 > 2. **`helm template`**:看渲染后的全量 YAML,逐段排查问题 > 3. **`helm diff upgrade --install ...`**:配合 [helm-diff 插件](https://github.com/helm/diff),对比升级前后的差异 ### Dry Run ```bash # 模拟部署(服务端校验,但不会真正改变集群状态) helm upgrade --install my-release ./charts/order-service \ --dry-run --debug ``` ## 最佳实践 | 原则 | 具体做法 | |------|---------| | **单一真相源** | 所有可配参数集中在 values.yaml,模板层只做引用不做硬编码 | | **Helper 集中管理** | 公共 labels / selectors / annotations 一律放在 `_helpers.tpl` | | **条件最小化** | 能用 defaults 解决的就不加 `if`;减少分支能大幅降低测试复杂度 | | **版本号分离** | `Chart.yaml` 的 `version` 管 Chart 本身迭代,`appVersion` 管应用版本 | | **先 lint 再 push** | CI 中加入 `helm lint` + `helm template --strict` 门禁 | | **值文件只写差异** | `values.prod.yaml` 只覆盖与默认值不同的字段,保持可读性 | ## 何时用裸 YAML vs Helm vs Kustomize? | 场景 | 推荐方案 | 理由 | |------|---------|------| | < 10 个服务 | Kustomize / 裸 YAML | 复杂度高于收益 | | 10~50 个服务 | Helm | 模板复用价值明显 | | > 50 个服务 | Helm + Kustomize overlays | Helm 管模板,Kustomize 管环境差异 | > [!note] Kustomize 的定位 > > Kustomize 不依赖 Go templating,更轻量。适合"基于同一套模板,按环境覆盖差异配置"的场景。 > > **常见组合**:Helm 生成基础模板,Kustomize overlay 处理 staging/prod 差异。 ## 关联笔记 - [[01-CICD基础与实践]] — CI/CD Pipeline 的搭建与实践 - [[02-GitOps与ArgoCD]] — GitOps 工作流与 ArgoCD - [[04-安全与发布策略]] — 安全管理与发布决策 - [[../03-CICD与GitOps]] — 参考手册与决策指南