Helm高级:Chart开发、模板语法、测试与包管理实战

深入讲解 Helm Chart 开发、Go Template 高级语法、Values 管理、Hooks、Chart 测试、库 Chart、仓库管理以及与 Kustomize 的对比选型。

Helm 是 Kubernetes 的包管理器,将一组 K8s 资源模板化为可复用的 Chart。掌握 Helm 不仅能简化部署,还能实现参数化配置、多环境管理和版本控制。


目录


1. Chart 结构详解

myapp/
├── Chart.yaml          # Chart 元数据
├── values.yaml         # 默认配置值
├── values-dev.yaml     # 开发环境覆盖
├── values-prod.yaml    # 生产环境覆盖
├── charts/             # 依赖的 Chart
│   └── postgresql-12.1.0.tgz
├── templates/          # K8s 资源模板
│   ├── _helpers.tpl    # 命名辅助函数
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   ├── serviceaccount.yaml
│   └── NOTES.txt       # 安装后提示信息
└── tests/              # Chart 测试
    └── test-connection.yaml

Chart.yaml

apiVersion: v2           # v2 = Helm 3, v1 = Helm 2(已废弃)
name: myapp
description: 我的应用 Helm Chart
type: application        # application 或 library
version: 1.2.3           # Chart 版本(语义化)
appVersion: "2.1.0"      # 应用版本
kubeVersion: ">= 1.24.0"  # 支持的 K8s 版本
home: https://example.com
sources:
  - https://github.com/example/myapp
maintainers:
  - name: Leeting Yan
    email: dev@example.com
dependencies:
  - name: postgresql
    version: "~12.1.0"      # ~ 允许 patch 更新
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled
    alias: db
  - name: redis
    version: "18.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled

语义化版本约束

约束含义示例
1.2.3精确版本仅 1.2.3
~1.2.3允许 patch 更新1.2.x,x >= 3
~1.2允许 patch 和 minor1.x.x,x >= 2.0
^1.2.3允许不破坏兼容的更新1.x.x
>= 1.2.3大于等于1.2.3 及以上

_helpers.tpl

{{/* 生成全名(包含 release 名) */}}
{{- define "myapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/* 生成标准标签 */}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.chart" . }}
{{ include "myapp.selectorLabels" . }}
app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/* 选择器标签 */}}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

2. Values 多环境管理

默认 values.yaml

# values.yaml
replicaCount: 2

image:
  repository: registry.example.com/myapp
  tag: ""
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 8080

ingress:
  enabled: false
  className: nginx
  hosts:
    - host: api.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: api-tls
      hosts:
        - api.example.com

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 100m
    memory: 128Mi

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

nodeSelector: {}
tolerations: []
affinity: {}

# 数据库依赖配置
db:
  enabled: true
  auth:
    username: appuser
    password: ""
    database: appdb

环境覆盖

# values-dev.yaml(开发环境)
replicaCount: 1

resources:
  limits:
    cpu: 200m
    memory: 256Mi
  requests:
    cpu: 50m
    memory: 64Mi

ingress:
  enabled: true
  hosts:
    - host: api-dev.example.com

db:
  auth:
    password: "dev-password"
# values-prod.yaml(生产环境)
replicaCount: 3

resources:
  limits:
    cpu: 2000m
    memory: 2Gi
  requests:
    cpu: 500m
    memory: 512Mi

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20

ingress:
  enabled: true
  hosts:
    - host: api.example.com
  tls:
    - secretName: api-tls-prod

db:
  auth:
    password: ""  # 生产密码通过 --set 或外部 Secret 注入

安装命令

# 开发环境
helm install myapp-dev ./myapp -f values-dev.yaml

# 生产环境(密码通过命令行注入,不存于文件)
helm install myapp-prod ./myapp \
  -f values-prod.yaml \
  --set db.auth.password="$(vault read -field=password secret/db)"

# 升级
helm upgrade myapp-prod ./myapp -f values-prod.yaml

# 查看渲染后的 YAML(不实际部署)
helm template myapp ./myapp -f values-prod.yaml | less

# 对比升级差异
helm diff upgrade myapp-prod ./myapp -f values-prod.yaml

3. Go Template 高级语法

条件判断

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "myapp.fullname" . }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- if .Values.ingress.className }}
  ingressClassName: {{ .Values.ingress.className }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType }}
            backend:
              service:
                name: {{ include "myapp.fullname" $ }}
                port:
                  number: {{ $.Values.service.port }}
          {{- end }}
    {{- end }}
{{- end }}

循环与变量

{{- range $key, $value := .Values.env }}
- name: {{ $key }}
  value: {{ $value | quote }}
{{- end }}

# 数组处理
{{- range .Values.service.ports }}
- port: {{ .port }}
  targetPort: {{ .targetPort | default .port }}
  protocol: {{ .protocol | default "TCP" }}
  name: {{ .name }}
{{- end }}

默认值与管道

# 默认值
image: {{ .Values.image.tag | default .Chart.AppVersion }}

# 缩进处理
{{- toYaml .Values.resources | nindent 10 }}

# 引用处理
{{ .Values.database.password | b64enc | quote }}

# 全局值访问
{{ $.Values.global.environment }}

内置函数

函数作用
default设置默认值
quote加引号
upper / lower大小写转换
b64enc / b64decBase64 编码/解码
trim / trunc截断字符串
sha256sum计算哈希
toYaml / fromYamlYAML 转换
nindent换行 + 缩进
required必填值校验

required 校验

image:
  tag: {{ required "image.tag is required" .Values.image.tag }}

image.tag 为空,Helm 渲染时报错:

Error: execution error at (myapp/templates/deployment.yaml:15:14): image.tag is required

4. Chart Hook

Hooks 在 Chart 生命周期的特定阶段执行:

Hook触发时机用途
pre-install安装前数据库初始化、前置检查
post-install安装后发送通知、健康检查
pre-delete删除前备份数据、优雅关闭
post-delete删除后清理外部资源
pre-upgrade升级前数据库迁移、兼容性检查
post-upgrade升级后验证新版本、清理旧版本
pre-rollback回滚前备份当前状态
post-rollback回滚后验证回滚结果
test测试时连接测试、功能测试

Hook 示例:数据库迁移

# templates/hook-migration.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "myapp.fullname" . }}-db-migrate
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command: ["python", "manage.py", "migrate"]
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: {{ include "myapp.fullname" . }}-db
                  key: url

权重hook-weight 数值越小越早执行(默认 0)。可用于控制有依赖关系的 hooks 顺序。

删除策略

  • before-hook-creation:创建新的前删除旧的
  • hook-succeeded:成功完成后删除
  • hook-failed:失败后删除
  • 多个策略用逗号分隔

5. Chart 测试

测试 Pod

# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "myapp.fullname" . }}-test-connection"
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['--timeout=5', '-O-', '{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/healthz']
  restartPolicy: Never

执行测试:

helm test myapp-prod

Helm Unittest 框架

更强大的单元测试框架:

# tests/deployment_test.yaml
suite: test deployment
templates:
  - deployment.yaml
tests:
  - it: should have correct replica count
    set:
      replicaCount: 5
    asserts:
      - equal:
          path: spec.replicas
          value: 5

  - it: should mount configmap
    asserts:
      - contains:
          path: spec.template.spec.volumes
          content:
            name: config
            configMap:
              name: myapp-config
helm plugin install https://github.com/helm-unittest/helm-unittest.git
helm unittest ./myapp

6. 库 Chart 与子 Chart

库 Chart(Library Chart)

复用通用模板,不部署任何资源:

# Chart.yaml (library)
apiVersion: v2
name: common
type: library
version: 1.0.0
# templates/_deployment.tpl
{{- define "common.deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.name }}
spec:
  replicas: {{ .Values.replicas }}
  selector:
    matchLabels:
      app: {{ .Values.name }}
  template:
    metadata:
      labels:
        app: {{ .Values.name }}
    spec:
      containers:
        - name: app
          image: {{ .Values.image }}:{{ .Values.tag }}
{{- end }}

在主 Chart 中使用:

# Chart.yaml
dependencies:
  - name: common
    version: "1.x.x"
    repository: https://charts.example.com

---
# templates/deployment.yaml
{{- include "common.deployment" . }}

子 Chart(Subcharts)

父 Chart 自动包含 charts/ 目录下的依赖 Chart,通过 values.yaml 配置:

# values.yaml (父 Chart)
# 配置子 Chart postgresql
postgresql:
  enabled: true
  auth:
    username: appuser
    password: secret
    database: appdb
  primary:
    persistence:
      size: 10Gi

7. Chart 仓库管理

自建 Chart Museum

# 安装 ChartMuseum
helm repo add chartmuseum https://chartmuseum.github.io/charts
helm install chartmuseum chartmuseum/chartmuseum

# 推送 Chart
helm package ./myapp          # 打包为 myapp-1.2.3.tgz
helm cm-push myapp-1.2.3.tgz my-repo

# 更新索引
helm repo update

OCI 注册表存储(推荐)

Helm 3.8+ 支持将 Chart 存储在 OCI 注册表中(如 Harbor、Docker Hub):

# 登录 OCI 注册表
helm registry login registry.example.com

# 保存 Chart 为 OCI 格式
helm package ./myapp

# 推送
helm push myapp-1.2.3.tgz oci://registry.example.com/charts

# 安装
helm install myapp oci://registry.example.com/charts/myapp --version 1.2.3

优势

  • 无需单独维护 Chart Museum 服务
  • 复用已有的容器镜像仓库基础设施
  • Harbor 支持签名验证(cosign)

8. Helm vs Kustomize

特性HelmKustomize
核心能力模板化 + 包管理基于覆盖的 YAML 定制
学习曲线中(Go Template)低(纯 YAML)
复用方式Chart + ValuesBase + Overlay
依赖管理内置
条件资源支持(if/else)不支持(需手动注释)
多环境values-{env}.yamloverlays/{env}/
版本管理Chart 版本Git 版本
生态系统丰富(ArtifactHub)K8s 内置

选型建议

  • 需要包管理、依赖、版本控制Helm
  • 只需要环境差异化配置Kustomize
  • 两者可以结合:Helm 管理依赖,Kustomize 做最终环境覆盖

Helm + Kustomize 结合

# 1. Helm 渲染基础模板
helm template myapp ./myapp -f values-base.yaml > base.yaml

# 2. Kustomize 叠加环境配置
kustomize build overlays/prod/ > final.yaml

# 3. 部署
kubectl apply -f final.yaml

ArgoCD 和 Flux 都支持 Helm + Kustomize 的组合方式。


9. 生产环境最佳实践

版本与发布管理

# 语义化版本发布流程
# 1. 更新版本号
sed -i 's/version: .*/version: 1.3.0/' Chart.yaml

# 2. 更新 CHANGELOG
# 3. 打包
helm package ./myapp

# 4. 签名(可选)
helm package --sign --key 'mykey' ./myapp

# 5. 推送到仓库
helm cm-push myapp-1.3.0.tgz my-repo

# 6. 标记 Git
git tag -a helm-1.3.0 -m "Release Chart 1.3.0"
git push origin helm-1.3.0

安全加固

  • Secrets 管理:绝不将真实密码放入 values.yaml,使用 Vault/Sealed Secrets
  • 镜像签名:启用 cosign 验证镜像签名
  • Chart 签名:用 PGP 签名 Chart,安装时验证 --verify
  • 最小权限:ServiceAccount 仅授予 Helm/Tiller 所需权限(Helm 3 无需 Tiller,已解决此问题)

CI/CD 集成

# GitHub Actions 示例
- name: Lint Chart
  run: helm lint ./myapp

- name: Template Check
  run: helm template myapp ./myapp -f values-prod.yaml > /dev/null

- name: Unit Test
  run: helm unittest ./myapp

- name: Package
  run: helm package ./myapp

- name: Push to Registry
  run: |
    helm registry login ${{ secrets.REGISTRY }} -u ${{ secrets.USER }} -p ${{ secrets.PASS }}
    helm push myapp-*.tgz oci://${{ secrets.REGISTRY }}/charts

总结

主题核心要点
Chart 结构Chart.yaml + values.yaml + templates/ + charts/
Values 管理默认 + 环境覆盖文件,secret 通过命令行注入
模板语法Go Template + Sprig 函数,_helpers.tpl 复用
Hooks生命周期钩子用于迁移、清理、验证
测试helm test + helm unittest 框架
库 Charttype: library 复用通用模板
仓库OCI 注册表存储是现代推荐方案
与 KustomizeHelm 管模板和依赖,Kustomize 管环境覆盖

Helm 不仅是一个部署工具,更是 K8s 应用交付的标准化基石。统一 Chart 规范、自动化测试、与 GitOps 的结合,是实现大规模集群应用管理的关键。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「云原生」更多文章

  1. Kubernetes多集群联邦:Karmada、Crossplane与Istio多集群实战
  2. CNCF云原生技术全景图:从毕业项目到前沿方向
  3. 容器运行时深度解析:从runc到containerd到安全容器