Kubernetes 基础体系 · 第 54/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Helm 完整指南:Chart、Template、Values、Release、Hook 和回滚
Helm 是 Kubernetes 的客户端发布工具和模板化打包工具。它把一组 Kubernetes 清单、默认配置、依赖关系和生命周期钩子组织成一个 Chart,然后根据输入配置渲染清单,提交给 Kubernetes API Server,并把发布记录保存为 Release。
可以先用一个关系式概括 Helm:
其中:
Chart:应用的打包内容,包括模板、默认 Values、依赖和元数据;Values:本次安装或升级使用的配置;Release:某个 Chart 在某个命名空间中的一次安装实例及其历史版本;Capabilities:Helm 根据目标 Kubernetes 集群发现到的 API 能力;Manifest:Helm 最终提交给 Kubernetes 的 YAML 对象集合。
Helm 不等于 Kubernetes 控制器。Helm 负责生成和提交期望对象,并保存发布历史;Deployment、StatefulSet、Job 等资源仍由 Kubernetes 控制器负责实际调度、创建 Pod 和维持运行状态。
一、Helm 解决什么问题
直接使用 Kubernetes YAML 时,一个应用通常需要维护:
- Deployment;
- Service;
- ConfigMap;
- Secret;
- Ingress 或 Gateway;
- ServiceAccount 和 RBAC;
- HPA;
- PDB;
- Job;
- 监控和网络策略对象。
不同环境又往往只有部分字段不同,例如:
- 镜像仓库和标签不同;
- 副本数不同;
- 域名不同;
- 资源请求不同;
- 是否启用 Ingress 不同;
- Secret 引用名称不同。
Helm 将“资源结构”和“环境参数”分离:
Chart
├── 模板结构:Deployment、Service、Ingress ...
└── 默认参数:镜像、端口、副本数、资源限制 ...
环境输入
├── values-dev.yaml
├── values-staging.yaml
└── values-prod.yaml
Helm 渲染
└── 生成当前环境的 Kubernetes Manifest
但 Helm 不是任意文本替换器。它的渲染结果还依赖:
- Go template 语法;
- Helm 提供的内置对象和函数;
- Values 合并规则;
- Kubernetes API 版本和资源校验;
- Release 的当前修订版本;
- 安装、升级、回滚命令的生命周期。
理解这些边界,才能正确判断“模板错误”“配置错误”“集群拒绝”“应用启动失败”分别发生在哪里。
二、先区分 Chart、Release 和 Kubernetes 资源
这三个概念经常被混淆。
2.1 Chart 是可安装的软件包
Chart 是描述应用如何部署的静态包。一个典型 Chart 目录如下:
demo/
├── Chart.yaml
├── values.yaml
├── values.schema.json
├── templates/
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ └── tests/
│ └── smoke-test.yaml
├── charts/
└── crds/
各文件作用如下:
Chart.yaml:Chart 名称、版本、应用版本、依赖等元数据;values.yaml:默认 Values;values.schema.json:可选的 Values 校验规则;templates/:需要渲染的模板;templates/_helpers.tpl:通常放命名、标签等命名模板,不直接生成 Kubernetes 对象;charts/:依赖 Chart 的归档目录;crds/:CRD 文件,安装时具有特殊处理规则。
Chart 本身不等于一个正在运行的应用。可以把它类比为一个软件安装包或部署程序。
2.2 Release 是 Chart 的一次安装实例
例如:
helm install demo ./demo -n demo --create-namespace
这里:
./demo是 Chart;demo是 Release 名称;demo命名空间中的 Deployment、Service 等是 Kubernetes 资源;- Helm 保存的这次安装记录是一个 Release。
同一个 Chart 可以安装成多个 Release:
helm install demo-dev ./demo -n dev
helm install demo-prod ./demo -n prod
它们使用同一份 Chart,但分别拥有不同的 Values、资源对象和 Release 历史。
2.3 Release revision 是发布历史序号
Release 会随着安装、升级、回滚形成修订历史:
revision 1: helm install
revision 2: helm upgrade
revision 3: helm upgrade
revision 4: helm rollback 1
回滚到 revision 1 并不是把历史指针简单地改回 1。Helm 通常会创建一个新的 Release revision,使当前发布状态变为:
revision 5: rollback to revision 1
因此,revision 5 的目标内容来自 revision 1,但它仍然是一次新的发布操作。
Helm 3 默认将 Release 信息保存为 Kubernetes Secret,通常位于该 Release 的命名空间中。可以查看:
kubectl get secret -n demo -l owner=helm
这些 Secret 是 Helm 的内部存储格式,不应直接修改。直接编辑可能导致 Helm 历史与集群资源不一致。
三、Chart 的完整结构和生命周期
3.1 创建一个最小 Chart
如果本机安装了 Helm,可以运行:
helm create demo
这会生成一个示例 Chart,但生产使用前应删除不需要的模板,避免默认示例内容影响部署。
一个最小 Chart.yaml 可以是:
apiVersion: v2
name: demo
description: A minimal demo application chart
type: application
version: 0.1.0
appVersion: "1.0.0"
这里有两个容易混淆的版本:
version:Chart 版本,遵循 Chart 包的版本管理;appVersion:应用版本,仅是元数据,Helm 不会自动用它替换镜像标签。
例如:
version: 0.2.0
appVersion: "2025.03.01"
这不代表镜像一定会使用 2025.03.01。镜像标签仍由模板中的 Values 决定。
apiVersion: v2 表示 Helm 3 使用的 Chart API 格式。它不是 Kubernetes 对象的 apiVersion,两者属于不同层次。
3.2 Chart 的处理过程
一次典型安装大致经过以下阶段:
sequenceDiagram
participant U as 用户或 CI
participant H as Helm
participant C as Chart
participant K as Kubernetes API Server
participant R as Kubernetes 控制器
U->>H: helm install/upgrade
H->>C: 加载 Chart、依赖和 Values
H->>H: 合并 Values、执行模板渲染
H->>H: 校验 YAML 和 Kubernetes API 能力
H->>K: 创建或更新资源
K->>R: 通知 Deployment、Service 等控制器
R->>K: 创建 Pod、更新状态
K-->>H: 返回对象和状态
H->>K: 保存 Release revision
需要注意,Helm 成功提交对象,不代表应用已经可用。除非使用了合适的 --wait、--wait-for-jobs 和超时设置,否则 Helm 可能在 Kubernetes 接受对象后就结束,而 Pod 之后才会拉取镜像、挂载卷并启动。
四、Template:Helm 如何生成 Kubernetes YAML
4.1 Template 的本质
Helm 模板使用 Go text/template 语法,并提供大量 Helm 和 Sprig 函数。
例如:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "demo.fullname" . }}
labels:
{{- include "demo.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
模板渲染时,. 表示当前上下文。最外层上下文通常包含:
.Values:合并后的配置;.Release:当前 Release 信息;.Chart:Chart 元数据;.Capabilities:集群能力;.Files:访问 Chart 内部文件;.Template:当前模板信息。
4.2 常用内置对象
.Values
image:
repository: nginx
tag: "1.27"
模板:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
.Release
metadata:
labels:
app.kubernetes.io/instance: {{ .Release.Name | quote }}
常用字段包括:
.Release.Name:Release 名称;.Release.Namespace:目标命名空间;.Release.IsInstall:是否为安装;.Release.IsUpgrade:是否为升级;.Release.Revision:当前修订版本;.Release.Service:通常为Helm。
.Chart
annotations:
chart-version: {{ .Chart.Version | quote }}
app-version: {{ .Chart.AppVersion | quote }}
.Chart.AppVersion 只是元数据,不会自动改变容器镜像。
.Capabilities
根据 Kubernetes API 能力选择不同资源版本:
{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1/Ingress" }}
apiVersion: networking.k8s.io/v1
{{- else }}
apiVersion: networking.k8s.io/v1beta1
{{- end }}
但是,旧 API 是否仍存在必须以目标集群实际能力为准。不要因为模板里保留了 fallback,就认为 Kubernetes 会接受已经废弃的 API。当前稳定 Kubernetes 通常要求使用稳定 API,例如:
apps/v1的 Deployment;batch/v1的 Job;networking.k8s.io/v1的 Ingress。
具体版本仍取决于目标集群发行版和版本。
4.3 命名模板、include 和作用域
templates/_helpers.tpl 中常见写法:
{{/*
生成资源全名。
*/}}
{{- define "demo.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
通用标签。
*/}}
{{- define "demo.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
在资源中使用:
metadata:
name: {{ include "demo.fullname" . }}
labels:
{{- include "demo.labels" . | nindent 4 }}
include 会返回字符串,因此可以继续交给 nindent、quote 等函数处理。相比之下,template 是动作,组合能力较弱。
模板的作用域会随着 with 和 range 改变:
{{- with .Values.image }}
image: "{{ .repository }}:{{ .tag }}"
{{- end }}
在 with 内部,. 已经变成 .Values.image,所以不能再直接写 .Values.image.repository。如果需要访问外层上下文,应保存根对象:
{{- $root := . }}
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
instance: {{ $root.Release.Name | quote }}
{{- end }}
这是模板中非常常见的错误来源。
4.4 YAML 缩进和空白控制
以下模板容易产生错误缩进:
labels:
{{ include "demo.labels" . }}
更安全的写法是:
labels:
{{- include "demo.labels" . | nindent 2 }}
nindent 2 表示先换行,再缩进两个空格。{{- 和 -}} 会去除模板边界的空白,但过度使用可能把相邻 YAML 拼接在一起,导致:
replicas: 2containers:
因此应始终渲染后检查,而不是凭模板源码猜结果。
4.5 条件、循环、默认值和强制校验
条件渲染:
{{- if .Values.service.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "demo.fullname" . }}
spec:
selector:
app.kubernetes.io/name: {{ .Chart.Name }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
{{- end }}
循环渲染环境变量:
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
默认值:
replicas: {{ .Values.replicaCount | default 1 }}
但 default 会把某些空值视为“没有设置”。如果 0 是合法值,就不能简单依赖 default。
强制要求字段:
image: "{{ required "image.repository is required" .Values.image.repository }}:{{ required "image.tag is required" .Values.image.tag }}"
条件失败会使渲染直接失败,而不是生成不完整的 Kubernetes 对象。
也可以用 fail:
{{- if and .Values.ingress.enabled (not .Values.ingress.host) }}
{{- fail "ingress.host must be set when ingress is enabled" }}
{{- end }}
4.6 模板渲染与 Kubernetes 校验必须分开诊断
先只渲染,不访问集群:
helm template demo ./demo \
-n demo \
-f values-prod.yaml \
--debug
检查最终 YAML:
helm lint ./demo -f values-prod.yaml
再让 Helm 调用 Kubernetes API 做 dry-run:
helm upgrade --install demo ./demo \
-n demo \
--create-namespace \
-f values-prod.yaml \
--dry-run=server \
--debug
区别是:
helm template主要检查模板渲染;helm lint检查 Chart 结构和常见问题;--dry-run=server会请求 API Server,可发现 API 版本、Schema、权限和对象校验问题;- 真正安装还可能遇到调度、镜像拉取、PVC、Webhook 和应用启动问题。
--dry-run 生成的输出可能包含 Secret 内容。不要把包含凭据的 dry-run 输出直接写入公共 CI 日志。
五、Values:配置如何进入模板
5.1 默认 Values
values.yaml:
replicaCount: 2
image:
repository: nginx
tag: "1.27.4"
pullPolicy: IfNotPresent
service:
enabled: true
type: ClusterIP
port: 8080
resources: {}
env:
- name: LOG_LEVEL
value: info
模板:
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
5.2 Values 的优先级
常见优先级从低到高大致是:
Chart 默认 values.yaml
< 依赖 Chart 的父级 Values
< 用户提供的 -f 文件,后写文件覆盖先写文件
< --set / --set-string / --set-file / --set-json
例如:
values.yaml:
replicaCount: 1
image:
tag: "1.0"
values-prod.yaml:
replicaCount: 3
image:
tag: "2.0"
命令:
helm upgrade --install demo ./demo \
-f values-prod.yaml \
--set replicaCount=5
最终结果:
replicaCount: 5
image:
tag: "2.0"
通常 Map 会递归合并,标量被高优先级值替换;列表应按“整体替换”理解,而不是假设 Helm 会按索引或键智能合并。列表合并行为是很多环境配置错误的来源。
5.3 --set 的类型陷阱
helm install demo ./demo \
--set replicaCount=3 \
--set image.tag=1.27
Helm 会尝试推断类型。镜像标签虽然看起来像数字,实际应当是字符串。更明确的写法是:
helm install demo ./demo \
--set replicaCount=3 \
--set-string image.tag=1.27
常用参数:
--set key=value:命令行设置值;--set-string key=value:强制字符串;--set-file key=path:把文件内容作为字符串值;--set-json key='{"a":1}':传入 JSON 结构。
例如把证书文件作为 Values:
helm upgrade --install demo ./demo \
--set-file tls.ca=ca.pem
这并不会自动创建 Secret,也不会自动加密文件内容。命令行、进程参数和 CI 日志可能暴露敏感信息。生产环境通常应使用外部 Secret 管理、Sealed Secrets、SOPS 或云厂商 Secret 集成,而不是把长期凭据直接写进普通 Values 文件。
5.4 Values Schema
values.schema.json 可以在 Chart 层面校验输入:
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"required": ["image"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1
},
"image": {
"type": "object",
"required": ["repository", "tag"],
"properties": {
"repository": { "type": "string", "minLength": 1 },
"tag": { "type": "string", "minLength": 1 }
}
}
}
}
这样可以在渲染前拒绝:
replicaCount: "three"
Schema 只能校验 Values 形状和部分值约束,不能证明:
- 镜像一定存在;
- PVC 一定有可用 StorageClass;
- 应用一定能连接数据库;
- IngressClass 一定已安装;
- Pod 一定能调度。
它减少的是配置输入错误,不是运行时不确定性。
六、一个完整可运行的 Chart 示例
下面构建一个简单的 Nginx Chart。
6.1 values.yaml
replicaCount: 2
image:
repository: nginx
tag: "1.27.4"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
podAnnotations: {}
6.2 templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "demo.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "demo.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
readinessProbe:
httpGet:
path: /
port: http
livenessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
Deployment.spec.selector 必须与 Pod 模板标签匹配。这个字段在创建后通常不可随意修改,因此模板生成的名称和 selector 设计需要稳定。
6.3 templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "demo.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "demo.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
Service 的 selector 必须能选中 Deployment 创建的 Pod。否则 Service 对象本身创建成功,但不会产生有效 Endpoints。
6.4 安装、观察和验证
helm lint ./demo
helm template demo ./demo -n demo
helm upgrade --install demo ./demo \
--namespace demo \
--create-namespace \
--wait \
--timeout 5m
预期过程:
- Helm 渲染 Deployment 和 Service;
- API Server 接受对象;
- Deployment 控制器创建 ReplicaSet;
- ReplicaSet 创建 Pod;
- Pod 拉取 Nginx 镜像并通过探针;
--wait等待 Helm 关注的资源达到就绪条件;- 命令成功退出。
检查资源:
helm status demo -n demo
helm get values demo -n demo
helm get manifest demo -n demo
kubectl get deploy,rs,pod,svc -n demo
kubectl rollout status deployment/demo-demo -n demo
如果 Pod 处于 ImagePullBackOff:
kubectl describe pod -n demo -l app.kubernetes.io/instance=demo
kubectl logs -n demo -l app.kubernetes.io/instance=demo
这时 Helm 模板可能完全正确,失败原因是镜像仓库、凭据、网络或节点运行时问题。
七、依赖 Chart 和模板边界
Chart 可以依赖其他 Chart。例如应用依赖 Redis:
# Chart.yaml
dependencies:
- name: redis
version: 20.6.0
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
然后下载依赖:
helm dependency update ./demo
condition 通常读取顶层 Values:
redis:
enabled: false
依赖 Chart 的 Values 可以通过嵌套键传入:
redis:
architecture: standalone
auth:
enabled: true
依赖管理的关键边界是:
- 父 Chart 可以覆盖子 Chart 的 Values;
- 子 Chart 通常不能直接读取父 Chart 任意私有字段;
- 全局配置可以放在
global,供多个子 Chart 约定使用; - 依赖版本应锁定,避免构建时无意获得不兼容版本。
Chart.lock 用于锁定依赖解析结果。生产构建应尽量基于锁文件和固定仓库来源,而不是每次重新解析浮动依赖。
八、Helm 如何更新资源:不要把它当作纯文本替换
执行:
helm upgrade demo ./demo -n demo
Helm 不只是删除旧 YAML 再创建新 YAML。它会根据历史 Release、当前模板结果和 Kubernetes API 的对象更新语义,对已有对象执行更新操作。最终具体行为还受到 Kubernetes 对象类型约束影响:
- Deployment 的 Pod 模板变化通常触发滚动更新;
- Service 的部分字段不可变;
- StatefulSet 的某些字段不可变;
- PVC 的容量扩容受 StorageClass 和底层存储支持限制;
- CRD 的升级有特殊规则;
- Secret 和 ConfigMap 内容变化不会自动重启 Pod,除非 Deployment 模板也发生变化。
常见做法是把配置内容的哈希加入 Pod 模板注解:
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
当 ConfigMap 模板内容变化时,哈希变化,Deployment 的 Pod 模板变化,Kubernetes 才会创建新 ReplicaSet。
这不是 Helm 自动行为,而是模板作者显式建立的因果链:
ConfigMap 内容变化
→ checksum 变化
→ Pod template annotation 变化
→ Deployment revision 变化
→ 新 Pod 创建
如果直接在模板中给 ConfigMap 加时间戳,则每次 Helm 操作都会触发重启,通常会破坏幂等性。
九、Release 状态、并发和失败路径
9.1 常见状态
helm list -A
helm status demo -n demo
helm history demo -n demo
常见状态包括:
deployed:当前 Release 已部署;pending-install:安装进行中或异常中断;pending-upgrade:升级进行中或异常中断;pending-rollback:回滚进行中或异常中断;failed:上一次操作失败;superseded:旧 revision 已被后续 revision 替代;uninstalled:已卸载但历史可能保留。
Helm 会对同一个 Release 加操作锁。两个 CI 任务同时执行:
helm upgrade demo ...
helm rollback demo ...
可能出现:
another operation (install/upgrade/rollback) is in progress
这不是 Kubernetes Pod 并发问题,而是 Helm Release 操作级别的并发冲突。应由发布系统保证同一个 Release 串行执行,而不是盲目重试多个写操作。
9.2 --atomic 的含义
helm upgrade --install demo ./demo \
-n demo \
--wait \
--timeout 10m \
--atomic
--atomic 通常意味着:
- 等待资源就绪;
- 如果安装或升级失败,自动执行清理或回滚;
- 命令最终返回失败。
它不能撤销所有副作用。例如:
- 已执行的数据库迁移不会自动逆向;
- 已发送的消息不会撤回;
- 外部云资源创建不会必然删除;
- PVC 中的数据不会因为回滚自动恢复;
- Job 写入外部系统的结果不会被 Kubernetes 还原。
--atomic 是发布失败处理策略,不是事务系统。
十、Hook:把动作插入 Release 生命周期
10.1 Hook 是什么
Hook 是带有特殊注解的 Kubernetes 资源。Helm 在特定生命周期阶段渲染并执行它们,例如:
pre-install:安装资源前;post-install:安装资源后;pre-upgrade:升级资源前;post-upgrade:升级资源后;pre-delete:卸载资源前;post-delete:卸载资源后;pre-rollback:回滚资源前;post-rollback:回滚资源后;test:通过helm test执行。
一个升级前数据库迁移 Job:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "demo.fullname" . }}-migration-{{ .Release.Revision }}
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 1
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: "{{ .Values.migration.image.repository }}:{{ .Values.migration.image.tag }}"
command: ["/app/migrate"]
这里使用 .Release.Revision 让每次 revision 生成不同的 Job 名称。否则同名 Job 在下一次运行时可能因为对象已存在而无法创建。
10.2 Hook 的排序和等待
多个 Hook 同一阶段执行时,Helm 会按以下概念排序:
helm.sh/hook-weight,数值越小越早;- 资源类型;
- 资源名称。
因此可以让迁移前置检查使用较小权重:
"helm.sh/hook-weight": "-20"
Hook 资源通常不是普通 Release 资源管理方式的一部分。Helm 会记录 Hook 的 manifest 和执行事件,但不会像普通 Deployment 那样持续负责其生命周期。因此必须设计删除策略:
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
常用策略:
before-hook-creation:创建同一 Hook 前删除旧 Hook;hook-succeeded:成功后删除;hook-failed:失败后删除。
如果保留失败 Job,诊断更容易;如果自动删除,环境更干净但证据会丢失。生产中常需在可观测性和清理之间取舍。
10.3 Hook 失败如何影响发布
如果 pre-upgrade Job 失败:
Helm upgrade
→ 执行 pre-upgrade Hook
→ Job 失败
→ 升级通常停止
→ 普通资源可能尚未应用
如果 post-upgrade Hook 失败:
Helm upgrade
→ 普通资源已更新
→ 执行 post-upgrade Hook
→ Hook 失败
→ Helm 命令失败,但部分资源已经是新版本
这说明 Hook 不是数据库事务。尤其是 post-* Hook 失败后,不能假设 Kubernetes 资源自动恢复到升级前状态。
10.4 Hook 的生产风险
数据库迁移是 Hook 的典型用途,但需要满足更强条件:
- 迁移脚本幂等;
- 同一 Release 不会并发执行迁移;
- 迁移失败时应用不会使用不兼容的 Schema;
- 新旧应用版本在迁移窗口内兼容;
- 回滚应用代码时,数据库 Schema 是否支持向后兼容;
- 超时后 Job 是否可能仍在运行;
- 失败证据是否保留。
如果迁移已经提交了不可逆的数据库变化,Helm 回滚只能恢复 Kubernetes 对象,无法恢复数据库状态。
十一、Rollback:回滚什么,不能回滚什么
11.1 查看历史
helm history demo -n demo
示例:
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 2025-03-01 10:00:00 superseded demo-0.1.0 1.0 Install complete
2 2025-03-01 11:00:00 superseded demo-0.2.0 2.0 Upgrade complete
3 2025-03-01 11:10:00 deployed demo-0.2.0 2.0 Rollback to 1
11.2 执行回滚
helm rollback demo 1 \
-n demo \
--wait \
--timeout 10m
回滚的核心过程可以表示为:
读取目标 revision 1 的 Chart 和配置
→ 生成目标 revision 1 对应的资源内容
→ 按回滚流程执行 pre-rollback Hook
→ 更新 Kubernetes 资源
→ 等待资源就绪(如果使用 --wait)
→ 执行 post-rollback Hook
→ 写入新的 Release revision
可以先观察计划:
helm get manifest demo -n demo --revision 1
helm get values demo -n demo --revision 1
如果当前 Helm 版本支持并且需要更明确的模拟验证,可使用:
helm rollback demo 1 -n demo --dry-run
具体 dry-run 行为和输出格式应以本机 Helm 版本为准;不要把它当成对 Pod 启动和控制器行为的完整模拟。
11.3 回滚的反例:数据库已经升级
假设:
revision 1:应用 v1,数据库字段 name
revision 2:迁移增加字段 display_name,应用 v2 使用 display_name
执行 revision 2 后:
数据库:已经有 display_name
应用:v2
此时 Helm rollback 到 revision 1:
Kubernetes Deployment:回到 v1
数据库:仍然有 display_name
如果 v1 兼容额外字段,回滚成功;如果迁移删除或重命名了 v1 需要的字段,应用可能启动失败。Helm 没有数据库快照,也不知道如何逆向迁移。
因此,安全回滚依赖的是应用和 Schema 的兼容策略,而不是 Helm 命令本身。
11.4 回滚的其他边界
Helm 回滚不能自动恢复:
- PVC 里的文件和数据库数据;
- 外部云负载均衡器的所有历史属性;
- 已发送的通知、消息和支付请求;
- 被 Hook 修改的外部系统;
- 被其他控制器或 GitOps 工具随后改写的字段;
- 已删除且没有写入 Release manifest 的外部资源。
此外,CRD 具有特殊风险。Chart 中的 crds/ 目录资源不会像普通模板一样参与常规升级和回滚流程;CRD 版本迁移通常应由专门的升级方案管理,而不能假设 helm rollback 会自动把 CRD Schema 变回旧版本。
十二、Release 与 Kubernetes 实际状态可能发生漂移
Helm 保存的是它认为某次发布应包含的对象内容,但 Kubernetes 集群中的实际对象可能被其他因素改变:
Helm Release manifest
↓
Kubernetes API 对象
↓
控制器、Webhook、管理员、GitOps 工具继续修改
例如:
- HPA 修改 Deployment 的
spec.replicas; - MutatingAdmissionWebhook 注入 sidecar;
- 管理员手工修改镜像;
- GitOps 控制器持续恢复另一份期望状态;
- 云控制器给 Service 添加 LoadBalancer 字段;
- Kubernetes 默认值和控制器自动填充字段。
执行:
helm get manifest demo -n demo
kubectl get deployment demo-demo -n demo -o yaml
两者不完全相同并不必然表示 Helm 出错。需要区分:
- Helm 模板生成的字段;
- API Server 默认字段;
- Admission Webhook 注入字段;
- 控制器维护字段;
- 人工或其他系统修改字段。
如果 Helm 和 GitOps 工具同时管理同一组资源,就会发生竞争:
Helm upgrade 修改 Deployment
→ GitOps 调谐发现与 Git 不同
→ GitOps 恢复旧值
→ 运维看到“Helm 成功但配置又变回去”
一个资源应尽量只有一个明确的声明式管理者。Helm 可以作为 GitOps 工具的渲染器,但此时通常由 Argo CD、Flux 等系统负责持续调谐,而不是在集群外重复执行 Helm 写操作。
十三、Helm 与 Kustomize 的边界
Helm 和 Kustomize 都能生成 Kubernetes YAML,但抽象重点不同。
13.1 Helm 更适合可复用 Chart 和参数化发布
Helm 典型结构是:
Chart + Values + Template
→ 一次 Release
它适合:
- 第三方软件分发;
- 多环境参数化;
- 依赖 Chart;
- 发布历史;
- Hook;
- 安装、升级和回滚。
13.2 Kustomize 更适合对已有 YAML 做变体组织
Kustomize 典型结构是:
Base
+ Overlay
+ Patch
+ Generator
→ 环境清单
它通常不引入类似 Helm Release 的发布历史,也不使用模板语言。配置通过资源合并、Patch、名称前缀、Generator 等方式变化。
13.3 两者不能互相替代所有能力
如果需求是:
同一个软件包交给多个团队
→ 每个团队提供 Values
→ 需要依赖、Hook、Release 历史
Helm 更自然。
如果需求是:
已有一套明确 YAML
→ 生产环境只修改少数字段
→ 希望避免通用模板逻辑
Kustomize 往往更直接。
也可以组合使用,例如 Helm 生成基础清单,Kustomize 再做环境 Patch,但此时必须明确:
- 哪一层拥有最终字段;
- 名称和标签在哪一层生成;
- Secret 和 ConfigMap 的哈希变化由谁负责;
- GitOps 工具实际调谐的是哪一层输出。
否则会形成难以诊断的多重渲染和覆盖关系。
十四、常用运维命令和结果解释
14.1 查询 Release
helm list -n demo
helm status demo -n demo
helm history demo -n demo
helm status 主要展示 Helm Release 状态和资源摘要,不等于应用健康检查。应用是否能正确响应,还需要:
kubectl get pods -n demo
kubectl describe pod -n demo <pod-name>
kubectl logs -n demo <pod-name>
kubectl get events -n demo --sort-by=.lastTimestamp
14.2 查看配置和渲染结果
helm get values demo -n demo
helm get values demo -n demo --all
helm get manifest demo -n demo
helm get hooks demo -n demo
helm get notes demo -n demo
区别:
helm get values:通常显示用户设置的值;helm get values --all:显示合并后的全部值,具体显示行为依 Helm 版本而定;helm get manifest:查看 Release 记录的普通 manifest;helm get hooks:查看 Hook;helm get notes:查看 Chart 输出的使用说明。
14.3 安装和升级
推荐先检查,再执行:
helm lint ./demo -f values-prod.yaml
helm template demo ./demo \
-n prod \
-f values-prod.yaml > rendered.yaml
kubectl apply --dry-run=server -f rendered.yaml
helm upgrade --install demo ./demo \
-n prod \
--create-namespace \
-f values-prod.yaml \
--wait \
--timeout 10m \
--history-max 20
--history-max 可以限制 Release 历史数量,避免历史 Secret 无限增长,但过小会减少可回滚的版本范围。
14.4 卸载
helm uninstall demo -n demo
卸载通常删除 Release manifest 中的普通资源,但以下内容可能不会按预期消失:
- Hook 创建的资源;
helm.sh/resource-policy: keep标记的资源;- 外部系统资源;
- PVC 数据;
- CRD 或其他 Chart 特殊处理资源。
卸载前应先检查:
helm get manifest demo -n demo
helm get hooks demo -n demo
kubectl get all -n demo
kubectl get pvc -n demo
不要仅依赖 kubectl get all,因为它不包含所有资源类型。
十五、典型故障的定位路径
15.1 模板解析失败
表现:
parse error
unexpected "{{"
unexpected EOF
定位:
helm lint ./demo
helm template demo ./demo --debug
优先检查:
{{、}}是否配对;if、with、range、define是否闭合;- YAML 缩进;
- 字符串引号;
- 变量作用域。
15.2 Values 缺失或类型错误
表现:
nil pointer evaluating interface
wrong type for value
定位:
helm template demo ./demo \
-f values-prod.yaml \
--debug
修复方式:
- 在
values.yaml提供完整结构; - 使用
required; - 使用
values.schema.json; - 检查
--set是否把字符串变成了数字或布尔值; - 检查列表是否被后一个文件整体替换。
15.3 Kubernetes API 拒绝
表现:
no matches for kind
unknown field
forbidden
immutable field
区分原因:
no matches for kind:目标集群没有该 API 或 CRD;unknown field:字段不符合目标版本 Schema;forbidden:执行身份没有权限;immutable field:对象创建后字段不可修改。
检查:
kubectl api-resources
kubectl api-versions
kubectl auth can-i create deployment -n demo
当前稳定 Kubernetes API 并不意味着所有云厂商集群、旧版本集群和扩展组件都一致。Chart 应明确声明支持的 Kubernetes 版本范围,并在 CI 中针对实际目标版本渲染和校验。
15.4 Helm 成功,但 Pod 不健康
表现:
STATUS: deployed
同时:
kubectl get pods -n demo
显示 CrashLoopBackOff、Pending 或 ImagePullBackOff。
这是因为 Helm 的“发布记录成功”和应用的“业务健康”不是同一个状态。继续检查:
kubectl describe pod -n demo <pod>
kubectl logs -n demo <pod> --previous
kubectl get events -n demo --sort-by=.lastTimestamp
kubectl get endpoints -n demo
可能原因包括:
- 镜像拉取失败;
- 探针路径错误;
- 资源请求导致无法调度;
- Secret 或 ConfigMap 不存在;
- PVC 未绑定;
- 应用启动命令错误;
- 网络策略阻止依赖访问。
15.5 Hook 卡住或失败
检查:
helm status demo -n demo
helm get hooks demo -n demo
kubectl get jobs,pods -n demo
kubectl describe job -n demo <job>
kubectl logs -n demo job/<job>
如果 Helm 命令超时,不能立即断定 Hook 已停止。Job 或 Pod 可能仍在集群中运行,尤其是超时、客户端断开或控制器延迟时。应先查看实际对象,再决定是否删除。
十六、安全、权限和供应链风险
Helm Chart 可以创建高权限 RBAC、读取 Secret、挂载主机路径和运行特权容器。因此安装第三方 Chart 前应检查:
helm show all oci://example.com/charts/demo
helm template demo oci://example.com/charts/demo \
--version 1.2.3 \
| less
关注:
ServiceAccount是否绑定cluster-admin;- 是否使用
hostNetwork、hostPID或privileged; - 是否挂载
/var/run/docker.sock; - 是否创建集群级 CRD 和 ClusterRole;
- 镜像来源和摘要是否固定;
- Hook 是否执行外部命令或访问敏感系统;
- 模板是否使用了
lookup读取集群对象; - 是否把 Secret 内容输出到 NOTES 或日志。
Helm 模板中的 lookup 可以查询集群现有对象:
{{- $existing := lookup "v1" "ConfigMap" .Release.Namespace "some-config" }}
它使渲染结果依赖集群当前状态,降低可重复性;离线 helm template 时也无法获得相同结果。除非确实需要读取已存在对象,否则应优先通过 Values 或显式资源依赖传递输入。
tpl 可以把字符串再次当成模板执行:
{{ tpl .Values.extraConfig . }}
它很灵活,但也扩大了配置执行能力。若 Values 来自不可信输入,不能把 tpl 当作普通字符串替换。
十七、生产发布的完整流程
一个较完整的发布流程可以是:
# 1. 更新依赖
helm dependency build ./demo
# 2. 静态检查
helm lint ./demo -f values-prod.yaml
# 3. 本地渲染
helm template demo ./demo \
-n prod \
-f values-prod.yaml \
--include-crds \
> rendered.yaml
# 4. 服务端 dry-run
helm upgrade --install demo ./demo \
-n prod \
-f values-prod.yaml \
--dry-run=server \
--debug
# 5. 执行发布
helm upgrade --install demo ./demo \
-n prod \
--create-namespace \
-f values-prod.yaml \
--wait \
--wait-for-jobs \
--timeout 10m \
--atomic \
--history-max 20
# 6. 验证
helm status demo -n prod
kubectl rollout status deployment/demo-demo -n prod
kubectl get pods -n prod
每一步解决的问题不同:
dependency build:确保依赖内容已准备;lint:发现 Chart 结构和模板常见错误;template:审查最终 YAML;dry-run=server:让 API Server 校验资源和权限;upgrade --install:执行幂等的安装或升级;--wait-for-jobs:等待 Job,包括某些迁移 Hook 的完成;--atomic:发布失败时自动尝试回滚或清理;kubectl rollout status:验证 Kubernetes 控制器是否真正完成滚动更新。
但这仍然不是完整业务验收。还需要根据应用执行健康检查、接口测试、数据迁移验证和指标观察。
十八、常见误解与反例
误解一:appVersion 会自动决定镜像版本
不会。只有模板显式引用它才会影响输出:
image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
即便如此,很多 Chart 仍应使用独立的 .Values.image.tag,因为 Chart 版本、应用版本和镜像版本不一定同步。
误解二:Helm 回滚等于完整系统回滚
不会。Helm 主要恢复由 Release 管理的 Kubernetes 对象内容。数据库、对象存储、消息系统、云资源和外部副作用需要各自的恢复机制。
误解三:Release 显示 deployed 就代表服务可用
不一定。没有 --wait 时,Helm 可能只确认 API 请求成功。即使使用 --wait,就绪探针也只能表达 Kubernetes 层面的就绪,不代表业务依赖和用户请求都正常。
误解四:Hook Job 会像 Deployment 一样自动被 Helm 管理
不会。Hook 资源具有特殊生命周期,必须配置删除策略,并单独处理日志、重试和失败清理。
误解五:修改 ConfigMap 后 Pod 会自动重启
通常不会。卷挂载内容可能最终更新,但应用是否重新读取取决于应用;环境变量不会自动更新。使用 checksum 注解或显式 rollout 才能让配置变化触发 Pod 模板变化。
误解六:多个系统同时改资源不会有问题
会。Helm、kubectl、Kustomize、GitOps 控制器和云控制器对同一字段拥有不同期望时,会产生漂移、覆盖和反复修改。必须明确资源和字段的管理边界。
十九、如何判断一次 Helm 回滚是否真正成功
不能只看命令退出码,应按层次验证:
helm status demo -n prod
helm history demo -n prod
kubectl get deployment -n prod demo-demo \
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
kubectl rollout status deployment/demo-demo -n prod
kubectl get pods -n prod
kubectl logs -n prod -l app.kubernetes.io/instance=demo --tail=100
验证逻辑是:
- Helm 是否创建了新的 rollback revision;
- Deployment 的镜像和配置是否回到目标版本;
- 新 ReplicaSet 是否完成滚动;
- Pod 是否通过探针;
- 应用日志是否显示兼容的数据库和外部依赖状态;
- 关键业务请求是否恢复。
如果第 1 步成功而第 4 步失败,说明 Release 元数据已更新,但应用运行状态仍未恢复。此时应继续分析 Pod、配置、镜像、存储和依赖,而不是重复执行回滚。
二十、总结:用正确的边界理解 Helm
Helm 的核心关系可以压缩为:
Chart:部署内容和模板
Values:本次渲染输入
Template:把输入生成 Kubernetes Manifest
Release:一次安装实例及其历史
Hook:插入安装、升级、删除、回滚阶段的动作
Rollback:用旧 revision 的发布内容创建一次新的发布
完整因果链是:
Chart + Values
→ Helm Template 渲染
→ Kubernetes API 校验与持久化
→ 控制器创建和维护运行对象
→ Helm 保存 Release revision
→ 后续 upgrade、rollback 或 GitOps 调谐继续改变状态
因此,使用 Helm 时至少要同时观察三个状态:
- 渲染状态:模板和 Values 是否生成了预期 YAML;
- 发布状态:Helm Release 是否成功提交、升级或回滚;
- 运行状态:Kubernetes 控制器和应用是否真正健康。
只有三者都验证通过,才可以把一次 Helm 操作视为完成。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:ResourceQuota 与 LimitRange:租户预算、默认值、对象数量和治理
- 下一篇:Kustomize 配置管理:Base、Overlay、Patch、Generator 和边界
- 延伸:Kubernetes GitOps:期望状态、调谐、Secret、漂移、晋级和回滚
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论