跳转至

Helm 与包管理

装了十几个组件之后,`kubectl apply -f` 的清单会变成一堆没人敢改的 YAML:镜像 tag 散落在十几处,回滚靠翻 Git 历史,环境差异靠复制目录。Helm 把「一组资源」打包成带版本、带默认值、可参数化的 Chart,再用 Release 记录每一次变更。

难度 · 入门到进阶 预计 50 分钟 前置:Deployment、Service、Ingress

你将学到

  • Chart、Release、Repository、Values 四个概念各自解决什么问题
  • Chart 目录结构,以及 .Values、.Release、.Chart、.Capabilities 内置对象
  • values 的覆盖顺序、--set 的转义陷阱,以及 upgrade / rollback / history 的工作方式
  • hooks、子 Chart 依赖、helm package 与 OCI 私有仓库

为什么需要它

Kubernetes 的声明式 API 解决了「描述期望状态」,但没解决「分发与复用」。一个 ingress-nginx 有十几个资源、几十个可调参数;不同环境要改镜像 tag、副本数、存储类、域名。用原始 YAML 只能复制目录,Kustomize 擅长 patch 却不擅长「打包 + 版本 + 依赖」。Helm 的定位就是 Kubernetes 的包管理器:Chart 是模板化的资源包,Values 是可覆盖的默认配置,Release 是一次安装的实例与版本记录。Helm 3 移除了服务端组件 Tiller,CLI 直接用 kubeconfig 与 API Server 交互,权限模型回到 RBAC。

核心概念

四个对象

概念 是什么 存在哪
Chart 模板 + 默认值 + 元数据的目录或压缩包 仓库或本地
Release 一个 Chart 在集群中的一次安装实例 命名空间内的 Secret
Repository 存放 Chart 包与 index.yaml 的 HTTP/OCI 服务 远端
Values 覆盖 Chart 默认值的配置 values.yaml、-f、--set

Chart 目录结构

demo/
demo/
├── Chart.yaml         # 名称、version、appVersion、依赖声明
├── values.yaml        # 默认值
├── charts/            # 子 Chart(helm dependency update 生成)
├── crds/              # 只安装、不随 upgrade 升级的 CRD
└── templates/         # 资源模板 + _helpers.tpl、NOTES.txt

Chart.yaml 的 version 是 Chart 自身版本,appVersion 是被打包应用的版本,两者独立演进——helm search repo 展示的是前者。

模板:内置对象与常用函数

templates/ 里是 Go template,渲染时能拿到四个内置对象:.Values(合并后的配置)、.Release(Name、Namespace、Revision、IsInstall、IsUpgrade)、.Chart(Chart.yaml 的内容)、.Capabilities(KubeVersion、APIVersions,用于按集群版本分支)。

demo/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "demo.fullname" . }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          env: [{name: LOG_LEVEL, value: {{ .Values.logLevel | quote }}}]

常用函数:include(引入 _helpers.tpl 的命名模板,可接管道)、tpl(把字符串当模板再渲染)、required(值缺失直接报错,比渲染出空值好排查)、default、quote、toYaml + nindent(把结构体缩进写进 YAML)、lookup(查询集群对象,慎用,会让渲染依赖集群状态)。

values 的覆盖顺序

优先级从低到高:Chart 的 values.yaml → 父 Chart 的 values.yaml(子 Chart 场景)→ -f 文件(多个按顺序,后者覆盖前者)→ --set。--set 优先级最高,也最容易踩坑:

helm upgrade app ./demo --set env.FOO="a\,b"                  # 逗号要转义,否则被拆成两个键
helm upgrade app ./demo --set-string podAnnotations."prometheus\.io/scrape"="true"   # 键名里的点要转义
helm upgrade app ./demo --set-string image.tag="1.10"         # 否则 1.10 会变成 1.1

仓库 helm-examples/README.md 部署 ingress-nginx 时用的正是 --set-string controller.podAnnotations."prometheus\.io/scrape"="true"——注解键里带 / 和 .,不转义就渲染不出预期结构。

install、upgrade、rollback 与 hooks

flowchart LR
  C["Chart 模板"] --> R["helm template 渲染"]
  V["values.yaml / -f / --set"] --> R
  R --> K["kube-apiserver"]
  K --> S["Release Secret v1..vN"]
  S -->|"helm rollback"| K

图:每次 install/upgrade 生成一个新 revision 的 Release Secret,rollback 就是拿旧 revision 重新 apply。

helm upgrade --install 是幂等的部署入口;helm history 列出所有 revision;helm rollback <release> <revision> 回到指定版本,它会生成一个新 revision而不是删历史。生产上常用 --atomic(失败自动回滚,隐含 --wait)、--cleanup-on-fail(清理本次新建却失败的对象)、--history-max 10(限制保留的 revision 数)。

hooks 是带注解的普通资源,Helm 在特定时刻 apply 它们:pre-install、post-install、pre-upgrade、post-upgrade、pre-delete、post-delete、test。helm.sh/hook-weight 控制顺序,helm.sh/hook-delete-policy 控制何时删除(before-hook-creation、hook-succeeded、hook-failed)。典型用法是升级前跑一次数据库迁移 Job。

依赖、打包与私有仓库

demo/Chart.yaml
apiVersion: v2
name: demo
version: 0.1.0
dependencies:
  - name: redis
    version: "19.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled      # 由父 values 的开关控制

helm dependency update 把子 Chart 拉到 charts/。分发三步:helm package demo -d ./repo 生成 demo-0.1.0.tgz;helm repo index ./repo --url http://<host> 生成索引,用任意静态服务器托管就是私有仓库;helm push demo-0.1.0.tgz oci://registry.magedu.com/charts 推到 Harbor 的 OCI 仓库,需先 helm registry login。

生产注意事项

  • values 不是秘密:Release Secret 里的 values 只是 base64,helm get values 有权限就能看。密码应放外部 Secret(existingSecret)或走 External Secrets / helm-secrets + SOPS。
  • CRD 不随 upgrade 更新:crds/ 只在 install 时安装,升级 Chart 不会升级 CRD,需单独 kubectl apply。
  • 先 diff 再升级:helm diff upgrade(插件)或 helm template . | kubectl diff -f -,比 --dry-run 更接近真实结果。
  • 固定版本:--version 明确指定 Chart 版本,别让 CI 每次拉到不同的 latest;仓库 prometheus-adapter.yml 里的 k8s.gcr.io 早已冻结,v1.31 上应换成 registry.k8s.io/prometheus-adapter/prometheus-adapter。

动手实验

实验:从 helm create 到私有仓库,再装一个 Harbor

目标:手写 Chart、改 values、完成 upgrade/rollback、打包成仓库,最后用仓库里的 values 部署 Harbor。

前置:Helm 3.x;集群有 nfs-csi StorageClass 与 ingress-nginx;可选装 helm-diff 插件。

步骤 1 · 创建并检查 Chart

helm create demo && helm lint demo && helm template demo ./demo | head -12
==> Linting demo
1 chart(s) linted, 0 chart(s) failed
# Source: demo/templates/serviceaccount.yaml
kind: ServiceAccount
metadata:
  name: demo

步骤 2 · 用 values 覆盖默认配置

my-values.yaml
replicaCount: 2
image: {repository: ikubernetes/demoapp, tag: "v1.0"}
logLevel: debug
service: {type: ClusterIP}
ingress:
  enabled: true
  className: nginx
  hosts: [{host: demo.magedu.com, paths: [{path: /, pathType: Prefix}]}]
helm install demo ./demo -n demo --create-namespace -f my-values.yaml
helm get values demo -n demo && kubectl -n demo get deploy,ingress
USER-SUPPLIED VALUES:
image:
  repository: ikubernetes/demoapp
  tag: v1.0

helm get values 只显示你覆盖的部分,加 -a 才是合并后的全量值——排查「为什么渲染出来是空」时先看这个;此时 kubectl -n demo get deploy 应显示 2/2。

步骤 3 · upgrade 与 rollback

helm upgrade demo ./demo -n demo -f my-values.yaml --set image.tag=v1.1 --atomic
helm history demo -n demo
helm rollback demo 1 -n demo && helm history demo -n demo
REVISION   STATUS       CHART        APP VERSION   DESCRIPTION
1          superseded   demo-0.1.0   1.0           Install complete
2          deployed     demo-0.1.0   1.0           Upgrade complete
3          deployed     demo-0.1.0   1.0           Rollback to 1

rollback 不是删除 revision 2,而是以 revision 1 的内容生成 revision 3。看差异用 helm diff revision demo 1 2,看渲染结果用 helm get manifest demo -n demo。

步骤 4 · 打包并建一个私有仓库

helm package demo -d ./repo && helm repo index ./repo --url http://127.0.0.1:8879
python3 -m http.server 8879 -d ./repo &
helm repo add local http://127.0.0.1:8879 && helm search repo local/demo
Successfully packaged chart and saved it to: repo/demo-0.1.0.tgz
NAME        CHART VERSION   APP VERSION   DESCRIPTION
local/demo  0.1.0           1.0           A Helm chart for Kubernetes

推到 OCI 仓库(Harbor 2.x 自带):helm registry login registry.magedu.com -u admin,再 helm push repo/demo-0.1.0.tgz oci://registry.magedu.com/charts,之后可以直接 helm install demo2 oci://registry.magedu.com/charts/demo --version 0.1.0。

步骤 5 · 用仓库里的 values 部署 Harbor

helm repo add harbor https://helm.goharbor.io && helm repo update
helm install harbor -f source-repo/helm-examples/harbor/harbor-values.yaml harbor/harbor -n harbor --create-namespace --atomic --timeout 10m
kubectl -n harbor get pod,pvc
pod/harbor-core-6f7b9c8d4-2xk9p       1/1     Running   0          3m
NAME                            STATUS   VOLUME    CAPACITY   STORAGECLASS
persistentvolumeclaim/harbor-registry   Bound   pvc-...   5Gi        nfs-csi

这份 values 的关键是把 expose.type 设成 ingress、className 设成 nginx,并给每个组件指定 storageClass: nfs-csi 与合适的 accessMode(registry 用 ReadWriteMany)。harbor-values-openebs.yaml 则换成 openebs-hostpath + ReadWriteOnce,适合单节点或本地盘场景。WordPress 同理,仓库里给了 bitnami 仓库与 OCI 两种引用方式(oci://registry-1.docker.io/bitnamicharts/wordpress),后者是 bitnami 现在推荐的路径。

另外两个仓库示例更省事:ingress-nginx 用 helm upgrade ingress-nginx ingress-nginx --install --repo https://kubernetes.github.io/ingress-nginx --namespace ingress-nginx --create-namespace --set controller.metrics.enabled=true;OpenEBS 先 helm repo add openebs https://openebs.github.io/charts,再 helm upgrade openebs --install --namespace openebs openebs/openebs --set engines.replicated.mayastor.enabled=false --create-namespace,不需要的 zfs / lvm 引擎同样用 --set engines.local.zfs.enabled=false 关掉。

验证清单

  • helm list -A 能看到 demo 与 harbor,STATUS=deployed
  • helm history demo -n demo 至少三个 revision,最后一个是 rollback
  • kubectl -n demo get deploy demo -o jsonpath='{..image}' 显示回滚后的 tag
  • helm search repo local/demo 能查到刚打包的 Chart
  • Harbor 的 PVC 全部 Bound,Ingress 有 ADDRESS

常见问题与排错

现象 可能原因 处理方式
cannot re-use a name that is still in use 同名 Release 已存在(可能在 failed 状态) helm list -a 确认后 helm uninstall,日常用 helm upgrade --install
渲染结果出现 <no value> values 路径写错、子 Chart 未传 global helm get values -a 对照 values.yaml;用 required 让缺失直接报错
--set 的值没生效 键名含 . 未转义,或数字被转成 int 改用 --set-string,键名写成 a\.b
upgrade 后 Pod 没更新 模板没引用改动的值(镜像 tag 写死) helm get manifest 与 helm template 对比,确认渲染结果真的变了
--atomic 超时回滚 --timeout 太短、镜像拉取慢、readiness 未就绪 提高 --timeout;kubectl -n <ns> get pod,events 看卡在哪

进阶挑战

  1. 加一个 templates/configmap.yaml,用 toYaml + nindent 把 .Values.env 渲染成 ConfigMap 的 data,再用 checksum/config 注解触发滚动更新。
  2. 加一个 post-install hook Job 打印访问地址,并用 helm.sh/hook-delete-policy 控制它何时被清理。
  3. 用 helm dependency update 引入 redis 子 Chart,通过 condition 在 prod 环境关掉它。

小结

  • Chart 是模板包,Release 是安装实例,Values 是覆盖层,Repository 是分发渠道,四者职责不同。
  • 模板靠 .Values / .Release / .Chart / .Capabilities 拿上下文,include + nindent 是组织 YAML 的主力。
  • 覆盖顺序是「Chart 默认 < -f < --set」,--set 的转义与类型是最高频的坑。
  • upgrade 产生新 revision,rollback 以旧 revision 生成更新的 revision;--atomic 把失败变成自动回滚。

延伸阅读