455 lines
13 KiB
Markdown
455 lines
13 KiB
Markdown
---
|
||
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]] — 参考手册与决策指南
|