chart模板实战

发布时间 2023-09-25 11:49:08作者: 坚强的小蚂蚁

参考:

https://helm.sh/zh/docs/chart_template_guide/getting_started/

https://helm.sh/zh/docs/chart_template_guide/function_list/

一. 入门chart

1.创建一个chart

helm create mychart

查看目录结构

[root@k8s-master helm-test]# tree mychart/
mychart/
├── charts
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml
  • NOTES.txt: chart的"帮助文本"。这会在你的用户执行helm install时展示给他们。
  • deployment.yaml: 创建Kubernetes 工作负载的基本清单
  • service.yaml: 为你的工作负载创建一个 service终端基本清单。
  • _helpers.tpl: 放置可以通过chart复用的模板辅助对象

删掉templates下的所有文件,从头学习

rm -rf mychart/templates/*

2.第一个模板

让我们以创建一个名为 mychart/templates/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-configmap
data:
  myvalue: "Hello World"

有了这个简单的模板,现在有一个可安装的chart了。现在安装如下

[root@k8s-master helm-test]# helm install full-coral ./mychart
NAME: full-coral
LAST DEPLOYED: Tue Sep 19 21:07:10 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

使用下面命令查看实际加载的模板

[root@k8s-master helm-test]# helm get manifest full-coral
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-configmap
data:
  myvalue: "Hello World"

helm get manifest 命令后跟一个发布名称(full-coral)然后打印出了所有已经上传到server的Kubernetes资源。 每个文件以---开头表示YAML文件的开头,然后是自动生成的注释行,表示哪个模板文件生成了这个YAML文档。

卸载这个chart

helm uninstall full-coral

3.添加一个简单的模板调用

修改configmap.yaml如下

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"

模板命令 {{ .Release.Name }} 将发布名称注入了模板。值作为一个 命名空间对象 传给了模板,用点(.)分隔每个命名空间的元素。

Release前面的点表示从作用域最顶层的命名空间开始。这样.Release.Name就可解读为“通顶层命名空间开始查找 Release对象,然后在其中找Name对象”。

Release是一个Helm的内置对象

现在安装资源

[root@k8s-master helm-test]# helm install clunky-serval ./mychart
NAME: clunky-serval
LAST DEPLOYED: Tue Sep 19 21:15:08 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

使用helm get manifest clunky-serval查看生成的完整的YAML

[root@k8s-master mychart]# helm get manifest clunky-serval
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: clunky-serval-configmap
data:
  myvalue: "Hello World"

发现name值已经变为release名字

有时候我们只是想测试,那么可用用--dry-run命令,不会生成资源,只会显示信息到控制台

helm install --debug --dry-run goodly-guppy ./mychart

注意:

使用--dry-run会让你变得更容易测试,但不能保证Kubernetes会接受你生成的模板。 最好不要仅仅因为--dry-run可以正常运行就觉得chart可以安装

二. 内置对象

在上一部分中,我们用{{ .Release.Name }}在模板中插入版本名称。Release是你可以在模板中访问的顶层对象之一。

ReleaseRelease对象描述了版本发布本身。包含了以下对象:

  • Release.Name: release名称
  • Release.Namespace: 版本中包含的命名空间(如果manifest没有覆盖的话)

ValuesValues对象是从values.yaml文件和用户提供的文件传进模板的。默认为空

Chart: Chart.yaml文件内容,里面的数据都可以访问,比如{{.Chart.Name}}--{{.Chart.Version}}会打印mychart-0.1.0

注意

内置的值都是以大写字母开始,这是符合Go的命名惯例。我们自定义的可以用小写

三. values对象

上一节介绍的helm模板内置对象中,我们简单介绍了Values对象,该对象提供了传递值到chart的方法

其内容来自于多个位置

  • chart中的values.yaml文件
  • 如果是子chart,就是父chart中的values.yaml文件
  • 使用-f参数(helm install -f myvals.yaml ./mychart)传递到 helm installhelm upgrade的values文件
  • 使用--set (比如helm install --set foo=bar ./mychart)传递的单个参数

以上列表有明确顺序:默认使用values.yaml,可以被父chart的values.yaml覆盖,继而被用户提供values文件覆盖, 最后会被--set参数覆盖,优先级为values.yaml最低,--set参数最高。

1. 编辑values.yaml

删除values.yaml默认内容,只设置一个参数

favoriteDrink: coffee

然后在模板中使用它

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favoriteDrink }}

看下是怎么渲染的

helm install geared-marsupi ./mychart --dry-run --debug
...
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: geared-marsupi-configmap
data:
  myvalue: "Hello World"
  drink: coffee

使用--set的方式验证下

helm install solid-vulture ./mychart --dry-run --debug --set favoriteDrink=slurm
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: solid-vulture-configmap
data:
  myvalue: "Hello World"
  drink: slurm

由于--set比默认的values.yaml文件优先级更高,模板就生成了drink: slurm

2.values文件中加点结构化内容

修改values.yaml

favorite:
  drink: coffee
  food: pizza

修改configmap.yaml模板

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favorite.drink }}
  food: {{ .Values.favorite.food }}

虽然可以这样构造数据,但还是建议构建更加平坦的浅层树。以后想要给子chart赋值时,会看到如何使用树结构给value命名。

3.删除默认的key

如果需要从默认的value中删除key,可以将key设置为null

例如想把livenessProbe设为空

livenessProbe:
  httpGet:
    path: /user/login
    port: http
  initialDelaySeconds: 120

如果你想替换掉httpGetexec重写活动探针,使用--set livenessProbe.exec.command=[cat,docroot/CHANGELOG.txt], Helm会把默认的key和重写的key合并在一起,从而生成以下YAML:

livenessProbe:
  httpGet:
    path: /user/login
    port: http
  exec:
    command:
    - cat
    - docroot/CHANGELOG.txt
  initialDelaySeconds: 120

因为Kubernetes中不能声明多个活动探针句柄,从而会应用发布会失败。为了解决这个问题,Helm可以指定通过设定null来删除livenessProbe.httpGet

helm install stable/drupal --set image=my-registry/drupal:0.1.0 --set livenessProbe.exec.command=[cat,docroot/CHANGELOG.txt] --set livenessProbe.httpGet=null

四. 模板函数和流水线

我们已经知道了如何将信息传到模板中。 但是传入的信息并不能被修改,如果我们想把传入的信息加个引号,变成大写等怎么做呢

1.quote函数

串属性用引号引起来,然后放到模板中

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ quote .Values.favorite.drink }}
  food: {{ quote .Values.favorite.food }}

在上面的代码片段中,quote .Values.favorite.drink调用了quote函数并传递了一个参数(.Values.favorite.drink)。

2.管道符

我们也可以使用管道符的方法来实现,并且可以使用多个管道符

piVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favorite.drink | quote }}
  food: {{ .Values.favorite.food | upper | quote }}

在这个示例中,并不是调用quote 参数,而是倒置了命令。使用管道符(|)将参数“发送”给函数: .Values.favorite.drink | quote

验证下

helm install guandao-test ./mychart --dry-run
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: guandao-test-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"

3.repeat函数

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favorite.drink | repeat 5 | quote }}

repeat函数会返回给定参数特定的次数,则可以得到以下结果:

...
  drink: "coffeecoffeecoffeecoffeecoffee"
...

4.default函数

这个函数允许你在模板中指定一个默认值,以防这个值被忽略。现在使用它修改上述示例:

drink: {{ .Values.favorite.drink | default "tea" | quote }}

如果运行,会得到values.yaml设置的drink值 coffee:

现在,从values.yaml中移除设置:

favorite:
  #drink: coffee
  food: pizza

现在重新运行 helm install --dry-run --debug fair-worm ./mychart 会生成如下内容:

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fair-worm-configmap
data:
  myvalue: "Hello World"
  drink: "tea"
  food: "PIZZA"

5.运算符也是函数

对于模板来说,运算符(eq, ne, lt, gt, and, or等等) 都是作为函数来实现的。 在管道符中,操作可以按照圆括号分组。

修改values.yaml

favorite:
  drink: coffee
  food: pizza
  
num1: 12
num2: 2

修改configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  eqtest: {{ eq .Values.num1 .Values.num2 }}

测试下

helm install eq-test ./mychart --dry-run
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: eq-test-configmap
data:
  myvalue: "Hello World"
  eqtest: false

五. 流控制

1. If/Else

基本的条件结构看起来像这样:

{{ if PIPELINE }}
  # Do something
{{ else if OTHER PIPELINE }}
  # Do something else
{{ else }}
  # Default case
{{ end }}

如果是以下值时,管道会被设置为 false

  • 布尔false
  • 数字0
  • 空字符串
  • nil (空或null)
  • 空集合(map, slice, tuple, dict, array)

在所有其他条件下,条件都为true。

一个简单的例子

values.yaml

favorite:
  drink: coffee
  food: pizza

configmap.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 }}

验证下

helm install if-test ./mychart --dry-run
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: if-test-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"
  mug: "true"

2.控制空格

看例子

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 }}

验证下

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: telling-chimp-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"
  #空行
  mug: "true"

注意在YAML中有一个空行,为什么?当模板引擎运行时,它 移除了 {{}} 里面的内容,但是留下的空白完全保持原样。

YAML认为空白是有意义的,因此管理空白变得很重要。幸运的是,Helm模板有些工具可以处理此类问题。

首先,模板声明的大括号语法可以通过特殊的字符修改,并通知模板引擎取消空白。{{- (包括添加的横杠和空格)表示向左删除空白, 而 -}}表示右边的空格应该被去掉。 一定注意空格就是换行

其实不管是写{{- }}还是{{ -}}都可以删除当前空行,但如果这俩都写就会影响它上下行的换行导致错误。

要确保-和其他命令之间有一个空格。 {{- 3 }} 表示“删除左边空格并打印3”,而{{-3 }}表示“打印-3”

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 }}

输出

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mytest1-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"
  mug: "true"

如果想实现缩进,可以使用indent方法,比如({{ indent 2 "mug:true" }})会很有用。

3. with操作

作用域可以被改变。with允许你为特定对象设定当前作用域(.)。比如,我们已经在使用.Values.favorite。 修改配置映射中的.的作用域指向.Values.favorite

语法和if类似

{{ with PIPELINE }}
  # restricted scope
{{ end }}

修改configmap.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 }}
  {{- end }}

注意我们从之前的练习中移除了if条件,因为现在不需要了——with后面的块只有在 PIPELINE 的值不为空时才会执行。

注意现在我们可以引用.drink.food了,而不必限定他们。因为with语句设置了.指向.Values.favorite.被重置为{{ end }}之后的上一个作用域。

但是这里有个注意事项,在限定的作用域内,无法使用.访问父作用域的对象。错误示例如下:

  {{- with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  release: {{ .Release.Name }}
  {{- end }}

这样会报错因为Release.Name不在.限定的作用域内。不过,我们可以使用$从父作用域中访问Release.Name对象。当模板开始执行后$会被映射到根作用域,且执行过程中不会更改。

  {{- with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  release: {{ $.Release.Name }}
  {{- end }}

4. range操作符

在Helm的模板语言中,在一个集合中迭代的方式是使用range操作符。

修改values.yaml

favorite:
  drink: coffee
  food: pizza
pizzaToppings:
  - mushrooms
  - cheese
  - peppers
  - onions

现在我们有了一个pizzaToppings列表(模板中称为切片)。修改模板把这个列表打印到配置映射中:

修改configmap.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 }}
  {{- end }}
  toppings: |-
    {{- range .Values.pizzaToppings }}
    - {{ . | title | quote }}
    {{- end }}    

让我们仔细看看toppings:列表。range方法“涵盖”(迭代)pizzaToppings列表。但现在发生了有意思的事情。 就像with设置了.的作用域,range操作符也做了同样的事。每一次循环,.都会设置为当前的披萨配料。 也就是说,第一次.设置成了mushrooms,第二次迭代设置成了cheese,等等。

我们可以直接发送.的值给管道,因此当我们执行{{ . | title | quote }}时,它会发送.title然后发送到quote。 如果执行这个模板,输出是这样的:

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: edgy-dragonfly-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"
  toppings: |-
    - "Mushrooms"
    - "Cheese"
    - "Peppers"
    - "Onions"    

现在,我们已经处理了一些棘手的事情。toppings: |-行是声明的多行字符串。所以这个配料列表实际上不是YAML列表, 是个大字符串。为什么要这样做?因为在配置映射data中的数据是由键值对组成,key和value都是简单的字符串

5. tuple函数

有时能在模板中快速创建列表然后迭代很有用,Helm模板的tuple可以很容易实现该功能。在计算机科学中, 元组表示一个有固定大小的类似列表的集合,但可以是任意数据类型。这大致表达了tuple的用法。

sizes: |-
    {{- range tuple "small" "medium" "large" }}
    - {{ . }}
    {{- end }}  

上述模板会生成以下内容:

sizes: |-
    - small
    - medium
    - large    

六. 变量

我们可以使用变量简化代码,并更好地使用withrange

1.with中变量用法

在之前的例子中,我们看到下面的代码会失败

  {{- with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  release: {{ .Release.Name }}
  {{- end }}

Release.Name 不在with块的限制范围内。解决作用域问题的一种方法是将对象分配给可以不考虑当前作用域而访问的变量。

Helm模板中,变量是对另一个对象的命名引用。遵循$name变量的格式且指定了一个特殊的赋值运算符::=。 我们可以使用针对Release.Name的变量重写上述内容

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  #注意这里前后有2个取消空格,经验证只写一个也行,猜测是因为连着写两行{{ }}
  {{- $relname := .Release.Name -}}  
  {{- with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  release: {{ $relname }}
  {{- end }}

注意在with块开始之前,赋值$relname := .Release.Name。 现在在with块中,$relname变量仍会执行版本名称。

验证下

helm install mytest1 -f values.yaml . --dry-run 
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mytest1-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"
  release: mytest1

2.range中的变量用法

变量在range循环中特别有用。可以用于类似列表的对象,以捕获索引和值:

values.yaml

favorite:
  drink: coffee
  food: pizza
pizzaToppings:
  - mushrooms
  - cheese
  - peppers
  - onions

修改configmap.yaml如下

  toppings: |-
    {{- range $index, $topping := .Values.pizzaToppings }}
      {{ $index }}: {{ $topping }}
    {{- end }}    

注意先是range,然后是变量,然后是赋值运算符,然后是列表。会将整型索引(从0开始)赋值给$index并将值赋值给$topping。 执行会生成:

  toppings: |-
      0: mushrooms
      1: cheese
      2: peppers
      3: onions

对于数据结构有key和value的情况,可以使用range获取key和value。比如,可以通过.Values.favorite进行循环:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}

第一次迭代,$key会是drink$val会是coffee,第二次迭代$key会是food$val会是pizza。 运行之后会生成:

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: eager-rabbit-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"

变量一般不是"全局的"。作用域是其声明所在的块。上面我们在模板的顶层赋值了$relname。变量的作用域会是整个模板。 但在最后一个例子中$key$val作用域会在{{ range... }}{{ end }}块内。

但有个变量一直是全局的 $ ,这个变量一直是指向根的上下文

{{- with  .Values.favorite }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: "{{ $.Release.Name }}"
data:
  myvalue: "Hello World"
  drink: {{ .drink }}
  eat: {{ .food }}
{{- end }}

验证下

helm install mytest2 -f values.yaml . --dry-run
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: "mytest2"
data:
  myvalue: "Hello World"
  drink: coffee
  eat: pizza

七. 命名模板

用法像是一个自定义函数

模板名称是全局的。如果您想声明两个相同名称的模板,哪个最后加载就使用哪个。 因为在子chart中的模板和顶层模板一起编译,命名时要注意 chart特定名称

一个常见的命名惯例是用chart名称作为模板前缀:{{ define "mychart.labels" }}。使用特定chart名称作为前缀可以避免可能因为 两个不同chart使用了相同名称的模板而引起的冲突

在编写模板细节之前,文件的命名惯例需要注意:

  • templates/中的大多数文件被视为包含Kubernetes清单
  • NOTES.txt是个例外
  • 命名以下划线(_)开始的文件则假定 没有 包含清单内容。这些文件不会渲染为Kubernetes对象定义,但在其他chart模板中都可用。

这些文件用来存储局部和辅助对象,实际上当我们第一次创建mychart时,会看到一个名为_helpers.tpl的文件,这个文件是模板局部的默认位置。

1.用definetemplate声明和使用模板

define操作允许我们在模板文件中创建一个命名模板

比如我们可以定义一个模板封装Kubernetes的标签:

{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}

现在我们将模板嵌入到了已有的配置映射中,然后使用template包含进来:

{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}
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 }}

当模板引擎读取该文件时,它会存储mychart.labels的引用直到template "mychart.labels"被调用。 然后会按行渲染模板,因此结果类似这样:

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: running-panda-configmap
  labels:
    generator: helm
    date: 2016-11-02
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"

注意:define不会有输出,除非像本示例一样用模板调用它。

现在我们修改下定义模板的位置,在templates目录下创建_helpers.tpl,内容如下

{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}

然后在configmap中直接调用此模板

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 }}

结果发现和之前输出一样,也就是说尽管这个定义是在_helpers.tpl中,但它在其他模板中可以直接使用

2.设置模板范围

在上面定义的模板中,我们没有使用任何对象,仅仅使用了一些内置方法。那么里面可以包含chart名称和版本号么?

验证下,修改_helpers.tpl如下

{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
    chart: {{ .Chart.Name }}
    version: {{ .Chart.Version }}
{{- end }}

结果得到下面错误

$ helm install --dry-run moldy-jaguar ./mychart
Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: [unknown object type "nil" in ConfigMap.metadata.labels.chart, unknown object type "nil" in ConfigMap.metadata.labels.version]

要查看渲染了什么,可以用--disable-openapi-validation参数重新执行:

helm install --dry-run --disable-openapi-validation moldy-jaguar ./mychart
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: moldy-jaguar-configmap
  labels:
    generator: helm
    date: 2023-09-22
    chart: 
    version: 
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"

发现{{ .Chart.Name }}和{{ .Chart.Version }}居然是空值,为什么呢?

当一个(使用define创建的)命名模板被渲染时,会接收被template调用传入的内容。 在我们的示例中,包含模板如下:

{{- template "mychart.labels" }}

此时是没有内容传入的,所以模板中没法用 "." 访问任何内容,怎么解决呢?原先模板中后面加个 "." 就行

{{- template "mychart.labels" . }}

注意这个在template调用末尾传入的.,我们可以简单传入.Values.Values.favorite或其他需要的范围。但一定要是顶层范围。

再验证下

helm install mytest1 . --dry-run
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mytest1-configmap
  labels:
    generator: helm
    date: 2023-09-22
    chart: mychart
    version: 0.1.0
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"

3.include方法

修改_helpers.tpl 为如下

{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}"
{{- end -}}

然后我想把这个插入到模板的labels:部分和data:部分

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
    {{ template "mychart.app" . }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}
  {{ template "mychart.app" . }}

此时渲染会报错,看下渲染内容

helm install --dry-run --disable-openapi-validation measly-whippet ./mychart
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: measly-whippet-configmap
  labels:
    app_name: mychart
app_version: "0.1.0"
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"
  app_name: mychart
app_version: "0.1.0"

注意两处的app_version缩进都不对,为啥?因为被替换的模板中文本是左对齐的。由于template是一个行为,不是方法,无法将 template调用的输出传给其他方法,数据只是简单地按行插入

为了解决这个问题,可以使用include方法将模板内容导入当前管道,然后使用indent方法来缩进

修改configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
{{ include "mychart.app" . | indent 4 }}  #注意这里紧挨着左边写的
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}
{{ include "mychart.app" . | indent 2 }}  ##注意这里紧挨着左边写的

验证下

helm install mytest1 ./mychart --dry-run
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mytest1-configmap
  labels:
    app_name: mychart
    app_version: "0.1.0"
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"
  app_name: mychart
  app_version: "0.1.0"

4._helper.tpl中写多个自定义模板

像定义普通的模板一样,如果想在一个yaml文件中写入多个模板类型,只需要中间用"---"分割就行

修改_helper.tpl

{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}"
{{- end -}}

---
{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
    chart: {{ .Chart.Name }}
    version: {{ .Chart.Version }}
{{- end }}

修改configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
{{ include "mychart.app" . | indent 4 }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}
{{ include "mychart.app" . | indent 2 }}

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap1
  {{- template "mychart.labels" . }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}

验证下

[root@k8s-master helm-test]# helm install mytest1 ./mychart --dry-run
NAME: mytest1
LAST DEPLOYED: Fri Sep 22 23:29:43 2023
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mytest1-configmap
  labels:
    app_name: mychart
    app_version: "0.1.0"
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"
  app_name: mychart
  app_version: "0.1.0"
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mytest1-configmap1
  labels:
    generator: helm
    date: 2023-09-22
    chart: mychart
    version: 0.1.0
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"

注意,输入"---"时不要用shift,就是普通的破折号

八. 使用Files对象访问文件

1.基本例子

mychart目录中创建两个文件

config1.toml

message=hello config1

config2.toml

message=hello config2

修改configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  {{- $files := .Files }}
  {{- range tuple "config1.toml" "config2.toml" }}
  {{ . }}: |-
        {{ $files.Get . }}
  {{- end }}

我们创建了一个$files变量来引用.Files对象。我们也使用了tuple方法创建了一个可遍历的文件列表。 然后我们打印每个文件的名字({{ . }}: |-),然后通过{{ $files.Get . }}打印文件内容

验证下

root@k8s-master mychart]# helm install mytest1 ./mychart --dry-run
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mytest1-configmap
data:
  config1.toml: |-
        message=hello config1

  config2.toml: |-
        message=hello config2

2.Path helpers

使用文件时,对文件路径本身执行一些标准操作会很有用。为了实现这些,Helm从Go的 path包中导入了一些功能。 都使用了与Go包中一样的名称就可以访问。但是第一个字符使用了小写,比如Base变成了base等等。

导入的功能包括:

  • Base
  • Dir
  • Ext
  • IsAbs
  • Clean

了解就行,感觉用处不大

3.configmap和secrets

把文件内容放入配置映射和密钥是很普遍的功能,为了运行时挂载到你的pod上。为了实现它,我们提供了一些基于Files类型的实用方法。

为了进一步组织文件,这些方法结合Glob方法使用时尤其有用。

在mychart目录下新建foo和bar目录, foo目录中新建foo-config.txt文件, bar目录中新建bar.conf文件

foo.conf

hello foo

bar.conf

hello bar

修改templates/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: conf
data:
{{ (.Files.Glob "foo/*.conf").AsConfig | indent 2 }}
---
apiVersion: v1
kind: Secret
metadata:
  name: very-secret
type: Opaque
data:
{{ (.Files.Glob "bar/*.conf").AsSecrets | indent 2 }}

验证下

helm install test1 ./mychart --dry-run
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: Secret
metadata:
  name: very-secret
type: Opaque
data:
  bar.conf: aGVsbG8gYmFyCg==
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: conf
data:
  foo.conf: |
    hello foo

4.Encoding

您可以导入一个文件并使用模板的base-64方式对其进行编码来保证成功传输:

修改configmap.yaml, 使用之前创建的config1.toml,把内容转为base64的格式

apiVersion: v1
kind: Secret
metadata:
  name: {{ .Release.Name }}-secret
type: Opaque
data:
  token: |-
        {{ .Files.Get "config1.toml" | b64enc }}

输出

MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mytest1-secret
type: Opaque
data:
  token: |-
        bWVzc2FnZT1oZWxsbyBjb25maWcxCg==

5.Lines

有时需要访问模板中的文件的每一行。我们提供了一个方便的Lines方法。

你可以使用range方法遍历Lines

新建foo/foo.txt

apple
banana
grape
orange

修改configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: conf
data:
  some-file.txt: {{ range .Files.Lines "foo/foo.txt" }}
    {{ . }}{{ end }}

验证下

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: conf
data:
  some-file.txt: 
    apple
    banana
    grape
    orange