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 清单文件。

这个过程可以直观地表示为:

输入数据
模板
渲染 (Render)
kubectl apply
values.yaml &
用户自定义值
Helm 模板引擎
templates/ 目录下的
模板文件
最终生成的
Kubernetes YAML 清单
Kubernetes 集群

理解了这个核心机制,我们就能明白,学习 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 installhelm 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.createtrue 时,才创建 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 定义与引用 (defineinclude)

(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 4nindent 6nindent 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 语句优雅地处理可选的 podAnnotationswith 可以将作用域切换到指定的对象,如果对象为空则不渲染块内容。
  • 使用 if 检查 env 是否存在,并使用 toYaml 函数将一个 values 中的 YAML 块直接转换为格式正确的 YAML 字符串。

5.4 验证与部署

在部署前,可以使用 helm templatehelm 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 开发者转变的关键一步。通过本文的学习,我们应把握以下核心要点:

  1. 核心机制:Helm 的本质是模板渲染,它将 values.yaml(及用户覆盖值)作为数据,输入到位于 templates/ 目录下的 Go 模板中,生成最终的 Kubernetes YAML 清单。
  2. values.yaml 是配置入口:它为 Chart 提供了默认配置,并且是用户进行自定义的主要接口。其结构应该清晰、易于理解。
  3. 模板语法是动态能力的关键
    • 使用 {{ .Values.path.to.key }} 访问值。
    • 利用 if/elserange 实现条件渲染和循环,使 Chart 更加灵活。
    • 善用 .Release.Chart内置对象,获取发布和 Chart 的上下文信息。
    • 通过函数和管道(如 quote, default, nindent)来处理和格式化数据,确保生成的 YAML 正确无误。
  4. 最佳实践提升可维护性
    • 将可复用的逻辑抽象为命名模板,并集中存放在 _helpers.tpl 文件中,通过 include 调用。
    • 时刻注意 YAML 的缩进问题,并使用 nindent 函数来精确控制。
    • 编写良好的注释是让其他人(以及未来的自己)能够理解和使用你的 Chart 的重要一环。

通过将这些概念和技巧融会贯通,你将能够构建出强大、灵活且易于维护的 Helm Chart,极大地提升 Kubernetes 应用的部署效率和标准化水平。


Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐