Helm 与包管理¶
装了十几个组件之后,`kubectl apply -f` 的清单会变成一堆没人敢改的 YAML:镜像 tag 散落在十几处,回滚靠翻 Git 历史,环境差异靠复制目录。Helm 把「一组资源」打包成带版本、带默认值、可参数化的 Chart,再用 Release 记录每一次变更。
你将学到¶
- 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/
├── 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,用于按集群版本分支)。
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。
依赖、打包与私有仓库¶
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¶
==> Linting demo
1 chart(s) linted, 0 chart(s) failed
# Source: demo/templates/serviceaccount.yaml
kind: ServiceAccount
metadata:
name: demo
步骤 2 · 用 values 覆盖默认配置¶
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
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 看卡在哪 |
进阶挑战¶
- 加一个
templates/configmap.yaml,用toYaml+nindent把.Values.env渲染成 ConfigMap 的data,再用checksum/config注解触发滚动更新。 - 加一个
post-installhook Job 打印访问地址,并用helm.sh/hook-delete-policy控制它何时被清理。 - 用
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把失败变成自动回滚。