Helm 从概念到实战:以一个普通 Web 应用为例

目录

    • [一、为什么需要 Helm](#一、为什么需要 Helm)
    • 二、核心概念模型
      • [2.1 一张关系图](#2.1 一张关系图)
      • [2.2 概念对照表](#2.2 概念对照表)
      • [2.3 几个最容易混淆的点](#2.3 几个最容易混淆的点)
    • 三、Chart:包的结构
      • [3.1 标准目录](#3.1 标准目录)
      • [3.2 Chart.yaml 字段](#3.2 Chart.yaml 字段)
      • [3.3 Package 与 Repository](#3.3 Package 与 Repository)
    • 四、values:参数的来源与优先级
      • [4.1 优先级(从低到高)](#4.1 优先级(从低到高))
      • [4.2 使用说明与坑](#4.2 使用说明与坑)
    • 五、templates:渲染机制
      • [5.1 模板长什么样](#5.1 模板长什么样)
      • [5.2 把逻辑收敛到 `_helpers.tpl`](#5.2 把逻辑收敛到 _helpers.tpl)
      • [5.3 条件渲染与 NOTES.txt](#5.3 条件渲染与 NOTES.txt)
      • [5.4 渲染流程](#5.4 渲染流程)
    • 六、命令速查(按使用阶段)
      • [6.1 环境与仓库](#6.1 环境与仓库)
      • [6.2 本地开发与校验(无需连集群)](#6.2 本地开发与校验(无需连集群))
      • [6.3 安装与升级](#6.3 安装与升级)
      • [6.4 查看、回滚与卸载](#6.4 查看、回滚与卸载)
    • [七、实战:从零发布一个普通 Web 应用](#七、实战:从零发布一个普通 Web 应用)
      • [7.1 前置条件](#7.1 前置条件)
      • [7.2 创建并定制 Chart](#7.2 创建并定制 Chart)
      • [7.3 拆分环境 values(只写差异)](#7.3 拆分环境 values(只写差异))
      • [7.4 四步发布法](#7.4 四步发布法)
      • [7.5 发布后验证](#7.5 发布后验证)
      • [7.6 升级、回滚与卸载](#7.6 升级、回滚与卸载)
      • [7.7 打包分发给别的集群](#7.7 打包分发给别的集群)
    • 八、进阶话题
      • [8.1 敏感信息不要进 ConfigMap](#8.1 敏感信息不要进 ConfigMap)
      • [8.2 Subchart 与 global](#8.2 Subchart 与 global)
      • [8.3 按集群版本选择 apiVersion](#8.3 按集群版本选择 apiVersion)
      • [8.4 CI/CD 建议](#8.4 CI/CD 建议)
    • 九、常见问题排查
    • 十、小结

适用版本:Helm 3.x、Kubernetes 1.19+

全文以一个虚构的普通 Web 服务 myapp 为贯穿示例:镜像 registry.example.com/team/myapp,容器端口 8080,部署到 app-dev / app-prod 两个命名空间。

约定:命令均在项目根目录执行,Chart 位于 ./myapp;示例为 Bash,Windows 下可用 Git Bash 或 WSL。


一、为什么需要 Helm

一个 Web 服务上 K8s,往往要维护 Deployment、Service、ConfigMap、ServiceAccount、Ingress、HPA 等七八个 YAML。真实环境里还要面对:

  • 多环境:开发/测试/生产的镜像 tag、副本数、资源配额、域名都不同,复制粘贴几套 YAML 后很快失控;
  • 配置散落:应用参数一部分写在环境变量,一部分写在配置文件,改一处漏一处;
  • 发布无状态 :kubectl apply -f 之后没有「上一次发布是什么」的记录,回滚只能靠 Git 翻历史。

Helm 是 Kubernetes 的包管理器 (类比 apt / npm)。它把一组 YAML 模板化、参数化、版本化:参数集中在一个文件里,模板按参数渲染,每次发布在集群里留下一个可查询、可回滚的快照。

以 myapp 为例,同一份 Chart 部署到两个环境,只需换一个 values 文件:

bash 复制代码
helm upgrade --install myapp ./myapp -n app-dev  -f values-dev.yaml
helm upgrade --install myapp ./myapp -n app-prod -f values-prod.yaml

镜像 tag、副本数、资源配额、是否启用 Ingress 全部由参数决定,模板只写一遍。

Helm 3 是纯客户端工具,集群内不再需要 Tiller,直接复用本地 kubeconfig 与集群 RBAC。


二、核心概念模型

2.1 一张关系图

#mermaid-svg-PQMqkybVjbTJ5JM9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PQMqkybVjbTJ5JM9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PQMqkybVjbTJ5JM9 .error-icon{fill:#552222;}#mermaid-svg-PQMqkybVjbTJ5JM9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PQMqkybVjbTJ5JM9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PQMqkybVjbTJ5JM9 .marker.cross{stroke:#333333;}#mermaid-svg-PQMqkybVjbTJ5JM9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PQMqkybVjbTJ5JM9 p{margin:0;}#mermaid-svg-PQMqkybVjbTJ5JM9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PQMqkybVjbTJ5JM9 .cluster-label text{fill:#333;}#mermaid-svg-PQMqkybVjbTJ5JM9 .cluster-label span{color:#333;}#mermaid-svg-PQMqkybVjbTJ5JM9 .cluster-label span p{background-color:transparent;}#mermaid-svg-PQMqkybVjbTJ5JM9 .label text,#mermaid-svg-PQMqkybVjbTJ5JM9 span{fill:#333;color:#333;}#mermaid-svg-PQMqkybVjbTJ5JM9 .node rect,#mermaid-svg-PQMqkybVjbTJ5JM9 .node circle,#mermaid-svg-PQMqkybVjbTJ5JM9 .node ellipse,#mermaid-svg-PQMqkybVjbTJ5JM9 .node polygon,#mermaid-svg-PQMqkybVjbTJ5JM9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PQMqkybVjbTJ5JM9 .rough-node .label text,#mermaid-svg-PQMqkybVjbTJ5JM9 .node .label text,#mermaid-svg-PQMqkybVjbTJ5JM9 .image-shape .label,#mermaid-svg-PQMqkybVjbTJ5JM9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-PQMqkybVjbTJ5JM9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PQMqkybVjbTJ5JM9 .rough-node .label,#mermaid-svg-PQMqkybVjbTJ5JM9 .node .label,#mermaid-svg-PQMqkybVjbTJ5JM9 .image-shape .label,#mermaid-svg-PQMqkybVjbTJ5JM9 .icon-shape .label{text-align:center;}#mermaid-svg-PQMqkybVjbTJ5JM9 .node.clickable{cursor:pointer;}#mermaid-svg-PQMqkybVjbTJ5JM9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PQMqkybVjbTJ5JM9 .arrowheadPath{fill:#333333;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PQMqkybVjbTJ5JM9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PQMqkybVjbTJ5JM9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PQMqkybVjbTJ5JM9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PQMqkybVjbTJ5JM9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PQMqkybVjbTJ5JM9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PQMqkybVjbTJ5JM9 .cluster text{fill:#333;}#mermaid-svg-PQMqkybVjbTJ5JM9 .cluster span{color:#333;}#mermaid-svg-PQMqkybVjbTJ5JM9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PQMqkybVjbTJ5JM9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PQMqkybVjbTJ5JM9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-PQMqkybVjbTJ5JM9 .icon-shape,#mermaid-svg-PQMqkybVjbTJ5JM9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PQMqkybVjbTJ5JM9 .icon-shape p,#mermaid-svg-PQMqkybVjbTJ5JM9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PQMqkybVjbTJ5JM9 .icon-shape .label rect,#mermaid-svg-PQMqkybVjbTJ5JM9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PQMqkybVjbTJ5JM9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PQMqkybVjbTJ5JM9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PQMqkybVjbTJ5JM9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} helm package
helm push
提交集群
每次变更
Chart 目录

Chart.yaml + values.yaml + templates/
Package

name-version.tgz
Repository / OCI Registry

index.yaml
values 输入

values.yaml + -f + --set
helm install / upgrade

模板渲染
K8s manifests
Release

集群资源 + 发布记录
Revision 1..N

用编程类比:Chart 是类,values 是构造参数,templates 是方法体,Release 是对象实例,Revision 是实例的历史快照,Package 是编译产物。

2.2 概念对照表

概念 说明 存在形态
Chart 一个应用包,含元数据、默认值、模板 目录
Package Chart 的分发形态,本质是带 Chart.yaml 的压缩包 .tgz 文件
Repository 存放并索引 Chart 的 HTTP 服务(index.yaml + 若干 .tgz) 远程服务 + 本地缓存
Release Chart 在某命名空间的一次运行实例,<名称> + <命名空间> 唯一 集群资源 + 一条记录
Revision 每次 install/upgrade/rollback 产生的递增版本号 Secret 中的发布记录
values 渲染时的输入参数,多层合并的结果 YAML / 命令行
templates Go template + Sprig,消费 values 生成清单 templates/*.yaml
Hook 带 helm.sh/hook 注解的资源,在发布生命周期特定时点执行 Job / ConfigMap 等

2.3 几个最容易混淆的点

易混淆 结论
Chart vs Package 同一东西的「源码目录」与「制品文件」,helm install 两者都能接
version vs appVersion 前者是 Chart 版本(决定 .tgz 文件名),后者是应用版本标记,只有模板显式引用它时才会影响镜像
Release vs Revision 实例 vs 该实例的历史版本;rollback 会新增 Revision,不是把编号退回去
values.yaml vs .Values 默认参数文件 vs 多层合并后的最终对象
helm template vs --dry-run 前者纯本地渲染不连集群;后者仍与 API Server 交互做校验

三、Chart:包的结构

3.1 标准目录

执行 helm create myapp 会生成如下脚手架,这也是社区 Chart 的通用结构:

复制代码
myapp/
├── Chart.yaml                # 必需:元数据
├── values.yaml               # 默认参数
├── values.schema.json        # 可选:JSON Schema 校验 values
├── Chart.lock                # 可选:锁定依赖版本(有 dependencies 时生成)
├── charts/                   # 可选:子 Chart(subchart)
├── crds/                     # 可选:CRD,不参与模板渲染,最先安装
├── .helmignore               # 可选:打包时排除文件
├── README.md
└── templates/
    ├── _helpers.tpl              # 命名模板,不生成资源
    ├── deployment.yaml
    ├── service.yaml
    ├── ingress.yaml
    ├── hpa.yaml
    ├── serviceaccount.yaml
    ├── configmap.yaml
    ├── NOTES.txt                 # 安装成功后打印的提示
    └── tests/
        └── test-connection.yaml  # helm test 用

约定:templates/ 下以 _ 开头的文件不渲染成资源 ;crds/ 下的资源 Helm 不做模板渲染、也不纳入卸载管理。

3.2 Chart.yaml 字段

yaml 复制代码
apiVersion: v2            # v2 = Helm 3 格式;v1 是 Helm 2 遗留
name: myapp
description: A generic web application chart
type: application         # application(可部署)| library(只提供命名模板)
version: 0.1.0            # Chart 自身版本,打包文件名用它
appVersion: "1.0.0"       # 应用版本标记,需模板显式使用才生效
keywords:
  - web
maintainers:
  - name: platform-team
dependencies:             # 可选:依赖的子 Chart
  - name: redis
    version: "18.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled

apiVersion: v2 相对 v1 的变化:依赖声明从独立的 requirements.yaml 移入 Chart.yaml 的 dependencies,新增 type 字段,并引入 Chart.lock 锁定依赖版本。

appVersion 的常见用法是在模板里兜底镜像 tag:

gotemplate 复制代码
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"

这样 image.tag 留空时自动跟随 appVersion,显式指定时以 --set image.tag=1.0.1 优先。

3.3 Package 与 Repository

bash 复制代码
# 打包:产出 ./myapp-0.1.0.tgz
helm package ./myapp --destination ./dist

# 打包时覆盖版本号(CI 中按构建号生成版本)
helm package ./myapp --version 0.2.0 --app-version 1.0.1 --destination ./dist

# 直接从打好的包安装,效果与目录完全一致
helm upgrade --install myapp ./dist/myapp-0.1.0.tgz -n app-prod -f values-prod.yaml

# 查看包内信息(无需解压)
helm show chart ./myapp          # Chart.yaml
helm show values ./myapp        # 默认 values
helm show readme ./myapp        # README
helm show all ./myapp           # 全部

分发渠道有两类:

bash 复制代码
# 1) 传统 Chart Repository:HTTP 服务 + index.yaml
helm repo add bitnami https://charts.bitnami.com/bitnami   # 添加仓库
helm repo list                                             # 已配置仓库
helm repo update                                           # 刷新本地索引缓存
helm search repo nginx                                     # 搜已添加仓库
helm search hub wordpress                                  # 搜 Artifact Hub
helm pull bitnami/nginx --untar --version 18.1.0           # 下载并解包到当前目录
helm repo remove bitnami

# 2) OCI Registry(Helm 3.8+ 原生,可复用现有镜像仓库)
helm registry login registry.example.com
helm push ./dist/myapp-0.1.0.tgz oci://registry.example.com/charts
helm pull oci://registry.example.com/charts/myapp --version 0.1.0
helm install myapp oci://registry.example.com/charts/myapp --version 0.1.0 -n app-prod

注意:往传统 Chart Repo 推送需要 helm-push(cm-push)插件;helm push ... oci:// 是原生能力,两者不要混用。

打包版本应当不可变:同一个 name-version 发布后改内容,会让仓库缓存和校验混乱,任何改动都要升 version。


四、values:参数的来源与优先级

values 是渲染时的唯一输入数据。一个普通 Web 应用的默认参数通常长这样:

yaml 复制代码
# values.yaml(Chart 自带默认值)
replicaCount: 2

image:
  repository: registry.example.com/team/myapp
  pullPolicy: IfNotPresent
  tag: ""                    # 留空则取 .Chart.AppVersion

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  automount: true
  name: ""

service:
  type: ClusterIP
  port: 80

containerPort: 8080

ingress:
  enabled: false
  className: nginx
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix
  tls: []

config:
  message: "Hello from Helm"
  logLevel: info

env: []

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

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

4.1 优先级(从低到高)

层级 来源 说明
1 Chart 内 values.yaml 默认值,最低
2 父 Chart 对子 Chart 的覆盖 子 Chart 只读自己那棵子树 + global.*
3 -f/--values file 多个文件按命令行从左到右合并,右侧优先
4 --set / --set-string / --set-file / --set-json 最高;多个按序后者胜
bash 复制代码
# 只用默认值渲染
helm template myapp ./myapp -n app-prod

# -f 覆盖(推荐:只写差异,不复制整份 values)
helm template myapp ./myapp -n app-prod -f values-prod.yaml

# 多个 -f,右侧优先;--set 又高于所有 -f
helm template myapp ./myapp -n app-prod \
  -f values-base.yaml -f values-prod.yaml \
  --set replicaCount=5 \
  --set config.logLevel=debug

# 类型敏感 / 大文本 / 结构化值的专用参数(--set-json 需 Helm 3.10+)
helm upgrade --install myapp ./myapp -n app-prod \
  --set-string service.port=8080 \
  --set-file config.message=./message.txt \
  --set-json ingress.tls='[{"secretName":"myapp-tls","hosts":["myapp.example.com"]}]'

--set-file 只是从文件读取值,若模板把它写进 ConfigMap,值依然是明文;密码不要用这种方式,见 8.1 的 Secret 方案。

4.2 使用说明与坑

  • 合并规则 :map 递归合并,标量和列表整体替换 。--set config.logLevel=debug 只改叶子;--set env[0].name=FOO 会重建整个列表而非追加。想给 env 加一项,推荐写进 -f 文件。
  • --set key=null 可删除默认值里的键;--set-string 强制字符串,避免 "8080" 被解析成数字导致类型校验失败。
  • upgrade 默认丢弃上次的 values ,按本次输入 + Chart 默认值重新计算;--reuse-values 才会沿用上次(CI 中容易踩坑,不建议),--reset-values 显式重置。
  • 用 values.schema.json 在渲染前做类型与必填校验,团队共享 Chart 时尤其有用:
json 复制代码
{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["image", "replicaCount"],
  "properties": {
    "replicaCount": { "type": "integer", "minimum": 1 },
    "image": {
      "type": "object",
      "required": ["repository"],
      "properties": { "repository": { "type": "string" } }
    }
  }
}
  • 查看某次发布实际用了什么:
bash 复制代码
helm get values myapp -n app-prod          # 仅用户显式提供的部分
helm get values myapp -n app-prod --all    # 含默认值(-a)
helm show values ./myapp                    # Chart 的默认 values

五、templates:渲染机制

templates/ 下是 Go template + Sprig 函数库,Helm 注入了一批内置对象:

对象 常用字段 典型用途
.Values 合并后的参数树 读配置
.Chart Name Version AppVersion 标签、版本、镜像兜底
.Release Name Namespace Service IsInstall IsUpgrade Revision 命名、条件分支
.Capabilities KubeVersion APIVersions 按集群版本选 apiVersion、判断 CRD 是否存在
.Files Get Glob 读取打包内的非模板文件
.Template Name BasePath 调试当前模板、拼模板路径

5.1 模板长什么样

deployment.yaml 开头:

gotemplate 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.containerPort }}
          env:
            {{- toYaml .Values.env | nindent 12 }}
  • {``{- / -}} 是空白控制,吃掉多余空行和缩进,否则渲染出的 YAML 可能非法。
  • 常用函数:quote、default、toYaml、nindent(缩进对齐)、include、required、fail、sha256sum、tpl、semverCompare,以及控制结构 if / with / range。
  • checksum/config 是社区通用技巧:把 ConfigMap 渲染结果做 sha256sum 写进 Pod 注解,配置一变注解就变,自动触发滚动更新,否则改了 ConfigMap 而 Pod 不会重启。

5.2 把逻辑收敛到 _helpers.tpl

命名模板用 define 定义、include 调用(. 是传入的根对象)。典型内容:

gotemplate 复制代码
{{/* 生成资源全名:优先 fullnameOverride,否则 <release>-<chart> */}}
{{- define "myapp.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}

{{/* 通用标签 */}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}

{{/* 用 fail 主动校验,避免渲染出错误资源 */}}
{{- define "myapp.validateIngress" -}}
{{- if .Values.ingress.enabled -}}
{{- if not .Values.ingress.hosts -}}
{{- fail "ingress.enabled=true 时必须提供 ingress.hosts" -}}
{{- end -}}
{{- end -}}
{{- end -}}

模板里也可用 required 对缺失值直接报错:

gotemplate 复制代码
{{- $host := required "必须配置 ingress.hosts[0].host" (first .Values.ingress.hosts).host -}}

5.3 条件渲染与 NOTES.txt

ingress.yaml 只在开关打开时产生资源,因此渲染结果里可能根本没有这个文件:

gotemplate 复制代码
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "myapp.fullname" . }}
spec:
  ingressClassName: {{ .Values.ingress.className }}
  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 }}
  • NOTES.txt 也参与模板渲染,但不作为资源提交 ,只在安装/升级成功后打印,可用 helm get notes 再次查看。常用它输出访问方式:
gotemplate 复制代码
应用 {{ .Release.Name }} 已部署到 {{ .Release.Namespace }}。

{{- if .Values.ingress.enabled }}
访问地址:http://{{ (first .Values.ingress.hosts).host }}
{{- else }}
本地访问:
  kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "myapp.fullname" . }} 8080:{{ .Values.service.port }}
{{- end }}
  • Hooks :带 helm.sh/hook: pre-install|post-upgrade|pre-delete|test|... 注解的模板在发布生命周期特定时点执行,常用于数据库迁移 Job 或 helm test 的连通性检查;hook-weight 控制顺序,hook-delete-policy 控制回收。

5.4 渲染流程

复制代码
加载 Chart 及 subchart → 合并 values(含 schema 校验)→ 渲染全部模板
→ 按 Kind 的既定安装顺序排序(不是文件名字母序)→ 与集群校验
→ 提交资源 → 生成新 Revision

Helm 3 把每次发布的清单、values、NOTES、状态编码后存进集群 Secret(sh.helm.release.v1.<name>.v<N>),所以 helm get manifest 和 helm rollback 不需要本地留 YAML。可用 HELM_DRIVER=configmap|secret|sql|memory 更换存储驱动。


六、命令速查(按使用阶段)

6.1 环境与仓库

bash 复制代码
helm version                     # 客户端版本
helm env                         # HELM_* 环境变量与目录(缓存、配置、插件路径)
helm repo add <name> <url>       # 添加 Chart 仓库
helm repo update                 # 刷新索引
helm repo list / remove <name>
helm search repo <kw>            # 搜本地已索引仓库
helm search hub <kw>             # 搜 Artifact Hub
helm show chart|values|readme|all <chart>
helm pull <chart> [--untar]      # 下载
helm plugin list|install|uninstall

6.2 本地开发与校验(无需连集群)

bash 复制代码
helm create myapp                                    # 生成脚手架
helm lint ./myapp                                    # 静态检查
helm lint ./myapp --strict                           # 把 warning 也当失败(CI 用)
helm lint ./myapp -f values-prod.yaml                # 用生产参数检查
helm template myapp ./myapp -n app-prod              # 纯本地渲染全部清单
helm template myapp ./myapp -n app-prod \
  -f values-prod.yaml --set replicaCount=5          # 带参数的渲染
helm template myapp ./myapp \
  --show-only templates/deployment.yaml             # 只看某一个模板(-s)
helm template myapp ./myapp --debug                 # 渲染失败时输出上下文
helm dependency build|update ./myapp                # 处理 dependencies
helm package ./myapp --destination ./dist          # 打成 .tgz

6.3 安装与升级

参数 作用
-n, --namespace 目标命名空间
--create-namespace 命名空间不存在时自动创建
-f, --values 追加 values 文件,可多次
--set 家族 命令行覆盖,优先级最高
--wait --timeout 5m 等 Pod Ready 才返回成功
--dry-run 走完整流程但不提交(仍需连集群校验)
--atomic 失败自动回滚到上一 Revision
--cleanup-on-fail 失败时清理本次新建资源
--history-max N 限制保留的 Revision 数量
--kubeconfig / --kube-context 切换集群
bash 复制代码
# 首次安装(同名 release 已存在会报错)
helm install myapp ./myapp -n app-prod -f values-prod.yaml

# 幂等安装/升级:CI 一律用这个
helm upgrade --install myapp ./myapp \
  -n app-prod --create-namespace \
  -f values-prod.yaml \
  --wait --timeout 5m

# 发一个新镜像版本
helm upgrade myapp ./myapp -n app-prod --reuse-values \
  --set image.tag=1.0.1

# 更稳妥的写法:把版本也写进 values 文件,避免依赖上次状态
helm upgrade --install myapp ./myapp -n app-prod \
  -f values-prod.yaml --set image.tag=1.0.1 --atomic --history-max 10

# 让 Helm 随机生成 release 名(临时验证用)
helm install ./myapp --generate-name -n app-dev

6.4 查看、回滚与卸载

bash 复制代码
helm list -n app-prod                        # 当前命名空间
helm list -A                                 # 全部命名空间
helm status myapp -n app-prod
helm history myapp -n app-prod                # 回滚依据
helm get manifest myapp -n app-prod           # 实际提交的清单
helm get values myapp -n app-prod -a          # 含默认值的完整参数
helm get notes myapp -n app-prod              # 安装时打印的 NOTES
helm get all myapp -n app-prod
helm test myapp -n app-prod                   # 跑 templates/tests/ 下的测试 Pod
helm rollback myapp 3 -n app-prod
helm uninstall myapp -n app-prod
helm uninstall myapp -n app-prod --keep-history   # 保留记录供审计

七、实战:从零发布一个普通 Web 应用

7.1 前置条件

工具 版本 用途
Helm 3.x Chart 渲染与发布
kubectl 与集群匹配 集群访问、验证
K8s 集群 1.19+ 需能拉取 image.repository 指向的镜像
bash 复制代码
helm version                          # 确认是 3.x
kubectl config current-context        # 确认连的是目标集群,别发错环境
kubectl create namespace app-dev
kubectl create namespace app-prod

# 若镜像仓库需要认证,先创建拉取凭证(values 里用 imagePullSecrets 引用)
kubectl create secret docker-registry registry-cred \
  --docker-server=registry.example.com \
  --docker-username=<用户名> --docker-password=<密码> \
  -n app-prod

7.2 创建并定制 Chart

bash 复制代码
helm create myapp
cd myapp

随后编辑三个文件:

  1. Chart.yaml:name: myapp、version: 0.1.0、appVersion: "1.0.0";
  2. values.yaml:把 image.repository 改成 registry.example.com/team/myapp,containerPort 设为 8080,其余按第四节示例填写;
  3. templates/deployment.yaml:确认 image 使用 {``{ .Values.image.tag | default .Chart.AppVersion }},容器端口读 .Values.containerPort。

构建并推送应用镜像,tag 与 appVersion 对齐:

bash 复制代码
docker build -t registry.example.com/team/myapp:1.0.0 .
docker push registry.example.com/team/myapp:1.0.0

7.3 拆分环境 values(只写差异)

values-dev.yaml:

yaml 复制代码
replicaCount: 1
image:
  tag: "1.0.0-rc1"
  pullPolicy: Always
service:
  type: NodePort
config:
  logLevel: debug
env:
  - name: LOG_LEVEL
    value: debug

values-prod.yaml:

yaml 复制代码
replicaCount: 3
image:
  tag: "1.0.0"
  pullPolicy: IfNotPresent
imagePullSecrets:
  - name: registry-cred
resources:
  limits:
    cpu: "1"
    memory: 512Mi
  requests:
    cpu: 200m
    memory: 256Mi
ingress:
  enabled: true
  className: nginx
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix

两个文件都只写与默认值不同的项。env 是列表,覆盖了就是整体替换,不会与默认值合并。

7.4 四步发布法

bash 复制代码
# 1) 静态检查:模板语法、values 结构、schema 校验
helm lint ./myapp --strict
helm lint ./myapp -f values-prod.yaml --strict

# 2) 看渲染结果:确认镜像 tag、副本数、端口、Ingress 是否符合预期
helm template myapp ./myapp -n app-prod -f values-prod.yaml > /tmp/prod.yaml
grep -E 'kind:|image:|replicas:|containerPort:|checksum/config' /tmp/prod.yaml

# 只看 Deployment,排查缩进问题
helm template myapp ./myapp -n app-prod -f values-prod.yaml \
  --show-only templates/deployment.yaml

# 3) 预演:走完整流程但不提交
helm upgrade --install myapp ./myapp -n app-prod \
  -f values-prod.yaml --dry-run --debug

# 4) 正式发布:等就绪 + 失败自动回滚
helm upgrade --install myapp ./myapp -n app-prod \
  --create-namespace -f values-prod.yaml \
  --atomic --wait --timeout 5m --history-max 10

开发环境同理,只换命名空间与 values 文件:

bash 复制代码
helm upgrade --install myapp ./myapp -n app-dev -f values-dev.yaml --wait --timeout 5m

7.5 发布后验证

bash 复制代码
helm list -A
helm status myapp -n app-prod
helm get values myapp -n app-prod -a
helm get manifest myapp -n app-prod | head -40

kubectl -n app-prod get deploy,po,svc,ing
kubectl -n app-prod get cm -l app.kubernetes.io/instance=myapp -o yaml
kubectl -n app-prod describe pod -l app.kubernetes.io/instance=myapp
kubectl -n app-prod logs -f deploy/myapp
kubectl -n app-prod port-forward svc/myapp 8080:80

# 若 Chart 带 tests/test-connection.yaml
helm test myapp -n app-prod

浏览器访问 http://localhost:8080(port-forward)或 http://myapp.example.com(Ingress)。

7.6 升级、回滚与卸载

bash 复制代码
# 升级:发布新镜像
docker push registry.example.com/team/myapp:1.0.1
helm upgrade --install myapp ./myapp -n app-prod \
  -f values-prod.yaml --set image.tag=1.0.1 --atomic --wait --timeout 5m

# 查看历史,确认每次升级的 STATUS / CHART / APP VERSION
helm history myapp -n app-prod

# 回滚到第 2 个 Revision(会生成一个新的 Revision)
helm rollback myapp 2 -n app-prod --wait

# 卸载(CRD、PVC、命名空间不会被删除,需单独处理)
helm uninstall myapp -n app-prod

镜像 tag 变化会触发 Deployment 滚动更新;ConfigMap 内容变化则依赖 checksum/config 注解触发,两者缺一不可。

7.7 打包分发给别的集群

bash 复制代码
helm lint ./myapp --strict
helm package ./myapp --destination ./dist          # dist/myapp-0.1.0.tgz
helm show values ./dist/myapp-0.1.0.tgz

helm registry login registry.example.com
helm push ./dist/myapp-0.1.0.tgz oci://registry.example.com/charts

# 另一集群直接安装(无需源码目录)
helm upgrade --install myapp oci://registry.example.com/charts/myapp \
  --version 0.1.0 -n app-prod --create-namespace -f values-prod.yaml

八、进阶话题

8.1 敏感信息不要进 ConfigMap

values.yaml 里的密码若被模板写进 ConfigMap,就是明文且可被任意有读权限的人看到。生产环境应改用 Secret + 环境变量引用:

bash 复制代码
kubectl create secret generic myapp-secret \
  --from-literal=db-password='<密码>' -n app-prod
yaml 复制代码
# values-prod.yaml
env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: myapp-secret
        key: db-password

配合 deployment.yaml 中的 {``{- toYaml .Values.env | nindent 12 }} 即可注入。更完整的方案是 External Secrets Operator、Sealed Secrets 或 Vault Agent;至少也要避免把含密钥的 values 文件提交到 Git。

8.2 Subchart 与 global

父 Chart 通过 Chart.yaml 的 dependencies 引入子 Chart(如 Redis),helm dependency build 依据 Chart.lock 拉取到 charts/。子 Chart 只能读自己名下的 values 子树,唯一例外是 global.*:

yaml 复制代码
# 父 Chart values.yaml
global:
  imageRegistry: registry.example.com
redis:                  # 子 Chart 名
  enabled: false        # 对应 dependencies 里的 condition: redis.enabled
  auth:
    enabled: true

调试某个子 Chart 的渲染结果:

bash 复制代码
helm template myapp ./myapp --show-only charts/redis/templates/deployment.yaml

8.3 按集群版本选择 apiVersion

gotemplate 复制代码
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.Version }}
apiVersion: networking.k8s.io/v1
{{- else }}
apiVersion: networking.k8s.io/v1beta1
{{- end }}

.Capabilities.APIVersions.Has "batch/v1" 还能判断某个 API / CRD 是否可用,避免在依赖不满足的集群里渲染失败。helm template --kube-version 1.20.0 可以在本地模拟不同集群版本。

8.4 CI/CD 建议

  • 流水线固定使用 helm upgrade --install,配合 --atomic --wait --timeout 5m --history-max 10;
  • 提交前跑 helm lint --strict,并把 helm template 输出做快照对比(渲染 diff 进 PR);
  • values 按环境拆文件,密钥走 Secret 而非 -f;
  • Chart 版本随模板变更递增,产物 .tgz 归档到 OCI Registry;应用发版则改 appVersion / image.tag;
  • 用 --kube-context 显式指定目标集群,防止误发。

九、常见问题排查

现象 排查方向
ImagePullBackOff 镜像 tag 是否推送成功;imagePullSecrets 引用的 Secret 是否在同一命名空间 ;pullPolicy 是否为 Always 但仓库不可达
CrashLoopBackOff kubectl logs 与 kubectl describe pod 看启动异常、探针失败、端口不一致
Error: cannot re-use a name helm install 遇同名 release 报错,改用 upgrade --install
模板报 nil pointer evaluating values 缺键,用 default 兜底或 required 显式报错;helm template --debug 定位
渲染出的 YAML 缩进错乱 检查 {``{- 空白控制与 nindent / indent 的层数,用 --show-only 单独看该模板
配置改了但 Pod 没重启 检查 Deployment 是否有 checksum/config 注解,及其值是否变化
values 覆盖没生效 检查 --set 与 -f 的优先级、多个 -f 的左右顺序、upgrade 是否用了 --reuse-values
升级报 field is immutable Service / Job 的部分字段不可变,需改名或先删除重建;避免用 --set 改 selector
--dry-run 也报集群错误 Helm 3 的 dry-run 仍需连 API Server 做校验;纯本地看结果用 helm template
helm rollback 后编号没退回 符合预期:回滚会新增一个 Revision
helm test 失败 查看测试 Pod 日志;templates/tests/ 下的资源只在 helm test 时创建
卸载后资源仍在 helm uninstall 不删 CRD、PVC、命名空间与 Hook 残留,需单独清理

十、小结

Helm 的价值不在「少写几行 YAML」,而在于建立了一套可追溯的发布模型:

  • Chart / Package 让应用定义可复用、可版本化、可分发;
  • values 让环境差异集中成参数,并有明确的覆盖优先级;
  • templates 让资源命名、镜像拼接、条件渲染、配置校验和只写一遍;
  • Release / Revision 让每次发布成为可查询、可对比、可回滚的快照。

回到 myapp:同一份 Chart + 两个 values 文件就能覆盖开发/生产,一次 --set image.tag=1.0.1 完成升级,而 helm history / rollback 让这次升级随时可撤回------这正是 Helm 在真实项目里最核心的收益。

延伸阅读 :helm help 与各子命令 --help、Helm 官方文档的 Chart 最佳实践与 Chart Template Guide、helm create 生成的脚手架注释。

相关推荐
分布式存储与RustFS8 小时前
镜像仓库的层别只堆本地盘:用 RustFS 给 Harbor 当 S3 后端
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
分布式存储与RustFS11 小时前
工作流脚本和依赖 JAR 散在各节点本地盘:用 DolphinScheduler 资源中心接到 RustFS
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
Eyfcom11 小时前
〖物流云原生实战 01〗K8s Operator 在物流定时任务中的实践:从“裸 CronJob”到“声明式任务编排”
云原生·物流管理系统·物流云原生实战
阿里云云原生1 天前
阿里云智能体白皮书开源进展
云原生
阿里云云原生1 天前
万联智策基于 AgentCore 构建数字员工团队,800+智能体规模化落地
云原生
分布式存储与RustFS1 天前
群晖 NAS 被 C3RB3R 锁了:本地备份一起被加密,RustFS 对象锁为什么删不掉
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
江华森1 天前
4 台华为云 ECS 从零到一:我用 AI Agent 搭了一套 K3s 多节点集群,全自动、免运维、翻车实录
云原生
时间售卖铺1 天前
Chrony与SSH实战:精准时间同步与安全远程登录
运维·安全·云原生·ssh
分布式存储与RustFS1 天前
OpenObserve 的日志别堆本地盘:用 RustFS 当它的对象存储底座
运维·云原生·开源·对象存储·分布式存储·s3·性能基准