背景介绍

在没有 Helm 之前,我们在 Kubernetes 上部署应用通常是这样的:

  1. 编写 Deployment.yaml, Service.yaml, Ingress.yaml 等。
  2. kubectl apply -f .
  3. 如果需要修改镜像版本或副本数,手动编辑 yaml 文件,再次 apply。

痛点:

  • 硬编码问题:参数(如副本数、镜像Tag)写死在 yaml 里,不同环境(Dev/Test/Prod)需要维护多套 yaml。
  • 版本管理难:如何一键回滚到上一个版本?很难追踪 yaml 的变更历史。
  • 共享困难:我写好的 Redis 高可用部署方案,很难打包给同事直接用。

Helm 的出现解决了这些问题。 它就像 Linux 下的 yumapt,将 Kubernetes 资源打包成 Chart,通过简单的命令即可实现安装、升级、回滚。
在这里插入图片描述

环境准备

在开始之前,请确保你的操作环境满足以下要求。本次实战演示基于 Linux 环境。

  • 操作系统:CentOS 7.9 或 Ubuntu 20.04+
  • Kubernetes 集群:v1.20+ (本文演示基于 v1.26)
  • 网络权限:能够访问外网(需要下载 Chart 包)
  • 权限:拥有集群的 admin 权限或足够的 RBAC 权限

Linux/macOS安装

# 下载最新版本
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

# 验证安装
helm version

补全插件

echo "source <(helm completion bash)" >> ~/.bashrc
source ~/.bashrc

核心概念速览

简单通过 3 个概念理清 Helm 逻辑:

  1. Chart(图表):即“软件包”,包含了一组定义 K8s 资源的文件(模板)。
  2. Repository(仓库):存放 Chart 的地方,类似 Docker Hub 或 Yum 源。
  3. Release(发行版):Chart 运行在集群中的一个实例。同一个 Chart 可以安装多次,每次安装都是一个不同的 Release(如 mysql-devmysql-prod)。
mychart/
├── Chart.yaml          # Chart元数据
├── values.yaml         # 默认配置值
├── charts/             # 子Chart目录
├── templates/          # 模板文件目录
│   ├── deployment.yaml
│   ├── service.yaml
│   └── _helpers.tpl    # 辅助模板
└── README.md

实战演练:部署 Nginx 应用

接下来,我们通过部署一个 Nginx 来体验 Helm 的完整生命周期:添加仓库 -> 搜索 -> 安装 -> 升级 -> 回滚 -> 卸载

添加 Chart 仓库

默认 Helm 没有添加仓库。Bitnami 是目前最活跃、质量最高的第三方仓库之一。

# 添加 bitnami 仓库
helm repo add bitnami https://charts.bitnami.com/bitnami

# 更新仓库缓存(类似 yum makecache)
helm repo update

搜索应用

helm search repo nginx

经验之谈:你会看到很多结果,通常选择 bitnami/nginx,关注 CHART VERSION (Chart包的版本) 和 APP VERSION (软件本身的版本)。

安装应用 (Install)

我们将 Nginx 安装到 web-test 命名空间中。

# 创建命名空间
kubectl create ns web-test

# 安装 Release,名称取名为 my-web
helm install my-web bitnami/nginx -n web-test

执行后,Helm 会输出安装说明(NOTES),告诉你如何获取 Service IP 等信息。

查看安装状态:

helm list -n web-test
kubectl get pods -n web-test

###自定义配置 (Values)

这步是运维的关键。默认安装的配置往往不符合生产需求(比如默认副本数是 1,我们需要 3)。

方法一:使用 --set 临时修改(适合少量修改)

helm upgrade my-web bitnami/nginx \
  --set replicaCount=2 \
  -n web-test

方法二:使用 values.yaml 文件(生产环境推荐)

首先,导出默认配置:

helm show values bitnami/nginx > values.yaml

然后,使用编辑器(vim)修改 values.yaml

# 修改如下内容
replicaCount: 3
image:
  tag: 1.25.0  # 假设我们要指定特定版本
service:
  type: NodePort # 方便外部访问测试
  nodePorts:
    http: 30080

执行升级:

helm upgrade my-web bitnami/nginx -f values.yaml -n web-test

版本回滚 (Rollback)

假设刚刚升级的 Nginx 1.25.0 有 Bug,老板要求立刻回滚。在没有 Helm 之前,你可能要改 yaml 重新 apply,现在只需:

# 1. 查看历史版本
helm history my-web -n web-test

# 输出:
# REVISION    UPDATED     STATUS      CHART           APP VERSION    DESCRIPTION
# 1           ...         superseded  nginx-15.0.0    1.23.0         Install complete
# 2           ...         superseded  nginx-15.0.0    1.23.0         Upgrade complete
# 3           ...         deployed    nginx-15.0.0    1.25.0         Upgrade complete

# 2. 回滚到版本 1(最初安装的版本)
helm rollback my-web 1 -n web-test

卸载 (Uninstall)

测试完毕,清理环境。

helm uninstall my-web -n web-test

创建自定义Chart

初始化Chart

# 创建新Chart
helm create myapp

# 查看生成的文件结构
tree myapp/

编写Chart.yaml

apiVersion: v2
name: myapp
description: A Helm chart for my application
type: application
version: 0.1.0
appVersion: "1.0.0"

# 依赖声明(可选)
dependencies:
  - name: mysql
    version: "8.0.0"
    repository: "https://charts.bitnami.com/bitnami"
    condition: mysql.enabled

模板开发示例

templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "myapp.labels" . | nindent 8 }}
        app.kubernetes.io/component: web
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: {{ .Values.service.port }}
              name: http
          env:
            - name: DATABASE_HOST
              value: {{ .Values.mysql.host | quote }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

values.yaml配置

replicaCount: 2

image:
  repository: nginx
  tag: "1.21-alpine"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

resources:
  limits:
    cpu: 200m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

mysql:
  enabled: true
  host: "myapp-mysql"

安装自定义Chart

# 依赖更新(如果声明了依赖)
helm dependency update myapp/

# 模板渲染测试
helm template myapp/ --debug

# 安装Chart
helm install my-release ./myapp \
  --namespace myapp \
  --create-namespace \
  --set replicaCount=3 \
  --set image.tag=1.22-alpine

# 查看渲染的YAML
helm get manifest my-release -n myapp

Chart维护与分发

打包和分发

# 打包Chart
helm package myapp/ --destination ./dist

# 创建本地仓库
mkdir repo
mv dist/*.tgz repo/
helm repo index repo/ --url https://mycompany.com/charts/

# 使用本地仓库
helm repo add myrepo ./repo
helm install myapp myrepo/myapp

Chart测试与验证

# 语法检查
helm lint myapp/

# 模板渲染测试
helm template myapp/ --values myapp/values.yaml

# 安装测试(dry-run)
helm install myapp ./myapp --dry-run --debug

# 升级测试
helm upgrade myapp ./myapp --dry-run --debug

模板编程技巧汇总

Helm 模板语法速查表

#=============================================================================
# 1. 变量访问
#=============================================================================
{{ .Values.replicaCount }}           # 访问 values
{{ .Release.Name }}                  # Release 名称
{{ .Release.Namespace }}             # 命名空间
{{ .Chart.Name }}                    # Chart 名称
{{ .Chart.Version }}                 # Chart 版本
{{ .Template.Name }}                 # 当前模板名称

#=============================================================================
# 2. 条件判断
#=============================================================================
# if-else
{{- if .Values.ingress.enabled }}
# 渲染 ingress
{{- else }}
# 不渲染
{{- end }}

# 复杂条件
{{- if and .Values.ingress.enabled (eq .Values.ingress.className "nginx") }}
# nginx ingress 特殊处理
{{- end }}

# 检查值是否存在
{{- if .Values.env }}
{{- if hasKey .Values "env" }}

# 空值检查
{{- if not (empty .Values.resources) }}

#=============================================================================
# 3. 循环
#=============================================================================
# range 循环
{{- range .Values.env }}
- name: {{ .name }}
  value: {{ .value | quote }}
{{- end }}

# range with index
{{- range $index, $host := .Values.ingress.hosts }}
- host: {{ $host.host }}
{{- end }}

# range over map
{{- range $key, $value := .Values.configMap.data }}
{{ $key }}: {{ $value }}
{{- end }}

#=============================================================================
# 4. 常用函数
#=============================================================================
{{ .Values.name | quote }}           # 加引号
{{ .Values.name | upper }}           # 大写
{{ .Values.name | lower }}           # 小写
{{ .Values.name | title }}           # 首字母大写
{{ .Values.name | trunc 63 }}        # 截断
{{ .Values.name | trimSuffix "-" }}  # 移除后缀
{{ .Values.name | indent 4 }}        # 缩进
{{ .Values.name | nindent 4 }}       # 换行+缩进
{{ .Values.data | toYaml }}          # 转 YAML
{{ .Values.data | toJson }}          # 转 JSON
{{ .Values.secret | b64enc }}        # Base64 编码
{{ .Values.secret | b64dec }}        # Base64 解码
{{ default "default" .Values.name }} # 默认值
{{ coalesce .Values.a .Values.b }}   # 返回第一个非空值

#=============================================================================
# 5. 逻辑运算
#=============================================================================
{{ and .Values.a .Values.b }}        # 且
{{ or .Values.a .Values.b }}         # 或
{{ not .Values.a }}                  # 非
{{ eq .Values.a "value" }}           # 等于
{{ ne .Values.a "value" }}           # 不等于
{{ lt .Values.a 10 }}                # 小于
{{ le .Values.a 10 }}                # 小于等于
{{ gt .Values.a 10 }}                # 大于
{{ ge .Values.a 10 }}                # 大于等于
{{ empty .Values.a }}                # 是否为空

#=============================================================================
# 6. 模板复用
#=============================================================================
# 定义模板
{{- define "myapp.labels" -}}
app: {{ .Chart.Name }}
{{- end }}

# 调用模板
{{ include "myapp.labels" . }}
{{ template "myapp.labels" . }}

# include vs template
# include 可以管道操作,template 不行
{{ include "myapp.labels" . | nindent 4 }}

#=============================================================================
# 7. 变量定义
#=============================================================================
{{- $name := .Values.name -}}
{{- $fullname := printf "%s-%s" .Release.Name .Chart.Name -}}

#=============================================================================
# 8. with 语句(改变作用域)
#=============================================================================
{{- with .Values.resources }}
resources:
  {{- toYaml . | nindent 2 }}
{{- end }}

#=============================================================================
# 9. 控制空白
#=============================================================================
{{- xxx }}    # 移除左边空白
{{ xxx -}}    # 移除右边空白
{{- xxx -}}   # 移除两边空白

实用技巧示例

# 技巧1: 根据条件设置不同的值
{{- $probePort := ternary .Values.service.port .Values.containerPort .Values.service.enabled -}}

# 技巧2: 合并多个 map
{{- $labels := merge .Values.podLabels (include "myapp.selectorLabels" . | fromYaml) -}}

# 技巧3: 动态生成环境变量
env:
  {{- range $key, $value := .Values.envVars }}
  - name: {{ $key }}
    value: {{ $value | quote }}
  {{- end }}

# 技巧4: 条件性地添加数组元素
containers:
  - name: main
    # ...
  {{- if .Values.sidecars }}
  {{- toYaml .Values.sidecars | nindent 2 }}
  {{- end }}

# 技巧5: 计算配置 checksum(用于触发滚动更新)
annotations:
  checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}

# 技巧6: 根据值类型处理
{{- if kindIs "string" .Values.command }}
command: {{ .Values.command }}
{{- else if kindIs "slice" .Values.command }}
command:
  {{- toYaml .Values.command | nindent 2 }}
{{- end }}

# 技巧7: 必需值验证
image: {{ required "image.repository is required" .Values.image.repository }}

# 技巧8: 失败时提供有意义的错误
{{- if and .Values.autoscaling.enabled (lt (int .Values.replicaCount) 1) }}
{{- fail "When autoscaling is enabled, replicaCount should be at least 1" }}
{{- end }}

更多推荐