Helm是Kubernetes的包管理工具,类似Linux系统的yum,apt-get,macOs的homebrew。使用Helm template可以方便我们部署和管理自己的应用。本篇将基于Helm3.12.0,通过几个例子快速入门Helm 语法。
一、 helm chart 结构
学习helm语法之前先了解一下helmchart结构,helm通过chart描述一个应用,安装了helm以后就可以通过命令下面这个命令创建一个chart了。
helm create mychart
这个基本的chart的结构是这样的:
bash
mychart
├── Chart.yaml
├── charts
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
- templates/ 目录下的.yaml文件是模板文件。当Helm需要生成chart的时,会渲染该目录下的模板文件,将渲染结果发送给kubernetes。
- values.yaml 模板的配置文件,用来向模板传递数据。用户可以在helm install 或者 helm upgrade可以指定新的值来覆盖默认值。
- Chart.yaml 文件保存chart的基本描述信息,这些描述信息也可以在模板中被引用。
- charts/目录 可以 包含其他的chart(称之为 子chart)。
- _helpers.tpl用于保存一些可以在该chart中复用的模板。
- NOTES.txt install的描述信息
二、 内置对象
内置对象中最常用的就是Values和Files,通过它们读取配置信息和访问外部文件。
Values
values 对象的值有四个来源
- 根目录的values.yaml 文件
- chart目录中的values.yaml 文件
- 使用 helm install 或者 helm upgrade 的 -f 或者 --values 参数传入的自定义的 yaml 文件
- 通过 --set 参数传入的值
Files
在chart中提供访问所有的非特殊文件的对象。你不能使用它访问Template对象,只能访问其他文件
- Files.Get 通过文件名获取文件的方法。 (.Files.Get config.ini)
- Files.GetBytes 用字节数组代替字符串获取文件内容的方法。 对图片之类的文件很有用
- Files.Glob 用给定的shell glob模式匹配文件名返回文件列表的方法
- Files.Lines 逐行读取文件内容的方法。迭代文件中每一行时很有用
- Files.AsSecrets 使用Base 64编码字符串返回文件体的方法
- Files.AsConfig 使用YAML格式返回文件体的方法
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "demo.fullname" . }}
data:
{{ (.Files.Glob "files/confd/**").AsConfig |indent 2 }}
内置的值都是以大写字母开始。 这是符合Go的命名惯例。当你创建自己的名称时,可以按照团队约定自由设置。 就像很多你在 Artifact Hub 中看到的chart,其团队选择使用首字母小写将本地名称与内置对象区分开
see: glob package - github.com/gobwas/glob - Go Packages
Capabilities
提供关于Kubernetes集群支持功能的信息
Template
包含当前被执行的当前模板信息
Chart
Chart.yaml文件内容。 Chart.yaml里的所有数据在这里都可以可访问的。比如 {{ .Chart.Name }}-{{ .Chart.Version }} 会打印出 mychart-0.1.0
Release
Release对象描述了版本发布本身。
三、values.yaml
模板的配置文件,用来向模板传递数据。用户可以在helm install 或者 helm upgrade可以指定新的值来覆盖默认值。
values.yaml
yaml
favoriteDrink: coffee
templates/configmap.yaml
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favoriteDrink }}
yaml
$ helm install geared-marsupi ./mychart --dry-run --debug
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: geared-marsupi-configmap
data:
myvalue: "Hello World"
drink: coffee
四、 常用函数
管道符 "|"
模板语言其中一个强大功能是 管道 概念。借鉴UNIX中的概念,下面两行是等效的表示读取food数据,并且加上双引号
yaml
{{ .Values.favorite.food | quote }} {{ quote .Values.favorite.food }}
default 函数
default是模板中频繁使用的一个函数,这个函数允许你在模板中指定一个默认值,当给定值没有取值是返回这个默认值。
语法: default DEFAULT_VALUE GIVEN_VALUE
这个例子中读取food值,如果food是
{{ $food := default "apple" .Values.favorite.food }}
indent
helm描述kubernetes资源文件用indent和nindent处理缩进,缩进错误chart就会解析失败。
- indent 2 $lots_of_text 表示在当前模板位置向后缩进两位输出字符串,注意是当前模板的位置开始,即模板的位置也是有效的
- nindent 2 $lots_of_text 表示在当前位置增加一个空行,从新行的开始位置缩进两位,输出字符串,注意此时模板的位置已经无效了
toYaml
改函数可以将列表,切片,数组,字典或对象转换成已缩进的yaml,但最常用的功能是该函数可以从任意源拷贝yaml块。
下面这个例子中我们用toYaml从values.yaml读取内容。
values.yaml
yaml
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nvidia-gpu
operator: Exists
模版片段
yaml
{{- if .Values.affinity }}
affinity:
{{- toYaml .Values.affinity | nindent 8 }}
{{- end }}
渲染结果:
yaml
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nvidia-gpu
operator: Exists
除了上面介绍的几个函数,helm 还提供了很多函数,我们可以参考官方文档,在此就不介绍了。下面是官方文档中提供的函数的分类:
- Cryptographic and Security 处理加密的函数
- Date 时间数据处理函数
- Dictionaries 字典函数
- Encoding 编码解码函数
- File Path Helm模板函数没有访问文件系统的权限,提供了遵循文件路径规范的函数。
- Kubernetes and Chart Helm 包含了用于 Kubernetes的函数
- Logic and Flow Control 逻辑函数
- Lists 提供了一个简单的list类型,包含任意顺序的列表。类似于数组或切片,但列表是被设计用于不可变数据类型。
- Math 除非另外指定,否则所有的math函数都是操作 int64 的值。
- Network Helm提供了一个网络函数:getHostByName,它接收一个域名返回IP地址。
- Reflection 反射工具函数,用于高级模板开发
- Regular Expressions 正则表达式函数
- Semantic Versions 语义版本函数,用于分析比较语义版本
- String 字符串函数
- Type Conversion 类型转换函数
- URL URL解析组装函数
- UUID helm 提供了一个uuidv4函数,用于生成UUID v4 通用唯一ID
五、流控制
if else
ifelse的结构是这样的,中间的else if和 else是可选的。
yaml
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
chart语法中借鉴了golang语言的0值的概念。如果是以下值时,PIPELINE会被设置为 false,除此之外都是true:
- 布尔false
- 数字0
- 空字符串
- nil (空或null)
- 空集合(map, slice, tuple, dict, array)
控制空格
我们先看一个例子,在下面的例子中我们看到渲染的结果有个空行,虽然合法,但看起来丑。
他是怎么形成的呢,原因是yaml认为空白是有意义的,而helm模版只移除了 {{ 和 }} 里面的内容,剩下下的空格完全保持原样。
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | default "tea" | quote }}
food: {{ .Values.favorite.food | upper | quote }}
{{ if eq .Values.favorite.drink "coffee" }}
mug: "true"
{{ end }}
渲染后的结果
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: telling-chimp-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
那helm如何管理空格呢,模板声明的大括号语法可以通过特殊的字符修改,并通知模板引擎取消空格。
{{- (包括添加的横杠和空格)表示向左删除空白, 而 -}}表示右边的空格应该被去掉。 一定注意空格就是换行,对于上面的例子我们值需要在if前增加 - 就可以去掉前面的空格。就像这样:
yaml
{{- if eq .Values.favorite.drink "coffee" }}
mug: "true"
{{- end }}
with
with允许你为特定对象设定当前作用域(.)。
helm 中 .是对当前作用域的引用。因此 .Values就是告诉模板在当前作用域查找Values对象。
with的作用是修改作用域,在下面的例子中,我们通过with 修改配置映射中的.的作用域指向.Values.favorite,然后.就表示 .Values.favorite,而with后面的块只有在 PIPELINE 的值不为空时才会执行。
所以有时候用with就可以省略if。
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ $.Release.Name }}
{{- end }}
在上面例子中.Release不在作用域范围内,所以增加了$,表示根作用域下的Release对象,否则会报错。
range
helm中只有一种循环 range,提供类似 for each的循环。range可以遍历list,tuple,数字
遍历values.yaml中的list:
yaml
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
toppings: |-
{{- range $.Values.pizzaToppings }}
- {{ . | title | quote }}
{{- end }}
{{- end }}
遍历tuple
yaml
sizes: |-
{{- range tuple "small" "medium" "large" }}
- {{ . }}
{{- end }}
遍历数字
这里用到了两个函数,until 和 untilStep
- until 构建一个整数范围。until 5 返回一个列表: [0, 1, 2, 3, 4]
- untilStep 类似until, untilStep 生成一个可计数的整型列表。但允许你定义开始,结束和步长
yaml
kind: Service
apiVersion: v1
metadata:
name: ngrinder-controller-service
namespace: qa-tools
spec:
selector:
app.kubernetes.io/name: ngrinder-controller
ports:
- protocol: TCP
port: 80
targetPort: 80
name: controller
{{- range $_,$p := until 5 }}
- port: {{ $p }}
targetPort: http
protocol: TCP
name: http
{{- end }}
{{- range $_,$e := untilStep 12000 12009 1 }}
- protocol: TCP
port: {{ $e }}
targetPort: {{ $e }}
name: p{{ $e }}
{{- end }}
六、命名模板
在编写命名模板细节之前,先了解一下文件的命名惯例,templates/中的大多数yaml文件被视为包含Kubernetes清单,命名以下划线(_)开始的文件则假定 没有 包含清单内容。这些文件不会渲染为Kubernetes对象定义,但在其他chart模板中都可用。_help.tpl就是保存模版的默认位置。
define操作允许我们在模板文件中创建一个命名模板,在下面例子中在_help.tpl文件中用 define 定义了一个模版 mychart.labels,然后通过 template 引用。
yaml
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" }}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end }}
在这个模版中我们无法用 . 访问任何内容,解决这个问题我们只需要在template调用末尾传入 . 然后就可以通过.Values或.Chart等对象引用数据。但一定要是顶层范围。
由于template是一个行为,不是方法,无法将 template输出内容传给其他方法,数据只是简单地按行插入。
为了处理template输出内容的缩进问题,Helm提供了一个template的可选项,可以将模板内容导入当前管道,然后传递给管道中的其他方法。这就是include,就像这样:
yaml
{{ include "mychart.app" . | indent 2 }}
最后
编写完 chart可以通过命令 helm template . --debug渲染,检查是否有问题。另外写好一个helm chart除了学习helm 语法,更重要的是对kubernetes资源的了解,只有了解了每个资源的功能和参数的作用,才有可能编写出合理的chart,否则调试过程也会非常困难。