Files
cs-note/hhs/MS/05-部署运维/03-CICD与GitOps/03-Helm模板管理.md
T
2026-05-24 11:42:38 +08:00

455 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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]] — 参考手册与决策指南