【Docker-Day 44】Helm 进阶:从 `values.yaml` 到模板函数,亲手打造可复用的高质量 Chart
Helm 的真正威力并不仅仅在于使用他人预先打包好的 Chart,更在于其强大的定制化能力。这种能力的核心便是其模板引擎。本文将深入 Helm Chart 的两大基石——**模板(Templates)**与**值(Values)**,详细拆解其工作原理、语法规则以及最佳实践。通过本文,你将学会如何阅读、修改甚至从零开始创建灵活、可复用且高度可配置的 Helm Chart,真正实现对 Kuberne
Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来
Python系列文章目录
PyTorch系列文章目录
机器学习系列文章目录
深度学习系列文章目录
Java系列文章目录
JavaScript系列文章目录
Python系列文章目录
Go语言系列文章目录
Docker系列文章目录
01-【Docker-Day 1】告别部署噩梦:为什么说 Docker 是每个开发者的必备技能?
02-【Docker-Day 2】从零开始:手把手教你在 Windows、macOS 和 Linux 上安装 Docker
03-【Docker-Day 3】深入浅出:彻底搞懂 Docker 的三大核心基石——镜像、容器与仓库
04-【Docker-Day 4】从创建到删除:一文精通 Docker 容器核心操作命令
05-【Docker-Day 5】玩转 Docker 镜像:search, pull, tag, rmi 四大金刚命令详解
06-【Docker-Day 6】从零到一:精通 Dockerfile 核心指令 (FROM, WORKDIR, COPY, RUN)
07-【Docker-Day 7】揭秘 Dockerfile 启动指令:CMD、ENTRYPOINT、ENV、ARG 与 EXPOSE 详解
08-【Docker-Day 8】高手进阶:构建更小、更快、更安全的 Docker 镜像
09-【Docker-Day 9】实战终极指南:手把手教你将 Node.js 应用容器化
10-【Docker-Day 10】容器的“持久化”记忆:深入解析 Docker 数据卷 (Volume)
11-【Docker-Day 11】Docker 绑定挂载 (Bind Mount) 实战:本地代码如何与容器实时同步?
12-【Docker-Day 12】揭秘容器网络:深入理解 Docker Bridge 模式与端口映射
13-【Docker-Day 13】超越默认Bridge:精通Docker Host、None与自定义网络模式
14-【Docker-Day 14】Docker Compose深度解析
15-【Docker-Day 15】一键部署 WordPress!Docker Compose 实战终极指南
16-【Docker-Day 16】告别单机时代:为什么 Docker Compose 不够用,而你需要 Kubernetes?
17-【Docker-Day 17】K8s 架构全解析:深入理解 Kubernetes 的大脑 (Master) 与四肢 (Node)
18-【Docker-Day 18】告别选择困难症:一文掌握 Minikube、kind、k3d,轻松搭建你的第一个 K8s 集群
19-【Docker-Day 19】万物皆 YAML:掌握 Kubernetes 声明式 API 的艺术
20-【Docker-Day 20】揭秘 Kubernetes 的原子单位:深入理解 Pod
21-【Docker-Day 21】Pod的守护神:ReplicaSet与ReplicationController,轻松实现应用高可用
22-【K8s-Day 22】深入解析 Kubernetes Deployment:现代应用部署的基石与滚动更新的艺术
23-【K8s-Day 23】从 Pod 的“失联”到 Service 的“牵线”:深入理解 ClusterIP 核心原理
24-【Docker-Day 24】K8s网络解密:深入NodePort与LoadBalancer,让你的应用走出集群
25-【Docker-Day 25】深入理解 Kubernetes Namespace:实现多租户与环境隔离的利器
26-【Docker-Day 26】K8s实战演练:从零开始部署一个完整的前后端分离Web应用
27-【K8s-Day 27】应用的“体检医生”:深入解析 Kubernetes 健康检查探针 (Probe)
28-【Docker-Day 28】K8s 核心配置管理:解密 ConfigMap,告别硬编码!
29-【Docker-Day 29】K8s 安全第一课:揭秘敏感信息管理器 Secret
30-【Docker-Day 30】解密 K8s 的“硬盘”:深入理解 PersistentVolume (PV) 与 PersistentVolumeClaim (PVC)
31-【Docker-Day 31】告别手动创建 PV!一文搞懂 Kubernetes StorageClass 工作原理与实战
32-【K8s-Day 32】StatefulSet 深度解析:为你的数据库和有状态应用保驾护航
33-【Docker-Day 33】掌握 K8s 任务调度:DaemonSet、Job、CronJob 实战指南
34-【Docker-Day 34】Kubernetes Ingress 详解:从小白到精通的 K8s 流量路由指南
35-【Docker-Day 35】实战部署 Nginx Ingress Controller:集群流量入口的终极指南
36-【Docker-Day 36】K8s网络解密:CNI接口如何为Pod分配IP地址?
37-【Docker-Day 37】K8s 网络“防火墙”:NetworkPolicy 深度解析与实战
38-【Docker-Day 38】Kubernetes 核心调度:深入解析资源请求 (Requests) 与限制 (Limits) 的奥秘
39-【Docker-Day 39】揭秘 Kubernetes 高级调度:从 nodeSelector 到亲和性与污点的实战指南
40-【Docker-Day 40】K8s 核心组件 HPA 深度实践:从原理到配置,实现智能扩缩容
41-【Docker-Day 41】解密 Kubernetes 权限管理:RBAC 核心概念(Role, ClusterRole)与实战演练
42-【Docker-Day 42】K8s 安全基石:掌握 ServiceAccount,精细化控制 Pod 权限
43-【Docker-Day 43】告别繁琐 YAML:K8s 包管理器 Helm 入门指南
44-【Docker-Day 44】Helm 进阶:从 values.yaml 到模板函数,亲手打造可复用的高质量 Chart
文章目录
摘要
在上一篇文章中,我们初步认识了 Kubernetes 的包管理器 Helm,并学习了如何使用它来快速部署应用。然而,Helm 的真正威力并不仅仅在于使用他人预先打包好的 Chart,更在于其强大的定制化能力。这种能力的核心便是其模板引擎。本文将深入 Helm Chart 的两大基石——模板(Templates)与值(Values),详细拆解其工作原理、语法规则以及最佳实践。通过本文,你将学会如何阅读、修改甚至从零开始创建灵活、可复用且高度可配置的 Helm Chart,真正实现对 Kubernetes 应用部署的精细化掌控。
一、Helm Chart 结构再回顾:蓝图的构成
在深入模板和值之前,我们有必要再次快速回顾一个标准 Helm Chart 的目录结构,理解每个部分扮演的角色。这就像是研究一栋建筑的设计图,先要明白客厅、卧室、厨房各自的位置和功能。
1.1 Chart 的核心文件与目录
一个典型的 Chart 包含了以下关键文件和目录,它们共同定义了一个 Kubernetes 应用的打包格式。
| 文件/目录 | 作用 | 描述 |
|---|---|---|
Chart.yaml |
元数据文件 | 包含 Chart 的基本信息,如名称、版本、描述、API 版本等。是 Chart 的“身份证”。 |
values.yaml |
默认配置文件 | 存储模板中可以被用户自定义的默认值。这是 Chart 可配置性的核心。 |
templates/ |
模板目录 | 存放所有 Kubernetes 资源清单的模板文件(如 Deployment, Service 等)。Helm 会将这些文件与 values.yaml 中的值结合,生成最终的 YAML。 |
_helpers.tpl |
辅助模板文件 | 位于 templates/ 目录下,通常用于定义可复用的模板片段(命名模板),保持主模板的整洁。 |
charts/ |
子 Chart 目录 | 用于存放该 Chart 依赖的其他 Chart(子 Chart),实现复杂应用的组合。 |
crds/ |
CRD 目录 | 存放自定义资源定义(Custom Resource Definitions)的 YAML 文件。 |
.helmignore |
忽略文件 | 类似于 .gitignore,定义在打包 Chart 时应忽略哪些文件。 |
1.2 模板与值的联动机制
Helm 的工作流程可以概括为一个“渲染”过程。它将 values.yaml(或用户提供的其他值)作为输入数据,通过 Go 模板引擎,对 templates/ 目录下的所有文件进行处理,最终生成可以被 Kubernetes API Server 直接应用的、标准的 YAML 清单文件。
这个过程可以直观地表示为:
理解了这个核心机制,我们就能明白,学习 Helm Chart 定制,本质上就是学习如何编写模板(templates/)和如何定义并使用值(values.yaml)。
二、深入 values.yaml:Chart 的配置中心
values.yaml 文件是用户与 Chart 交互的主要界面。一个设计良好的 values.yaml 文件应该像一份清晰的 API 文档,让使用者可以轻松理解并修改 Chart 的行为。
2.1 values.yaml 的角色与结构
它的主要角色是为模板提供默认配置。文件本身是一个标准的 YAML 文件,通过层级结构来组织配置项。
例如,一个简单的 Nginx Chart 的 values.yaml 可能如下所示:
# values.yaml
# 副本数量
replicaCount: 1
image:
# 镜像仓库
repository: nginx
# 镜像拉取策略
pullPolicy: IfNotPresent
# 镜像标签,留空则使用 Chart 的 appVersion
tag: ""
service:
type: ClusterIP
port: 80
# ... 其他配置
这种结构化的方式不仅可读性高,也便于在模板中通过路径进行访问(例如 image.repository)。
2.2 定义默认值
values.yaml 中定义的每一个值都是一个默认值。这意味着如果用户在安装 Chart 时不提供自己的配置,Helm 就会使用这些值。这为 Chart 提供了一个“开箱即用”的基础配置。
2.3 值的覆盖方式
Helm 提供了多种方式来覆盖 values.yaml 中的默认值,这赋予了 Chart 极大的灵活性。
2.3.1 使用 --set 命令行参数
--set 参数允许在 helm install 或 helm upgrade 命令中直接指定单个或多个值。
(1) 覆盖顶层值
# 将副本数修改为 3
helm install my-release ./my-chart --set replicaCount=3
(2) 覆盖嵌套值
对于嵌套的值,使用点(.)来表示层级关系。
# 修改镜像仓库和标签
helm install my-release ./my-chart \
--set image.repository=my-registry/my-nginx \
--set image.tag=1.21.6
2.3.2 使用 -f 或 --values 指定自定义文件
当需要修改的配置项较多时,使用 --set 会让命令变得冗长且难以管理。更好的方式是创建一个自定义的 values 文件,然后通过 -f 或 --values 参数传递给 Helm。
(1) 创建自定义配置文件
例如,我们创建一个 prod-values.yaml 文件用于生产环境部署:
# prod-values.yaml
replicaCount: 5
image:
repository: registry.my-company.com/nginx
tag: "stable"
pullPolicy: Always
service:
type: LoadBalancer
(2) 在安装时使用
helm install my-prod-release ./my-chart -f prod-values.yaml
Helm 会将 prod-values.yaml 与 Chart 内置的 values.yaml 进行深度合并(deep merge)。如果两个文件中存在相同的键,prod-values.yaml 中的值会覆盖默认值。未在 prod-values.yaml 中指定的键,则继续使用 values.yaml 中的默认值。
三、掌握模板语法:赋予 Chart 动态能力
templates/ 目录是魔法发生的地方。Helm 使用 Go 语言的模板库来处理这些文件。下面我们来学习最核心的模板语法。
3.1 模板基础:访问 values.yaml 中的值
所有模板操作都包裹在 {{ }} 中。要访问 values.yaml 中的值,需要通过内置对象 .Values。
3.1.1 基本语法:{{ .Values.<key> }}
. 代表当前作用域的根对象,在模板的顶层,它包含了所有可访问的内置对象,其中 .Values 就指向了 values.yaml 合并后的内容。
values.yaml 文件:
replicaCount: 2
templates/deployment.yaml 模板:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: {{ .Values.replicaCount }} # <-- 访问 replicaCount
# ...
渲染结果:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 2
# ...
3.1.2 嵌套值的访问
通过点(.)链式访问嵌套的 YAML 结构。
values.yaml 文件:
image:
repository: nginx
tag: "1.20"
templates/deployment.yaml 模板:
# ...
spec:
template:
spec:
containers:
- name: web
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" # <-- 访问嵌套值
渲染结果:
# ...
spec:
template:
spec:
containers:
- name: web
image: "nginx:1.20"
3.2 流程控制:让模板“思考”
模板不仅仅是简单的变量替换,它还支持条件判断和循环,这使得 Chart 能够根据不同的配置生成截然不同的 YAML 结构。
3.2.1 条件判断:if/else
if 语句允许我们根据一个值的存在或其布尔值为 true 来条件性地渲染某段模板。
场景:只有在 serviceAccount.create 为 true 时,才创建 ServiceAccount 资源。
values.yaml 文件:
serviceAccount:
# 指定是否创建 ServiceAccount
create: true
# 如果不创建,则使用此名称
name: ""
templates/deployment.yaml 模板:
# ...
spec:
template:
spec:
# 使用 if/else 块
{{- if .Values.serviceAccount.create }}
serviceAccountName: {{ .Release.Name }}-sa # 如果创建,则使用一个生成的名字
{{- else }}
serviceAccountName: {{ .Values.serviceAccount.name | default "default" }} # 否则使用指定的名字,或 "default"
{{- end }}
# ...
注意: {{- 和 -}} 中的 - 用于去除模板标签前后的空白字符(包括换行),这对于生成格式正确的 YAML至关重要。
3.2.2 循环遍历:range
range 语句用于遍历列表(数组)或字典(映射)。
场景:根据 values.yaml 中定义的端口列表,在 Service 中动态生成多个端口。
values.yaml 文件:
service:
ports:
- name: http
port: 80
targetPort: 8080
- name: metrics
port: 9100
targetPort: 9100
templates/service.yaml 模板:
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
ports:
# 使用 range 遍历 .Values.service.ports
{{- range .Values.service.ports }}
- name: {{ .name }}
port: {{ .port }}
targetPort: {{ .targetPort }}
{{- end }}
selector:
app: my-app
在 range 循环内部,. 的上下文变成了当前遍历到的元素。所以可以直接使用 .name、.port。
3.3 内置对象:模板的“上帝视角”
除了 .Values,Helm 还提供了许多其他有用的内置对象,它们提供了关于发布(Release)、Chart 本身、集群能力等信息。
| 内置对象 | 描述 | 示例用法 |
|---|---|---|
.Release |
包含关于本次发布的信息 | {{ .Release.Name }} (发布名称), {{ .Release.Namespace }} (发布命名空间) |
.Chart |
包含关于Chart本身的信息 | {{ .Chart.Name }} (Chart 名称), {{ .Chart.Version }} (Chart 版本) |
.Files |
允许访问 Chart 中的非模板文件 | {{ .Files.Get "config.txt" }} |
.Capabilities |
提供关于 Kubernetes 集群能力的信息 | {{ .Capabilities.KubeVersion.Version }} (K8s 版本) |
.Template |
包含当前正在渲染的模板的信息 | {{ .Template.Name }} (模板文件路径) |
3.4 函数与管道:模板的数据处理器
Helm 提供了超过60个内置函数,用于在模板中转换数据、执行逻辑等。函数通过管道操作符 | 进行调用。
3.4.1 常用函数:default, quote, upper
default: 如果一个值为空或不存在,则提供一个默认值。# 如果 .Values.image.tag 为空,则使用 .Chart.AppVersion tag: {{ .Values.image.tag | default .Chart.AppVersion }}quote: 将一个字符串用双引号包裹起来。这对于所有字符串类型的值都至关重要,以避免 YAML 解析错误。image: "my-repo:{{ .Values.tag | quote }}"upper: 将字符串转换为大写。name: {{ .Release.Name | upper }}
3.4.2 管道操作符:|
管道操作符 | 的作用类似于 Linux shell 中的管道,它将前一个命令(或值)的输出作为后一个函数的输入。
# 链式调用
config: {{ .Values.someSetting | upper | quote }}
这个表达式会先将 .Values.someSetting 的值转换为大写,然后将结果用双引号包裹起来。
四、最佳实践:编写可维护的模板
当 Chart 变得复杂时,保持模板的整洁和可维护性至关重要。
4.1 使用命名模板(Named Templates)
命名模板允许你创建可复用的模板片段,就像编程中的函数一样。
4.1.1 _helpers.tpl 的作用
按照惯例,所有可复用的命名模板都定义在一个名为 _helpers.tpl 的文件中。文件名以下划线 _ 开头,意味着 Helm 不会将这个文件渲染为 Kubernetes 的清单文件。它纯粹是为其他模板提供辅助工具。
4.1.2 定义与引用 (define 和 include)
(1) 在 _helpers.tpl 中定义
使用 define 动作来定义一个命名模板。一个常见的用途是生成标准的标签。
{{/* _helpers.tpl */}}
{{/*
生成标准标签
*/}}
{{- define "mychart.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
(2) 在其他模板中引用
使用 include 函数来引用并渲染这个命名模板。
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-deployment
labels:
{{- include "mychart.labels" . | nindent 4 }} # <-- 引用命名模板
spec:
selector:
matchLabels:
{{- include "mychart.labels" . | nindent 6 }} # <-- 再次引用
template:
metadata:
labels:
{{- include "mychart.labels" . | nindent 8 }} # <-- 第三次引用
# ...
注意 include "mychart.labels" . 中的 .,它将父模板的整个作用域传递给了命名模板,这样命名模板内部才能访问 .Chart 和 .Release 等对象。
4.2 控制 YAML 缩进:nindent
YAML 对缩进非常敏感。当使用 include 插入一个多行文本块时,它的缩进可能是不正确的。nindent 函数可以解决这个问题,它会将模板渲染出的每一行都增加指定数量的空格缩进。在上面的例子中,我们使用 nindent 4、nindent 6 和 nindent 8 来确保标签块在 YAML 的不同层级都有正确的缩进。
4.3 提供清晰的注释和文档
values.yaml中的注释:为每个可配置项提供清晰的注释,解释它的作用和可选值。- 模板中的注释:使用
{{/* ... */}}语法在模板中添加不会被渲染到最终输出的注释,解释复杂的逻辑。
五、实战演练:定制一个 Nginx Deployment Chart
让我们将所学知识整合起来,创建一个灵活的 Nginx Deployment Chart。
5.1 初始 Chart 结构
首先,创建一个新的 Chart:
helm create my-nginx-chart
然后清理 templates 目录,我们只保留 deployment.yaml 和 _helpers.tpl,并清空 values.yaml。
5.2 编写 values.yaml
# values.yaml
# 部署的副本数量
replicaCount: 1
image:
# 镜像仓库
repository: "nginx"
# 镜像标签,如果为空,则使用 Chart 的 appVersion
tag: ""
# 镜像拉取策略
pullPolicy: IfNotPresent
# 为 Pod 添加的自定义注解
podAnnotations: {}
# my-annotation: "my-value"
# 环境变量
# env:
# - name: MY_ENV_VAR
# value: "my_value"
5.3 编写 deployment.yaml 模板
{{/* templates/deployment.yaml */}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
labels:
{{- include "my-nginx-chart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "my-nginx-chart.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "my-nginx-chart.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
{{- if .Values.env }}
env:
{{- toYaml .Values.env | nindent 12 }}
{{- end }}
_helpers.tpl 文件:
{{/* templates/_helpers.tpl */}}
{{/*
生成 Chart 标签
*/}}
{{- define "my-nginx-chart.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | quote }}
{{ include "my-nginx-chart.selectorLabels" . }}
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
{{- end -}}
{{/*
生成选择器标签
*/}}
{{- define "my-nginx-chart.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name | quote }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- end -}}
这个例子展示了:
- 使用
include复用标签。 - 使用
default为镜像tag提供备用值。 - 使用
with语句优雅地处理可选的podAnnotations。with可以将作用域切换到指定的对象,如果对象为空则不渲染块内容。 - 使用
if检查env是否存在,并使用toYaml函数将一个values中的 YAML 块直接转换为格式正确的 YAML 字符串。
5.4 验证与部署
在部署前,可以使用 helm template 或 helm install --dry-run 命令来检查渲染后的结果。
# 检查默认值渲染结果
helm template my-release ./my-nginx-chart
# 使用自定义值进行测试
helm install my-test-release ./my-nginx-chart --dry-run \
--set replicaCount=3 \
--set image.tag="1.21-alpine" \
--set-file env=my-envs.yaml # 假设 my-envs.yaml 包含 env 列表
确认无误后,即可正式部署。
六、总结
掌握 Helm 的模板和值是从 Helm 用户向 Helm 开发者转变的关键一步。通过本文的学习,我们应把握以下核心要点:
- 核心机制:Helm 的本质是模板渲染,它将
values.yaml(及用户覆盖值)作为数据,输入到位于templates/目录下的 Go 模板中,生成最终的 Kubernetes YAML 清单。 values.yaml是配置入口:它为 Chart 提供了默认配置,并且是用户进行自定义的主要接口。其结构应该清晰、易于理解。- 模板语法是动态能力的关键:
- 使用
{{ .Values.path.to.key }}访问值。 - 利用
if/else和range实现条件渲染和循环,使 Chart 更加灵活。 - 善用
.Release、.Chart等内置对象,获取发布和 Chart 的上下文信息。 - 通过函数和管道(如
quote,default,nindent)来处理和格式化数据,确保生成的 YAML 正确无误。
- 使用
- 最佳实践提升可维护性:
- 将可复用的逻辑抽象为命名模板,并集中存放在
_helpers.tpl文件中,通过include调用。 - 时刻注意 YAML 的缩进问题,并使用
nindent函数来精确控制。 - 编写良好的注释是让其他人(以及未来的自己)能够理解和使用你的 Chart 的重要一环。
- 将可复用的逻辑抽象为命名模板,并集中存放在
通过将这些概念和技巧融会贯通,你将能够构建出强大、灵活且易于维护的 Helm Chart,极大地提升 Kubernetes 应用的部署效率和标准化水平。
更多推荐

所有评论(0)