helm charts 模板编程

Posted 看,未来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了helm charts 模板编程相关的知识,希望对你有一定的参考价值。

文章目录

模板函数与管道

模板函数

比如我们需要从.Values中读取的值变成字符串的时候就可以通过调用quote模板函数来实现:(templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue: "Hello World"
  k8s:  quote .Values.course.k8s 
  python:  .Values.course.python 

模板函数遵循调用的语法为:functionName arg1 arg2…。在上面的模板文件中,quote .Values.course.k8s调用quote函数并将后面的值作为一个参数传递给它。最终被渲染为:

$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '39405'
......
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: masked-saola-configmap
data:
  myvalue: "Hello World"
  k8s: "devops"
  python: django

我们可以看到.Values.course.k8s被渲染成了字符串devops。如果需要双引号",则需要添加\\来进行转义,而squote函数的用途则是用双引号将字符串括起来,而不会对内容进行转义。


辅助模板

有时你想在图表中创建一些可重复使用的部分,无论它们是块还是模板部分。通常,将它们保存在自己的文件中会更干净。

在templates/目录中,任何以下划线 ( _) 开头的文件都不会输出 Kubernetes 清单文件。所以按照惯例,辅助模板和部分被放置在一个_helpers.tpl文件中。


管道

比如我们用管道来重写上面的 ConfigMap 模板:(templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue: "Hello World"
  k8s:  .Values.course.k8s | quote 
  python:  .Values.course.python 

使用管道我们可以将几个功能顺序的连接在一起,比如我们希望上面的 ConfigMap 模板中的 k8s 的 value 值被渲染后是大写的字符串,则我们就可以使用管道来修改:(templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue: "Hello World"
  k8s:  .Values.course.k8s | upper | quote 
  python:  .Values.course.python 

然后我们用debug模式来查看下上面的模板最终会被渲染成什么样子:

$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '46651'
......
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: maudlin-labradoodle-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: django

我们可以看到之前我们的 devops 已经被渲染成了 “DEVOPS” 了,要注意的是使用管道操作的时候,前面的操作结果会作为参数传递给后面的模板函数,比如我们这里希望将上面模板中的 python 的值渲染为重复出现3次的字符串,则我们就可以使用到 repeat 函数,不过该函数需要传入一个参数repeat COUNT STRING表示重复的次数:(templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue: "Hello World"
  k8s:  .Values.course.k8s | upper | quote 
  python:  .Values.course.python | quote | repeat 3 

该repeat函数会将给定的字符串重复3次返回,所以我们将得到这个输出:

helm install --dry-run --debug .
[debug] Created tunnel using local port: '39712'

......

Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 7: did not find expected key

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: piquant-butterfly-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "django""django""django"

我们可以看到上面的输出中 python 对应的值变成了3个相同的字符串,这显然是不符合我们预期的,我们的预期是形成一个字符串,而现在是3个字符串了,而且上面还有错误信息,根据管道处理的顺序,我们将quote函数放到repeat函数后面去是不是就可以解决这个问题了:(templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue: "Hello World"
  k8s:  .Values.course.k8s | upper | quote 
  python:  .Values.course.python | repeat 3 | quote 

现在是不是就是先重复3次.Values.course.python的值,然后调用quote函数:

helm install --dry-run --debug .
[debug] Created tunnel using local port: '33837'

......

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: braided-manatee-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"

现在是不是就正常了,也得到了我们的预期结果,所以我们在使用管道操作的时候一定要注意是按照从前到后一步一步顺序处理的。


default 函数

另外一个我们会经常使用的一个函数是default 函数:default DEFAULT_VALUE GIVEN_VALUE。该函数允许我们在模板内部指定默认值,以防止该值被忽略掉了。比如我们来修改上面的 ConfigMap 的模板:(templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue:  .Values.hello | default  "Hello World" | quote 
  k8s:  .Values.course.k8s | upper | quote 
  python:  .Values.course.python | repeat 5 | quote 

由于我们的values.yaml文件中只定义了 course 结构的信息,并没有定义 hello 的值,所以如果没有设置默认值的话是得不到 .Values.hello 的值的,这里我们为该值定义了一个默认值:Hello World,所以现在如果在values.yaml文件中没有定义这个值,则我们也可以得到默认值:

$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '42670'

......

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: orbiting-hog-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjangodjangodjango"

lookup 函数

该 lookup 函数可用于查找正在运行的集群中的资源。查找函数的概要是

lookup apiVersion, kind, namespace, name -> resource or resource list
parameterapiVersionkindnamespacename
typestringstringstringstring

name和都是namespace可选的,可以作为空字符串 ( “”) 传递。

以下参数组合是可能的:

行为查找功能
Kubectl Get Pod Mypod -N MynamespaceLookup “v1” “Pod” “mynamespace” “mypod”
Kubectl Get Pods -N MynamespaceLookup “v1” “Pod” “mynamespace” “”
Kubectl Get Pods —all-NamespacesLookup “v1” “Pod” “” “”
Kubectl Get Namespace MynamespaceLookup “v1” “Namespace” “” “mynamespace”
Kubectl Get NamespacesLookup “v1” “Namespace” “” “”

当lookup返回一个对象时,它会返回一个字典。可以进一步导航此字典以提取特定值。

以下示例将返回mynamespace对象的注释:

(lookup "v1" "Namespace" "" "mynamespace").metadata.annotations

返回对象列表时lookup,可以通过以下items字段访问对象列表:

 range $index, $service := (lookup "v1" "Service" "mynamespace" "").items 
    /* do something with each service */
 end 

当没有找到对象时,返回一个空值。这可用于检查对象是否存在。

该lookup函数使用 Helm 现有的 Kubernetes 连接配置来查询 Kubernetes。如果与调用 API 服务器交互时返回任何错误(例如由于缺少访问资源的权限),则 helm 的模板处理将失败。


控制流程:判断、循环

模板函数和管道是通过转换信息并将其插入到YAML文件中的强大方法。但有时候需要添加一些比插入字符串更复杂一些的模板逻辑。这就需要使用到模板语言中提供的控制结构了。

控制流程为我们提供了控制模板生成流程的一种能力,Helm 的模板语言提供了以下几种流程控制:

if/else 条件块
with 指定范围
range 循环块

除此之外,它还提供了一些声明和使用命名模板段的操作:

define在模板中声明一个新的命名模板
template导入一个命名模板
block声明了一种特殊的可填写的模板区域

if/else 条件

if/else块是用于在模板中有条件地包含文本块的方法,条件块的基本结构如下:

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

当然要使用条件块就得判断条件是否为真,如果值为下面的几种情况,则管道的结果为 false:

一个布尔类型的假
一个数字零
一个空的字符串
一个nil(空或null)
一个空的集合(map、slice、tuple、dict、array)

除了上面的这些情况外,其他所有条件都为真。

同样还是以上面的 ConfigMap 模板文件为例,添加一个简单的条件判断,如果 python 被设置为 django,则添加一个web: true:(tempaltes/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue:  .Values.hello | default  "Hello World" | quote 
  k8s:  .Values.course.k8s | upper | quote 
  python:  .Values.course.python | repeat 3 | quote 
   if eq .Values.course.python "django" web: true end 

在上面的模板文件中我们增加了一个条件语句判断 if eq .Values.course.python “django” web: true end ,其中运算符eq是判断是否相等的操作,除此之外,还有ne、lt、gt、and、or等运算符都是 Helm 模板已经实现了的,直接使用即可。这里我们 .Values.course.python 的值在values.yaml文件中默认被设置为了django,所以正常来说下面的条件语句判断为真,所以模板文件最终被渲染后会有web: true这样的的一个条目:

$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '40143'

......

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fallacious-prawn-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjangodjangodjango"
  web: true

可以看到上面模板被渲染后出现了web: true的条目,如果我们在安装的时候覆盖下 python 的值呢,比如我们改成 ai:

helm install --dry-run --debug --set course.python=ai .
[debug] Created tunnel using local port: '42802'

......
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dull-mite-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "aiaiai"

根据我们模板文件中的定义,如果 .Values.course.python 的值为django的话就会新增web: true这样的一个条目,但是现在我们是不是通过参数–set将值设置为了 ai,所以这里条件判断为假,正常来说就不应该出现这个条目了,上面我们通过 debug 模式查看最终被渲染的值也没有出现这个条目,证明条件判断是正确的。


空格控制

上面我们的条件判断语句是在一整行中的,如果平时经常写代码的同学可能非常不习惯了,我们一般会将其格式化为更容易阅读的形式,比如:

 if eq .Values.course.python "django" 
web: true
 end 

这样的话看上去比之前要清晰很多了,但是我们通过模板引擎来渲染一下,会得到如下结果:

$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '44537'

......

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: bald-narwhal-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"

  web: true

我们可以看到渲染出来会有多余的空行,这是因为当模板引擎运行时,它将一些值渲染过后,之前的指令被删除,但它之前所占的位置完全按原样保留剩余的空白了,所以就出现了多余的空行。YAML文件中的空格是非常严格的,所以对于空格的管理非常重要,一不小心就会导致你的YAML文件格式错误。

我们可以通过使用在模板标识后面添加破折号和空格-来表示将空白左移,而在前面添加一个空格和破折号-表示应该删除右边的空格,另外需要注意的是换行符也是空格!

使用这个语法,我们来修改我们上面的模板文件去掉多余的空格:(templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue:  .Values.hello | default  "Hello World" | quote 
  k8s:  .Values.course.k8s | upper | quote 
  python:  .Values.course.python | repeat 3 | quote 
  - if eq .Values.course.python "django" 
  web: true
  - end 

现在我们来查看上面模板渲染过后的样子:

$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '34702'

......

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mangy-olm-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  web: true

现在是不是没有多余的空格了,另外我们需要谨慎使用-,比如上面模板文件中:

python:  .Values.course.python | repeat 3 | quote 
- if eq .Values.course.python "django" -
web: true
- end 

如果我们在if条件后面增加-,这会渲染成:

python: "djangodjangodjango"web: true

因为-它删除了双方的换行符,显然这是不正确的。


使用 with 修改范围

接下来我们来看下with关键词的使用,它用来控制变量作用域。还记得之前我们的 .Release.xxx 或者 .Values.xxx 吗?其中的.就是表示对当前范围的引用,.Values就是告诉模板在当前范围中查找Values对象的值。而with语句就可以来控制变量的作用域范围,其语法和一个简单的if语句比较类似:

 with PIPELINE 
  #  restricted scope
 end 

with语句可以允许将当前范围.设置为特定的对象,比如我们前面一直使用的.Values.course,我们可以使用with来将.范围指向.Values.course:(templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue:  .Values.hello | default  "Hello World" | quote 
  - with .Values.course 
  k8s:  .k8s | upper | quote 
  python:  .python | repeat 3 | quote 
  - if eq .python "django" 
  web: true
  - end 
  - end 

可以看到上面我们增加了一个- with .Values.course xxx- end 的一个块,这样的话我们就可以在当前的块里面直接引用.python.k8s了,而不需要进行限定了,这是因为该with声明将.指向了.Values.course,在- end .就会复原其之前的作用范围了,我们可以使用模板引擎来渲染上面的模板查看是否符合预期结果。

不过需要注意的是在with声明的范围内,此时将无法从父范围访问到其他对象了,比如下面的模板渲染的时候将会报错,因为显然.Release根本就不在当前的.范围内,当然如果我们最后两行交换下位置就正常了,因为- end 之后范围就被重置了:

- with .Values.course 
k8s:  .k8s | upper | quote 
python:  .python | repeat 3 | quote 
release:  .Release.Name 
- end 

range 循环

如果大家对编程语言熟悉的话,几乎所有的编程语言都支持类似于for、foreach或者类似功能的循环机制,在 Helm 模板语言中,是使用range关键字来进行循环操作。

我们在values.yaml文件中添加上一个课程列表:

course:
  k8s: devops
  python: django
courselist:
- k8s
- python
- search
- golang

现在我们有一个课程列表,修改 ConfigMap 模板文件来循环打印出该列表:

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  myvalue:  .Values.hello | default  "Hello World" | quote 
  - with .Values.course 
  k8s:  .k8s | upper | quote 
  python:  .python | repeat 3 | quote 
  - if eq .python "django" 
  web: true
  - end 
  - end 
  courselist:
  - range .Values.courselist 
  -  . | title | quote 
  - end 

可以看到最下面我们使用了一个range函数,该函数将会遍历 .Values.courselist 列表,循环内部我们使用的是一个.,这是因为当前的作用域就在当前循环内,这个.从列表的第一个元素一直遍历到最后一个元素,然后在遍历过程中使用了title和quote这两个函数,前面这个函数是将字符串首字母变成大写,后面就是加上双引号变成字符串,所以按照上面这个模板被渲染过后的结果为:

$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '34626'

......

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dining-terrier-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  web: true
  courselist:
  - "K8s"
  - "Python"
  - "Search"
  - "Golang"

我们可以看到courselist按照我们的要求循环出来了。除了 list 或者 tuple,range 还可以用于遍历具有键和值的集合(如map 或 dict),这个就需要用到变量的概念了。


变量

前面我们已经学习了函数、管理以及控制流程的使用方法,我们知道编程语言中还有一个很重要的概念叫:变量,在 Helm 模板中,使用变量的场合不是特别多,但是在合适的时候使用变量可以很好的解决我们的问题。如下面的模板:

- with .Values.course 
k8s:  .k8s | upper | quote 
python:  .python | repeat 3 | quote 
release:  .Release.Name 
- end 

我们在with语句块内添加了一个.Release.Name对象,但这个模板是错误的,编译的时候会失败,这是因为.Release.Name不在该with语句块限制的作用范围之内,我们可以将该对象赋值给一个变量可以来解决这个问题:

apiVersion: v1
kind: ConfigMap
metadata:
  name:  .Release.Name -configmap
data:
  - $releaseName := .Release.Name -
  - with .Values.course 
  k8s:  .k8s | upper | quote 
  python:  .python | repeat 3 | quote 
  release:  $releaseName 
  helm模板文件chart编写语法详解

④使用Helm开发一个Chart模板

helm chart预定义值

详解如何自定义开发构建一个Helm Chart包

helm 构建 chart

helm-chart7,调试与hook