vault backup: 2026-05-18 00:17:59

This commit is contained in:
hhs
2026-05-18 00:17:59 +08:00
parent bbea71f62b
commit f8bcbb9d37
26 changed files with 4828 additions and 712 deletions
@@ -0,0 +1,454 @@
---
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]] — 参考手册与决策指南