Helm最核心的就是模板,即模板化的K8S manifests文件。
它本质上就是一个Go的template模板。Helm在Go template模板的基础上,还会增加很多东西。如一些自定义的元数据信息、扩展的库以及一些类似于编程形式的工作流,例如条件语句、管道等等。这些东西都会使得我们的模板变得更加丰富。

创建chart中的自定义文件

我们自定义yaml文件

cd templates/
rm -rf *
kubectl create deployment --image=nginx nginx01 --dry-run -o yaml >deployment.yaml
kubectl expose deployment nginx01 --port=80 --target-port=80 --dry-run -o yaml >service.yaml

查看生成的yaml文件

cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx01
  name: nginx01
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx01
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx01
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

查看生成的service.yaml

cat service.yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  name: nginx01
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
status:
  loadBalancer: {}

实际上,这已经是一个可安装的Chart包了,通过 helm install命令来进行安装:

helm install web01 mychart

这样部署,其实与直接apply没什么两样。
然后使用如下命令可以看到实际的模板被渲染过后的资源文件:

helm get manifest web01

定义模板

可以看到,这与刚开始写的内容是一样的,包括名字、镜像等,我们希望能在一个地方统一定义这些会经常变换的字段,这就需要用到Chart的模板了,这样变的更通用
我们将其会变化的部分定义为变量:
编辑deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    chart: {{ .Chart.Name }}
    app: {{ .Release.Name }}
  name: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicas}}
  selector:
    matchLabels:
      app: {{ .Values.label }}
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: {{ .Values.label }}
    spec:
      containers:
      - image: {{ .Values.image }}:{{ .Values.imageTag }}
        name: {{ .Release.Name }}
        resources: {}
status: {}

编辑service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    chart: {{ .Chart.Name }}
  name: {{ .Release.Name }}
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: {{ .Values.label }}

编辑传入变量的values.yaml

[root@master templates]# cat ../values.yaml
replicas: 3
image: nginx
imageTag: 1.17
label: nginx

调试

我们编辑完成后可以用dry-run试运行下,看看有无错误:

 helm install web --dry-run ../../mychart

内置对象

上面我们用了很多变量,其中Release.Name、Chart.Name都为内置变量,
下面是一些常用的内置对象:
Release.Name release 名称
Release.Time release 的时间
Release.Namespace release 的 namespace(如果清单未覆盖)
Release.Service release 服务的名称
Release.Revision 此 release 的修订版本号,从1开始累加
Release.IsUpgrade 如果当前操作是升级或回滚,则将其设置为 true。
Release.IsInstall 如果当前操作是安装,则设置为 true。
此时我们已经可以执行安装动作了

 helm install web  ../../mychart

检测:

[root@master templates]# kubectl get pod
NAME                                      READY   STATUS             RESTARTS   AGE
web-785d8679f8-fncjd                      1/1     Running            0          4s
web-785d8679f8-lc28h                      1/1     Running            0          4s
web-785d8679f8-vkkq5                      1/1     Running            0          4s

更新

此时我们如果想更换配置就比较方面了,比如我们将nginx的版本换成1.16

[root@master demo]# cat mychart/values.yaml
replicas: 3
image: nginx
imageTag: 1.16
label: nginx

更新:

helm upgrade web01 mychart

此时我们就可以curl 看到nginx版本已经变化了

kubectl get pod -o wide
curl 172.17.9.3 -I
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Tue, 04 Feb 2020 12:45:56 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 13 Aug 2019 10:05:00 GMT
Connection: keep-alive
ETag: "5d528b4c-264"
Accept-Ranges: bytes

Values

Values对象是为Chart模板提供值,这个对象的值有4个来源:

  • chart 包中的 values.yaml 文件
  • 父 chart 包的 values.yaml 文件
  • 通过 helm install 或者 helm upgrade 的 -f或者 --values参数传入的自定义的 yaml 文件
  • 通过 --set 参数传入的值
    chart 的 values.yaml 提供的值可以被用户提供的 values 文件覆盖,而该文件同样可以被 --set提供的参数所覆盖。
    例如我们要新建一个应用,副本数只要1个:
helm install web02 --set replicas=1 mychart

Chart的管道与函数

模块实就是将值传给模板引擎进行渲染,模板引擎还支持对拿到数据进行二次处理。
例如从.Values中读取的值变成字符串(就是给值加个双引号“”),可以使用quote函数实现:
例如我们给标签(labels)加个双引号:
我们修改deployment.yaml文件

[root@master mychart]# cat templates/deployment.yaml
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: {{ quote .Values.label }}

试运行查看结果

helm install web03 --dry-run ../mychart
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: "nginx"

这里可以发现标签 app: “nginx” 加上了一个双引号“”

default函数:

另外还会经常使用一个default函数,该函数允许在模板中指定默认值,以防止该值被忽略掉。
例如我们新加一个值:test: demotest
我们修改deployment.yaml文件

cat templates/deployment.yaml
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: {{ quote .Values.label }}
        test: {{ default "demotest" .Values.test }}

试运行查看结果:

 helm install web03 --dry-run ../mychart
   template:
    metadata:
      creationTimestamp: null
      labels:
        app: "nginx"
        test: demotest

这里可以看到,我们定义的test: demotest已经生效。
我们不在values.yaml中定义test的值,默认就会是demotest,如果定义就会按定义的值来
例如我们把值定义为123456:

[root@master mychart]# cat values.yaml
replicas: 3
image: nginx
imageTag: 1.16
label: nginx
test: 123456

试运行查看结果:

 helm install web03 --dry-run ../mychart
   template:
    metadata:
      creationTimestamp: null
      labels:
        app: "nginx"
        test: 123456

其他函数:

缩进:{{ .Values.resources | indent 12 }} 这里表示改行缩进12个空格
大写:{{ upper .Values.resources }}
首字母大写:{{ title .Values.resources }}

模板函数调用语法为:functionName arg1 arg2…

流程控制

流程控制是为模板提供了一种能力,满足更复杂的数据逻辑处理。
Helm模板语言提供以下流程控制语句:

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

if

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)
    除了上面的这些情况外,其他所有条件都为 真。
    例如我们判断test的值来赋予一个新的值,修改deployment.yaml;:
template:
    metadata:
      creationTimestamp: null
      labels:
        app: {{ quote .Values.label }}
        #test: {{ default "demotest" .Values.test }}
        {{- if  eq .Values.test "devops"}}
        devops: k8s
        {{- else }}
        devops: docker
        {{- end }}

我们修改values.yaml中的值

[root@master mychart]# cat values.yaml
replicas: 3
image: nginx
imageTag: 1.16
label: nginx
test: devops

试运行查看结果:

[root@master mychart]# helm install web03 --dry-run ../mychart
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: "nginx"
        #test: devops
        devops: k8s

这里可以看到,根据values的值,我们可以判断赋予devops这个字段的值。
其中运算符 eq是判断是否相等的操作,除此之外,还有 ne、 lt、 gt、 and、 or等运算符。
其中渲染出来会有多余的空行,这是因为当模板引擎运行时,会将控制指令删除,所有之前占的位置也就空白了,所以我们需要使用{{- if …}} 的方式消除此空行。
如果使用-}}需谨慎,因为会删除当前换行符。
实战举例:
我们配置一个资源限制开关,当在values.yaml中存在resources时打开资源限制开关,resources为{}时,关掉资源限制。
编辑deployment.yaml资源限制模块

[root@master mychart]# cat templates/deployment.yaml
      containers:
      - image: {{ .Values.image }}:{{ .Values.imageTag }}
        name: {{ .Release.Name }}
        {{- if .Values.resources }}
        resources:
           limits:
             cpu: {{ .Values.resources.limits.cpu }}
             memory: {{ .Values.resources.limits.cpu }}
        {{- else }}
        resources: {}
        {{- end }}

编辑values.yaml,填入相对应的值:

[root@master mychart]# cat values.yaml
replicas: 3
image: nginx
imageTag: 1.16
label: nginx
test: devops
resources:
   limits:
     cpu: 100m
     memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi 

试运行查看结果:

[root@master mychart]# helm install web03 --dry-run ../mychart
      containers:
      - image: nginx:1.16
        name: web03
        resources:
           limits:
             cpu: 100m
             memory: 100m

如果此时我们将values.yaml中的resources内容注释,改为resources{},此时deployment.yaml中,就会没有资源限制相关内容。
现在我们做一个更有意义的事情,就是根据判断来是否判断创建ingress
编辑ingress.yaml,并写入判断

[root@master mychart]# cat templates/ingress.yaml
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: {{ .Release.Name }}-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        backend:
          serviceName: {{ .Release.Name }}
          servicePort: 80
{{- end }}

编辑values.yaml,填入相对应的值:

[root@master mychart]# cat values.yaml
replicas: 3
image: nginx
imageTag: 1.16
label: nginx
test: devops
resources:
   limits:
     cpu: 100m
     memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

ingress:
  enabled: true
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths: []
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

这里我们的enabled值为true,所以我们执行后会创建对应的ingress

helm install web03  ../mychart
[root@master mychart]# kubectl get ingress
NAME            HOSTS   ADDRESS   PORTS   AGE
web03-ingress   *                 80      3m21s

将enabled值改为false,将会不创建ingress

with

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

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

with语句可以允许将当前范围 .设置为特定的对象,比如我们前面一直使用的 .Values.label,我们可以使用 with来将 .范围指向 .Values.label:
举例:
我们添加一个标签,我们在在values.yaml中添加一段:

nodeSelector:
  team: changsha
   gpu: ok

1、首先我们用if判断来解决。
我们要在deploy.yaml中指定位置containers上面加入if判断内容:

spec:
  {{- if .Values.nodeSelector }}
  nodeSelector:
    team: {{ .Values.nodeSelector.team }}
    gpu: {{ .Values.nodeSelector.gpu }}
  {{- end }}
  containers:

试运行:

 helm install web --dry-run ../mychart

渲染的yaml文件会加入指定的内容:

    spec:
      nodeSelector:
        team: changsha
        gpu: ok
      containers:

2、上面方式有点死板,我们用with方法来解决。
我们在deploy.yaml中指定位置containers上面修改为with相关内容:

   spec:
      {{- with .Values.nodeSelector }}
      nodeSelector:
        team: {{ .team }}
        gpu: {{ .gpu }}
      {{- end }}
      containers:

试运行:

 helm install web --dry-run ../mychart

渲染的yaml文件也会加入指定的内容:

    spec:
      nodeSelector:
        team: changsha
        gpu: ok
      containers:

其中的team: {{ .team }}中的 .表示一个一个map集合,可以理解为python中的一个字典,.team表示取这个map中team的值,既是changsha。
3、上面方式还不够简化,要指定key值,我们可以用toYaml函数来处理
我们要在deploy.yaml中指定位置containers上面继续优化:

spec:
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      containers:

试运行:

 helm install web --dry-run ../mychart

此时我们得到的结果和上面一样。
其中 with是一个循环构造。使用.Values.nodeSelector中的值:将其转换为Yaml。
toYaml之后的点是循环中.Values.nodeSelector的当前值 。
使用 nindent 8 是因为用toYaml函数得到的值都是顶格,我们需要缩进8个字符,保持语法正确,并且每缩进一行都需要换行(indent函数不换行)

range

在 Helm 模板语言中,使用 range关键字来进行循环操作。
我们在 values.yaml文件中添加上一个变量列表:

xiaomei:
  - 1
  - 2
  - 3

在templates目录下面新建一个configmap.yaml文件

[root@master mychart]# cat templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}
data:
  test: |
  {{- range .Values.xiaomei }}
    {{ . }}
  {{- end }}

循环内部我们使用的是一个 .,这是因为当前的作用域就在当前循环内,这个 .引用的当前读取的元素
试运行:

 helm install web --dry-run ../mychart

渲染的yaml文件也会加入指定的内容:

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: web
data:
  test: |
    1
    2
    3

变量

变量在模板中,使用变量的场合不多,但它能来简化代码,并更好地利用with和range。因为with和range中不能直接使用.Values.Name

1、获取列表键值

# cat ../values.yaml
env:
  NAME: "gateway"
  JAVA_OPTS: "-Xmx1G"
  
# cat deployment.yaml 
...
		env:
		{{- range $k, $v := .Values.env }}
           - name: {{ $k }}
             value: {{ $v | quote }}
        {{- end }}

试运行:

 helm install web --dry-run ../mychart

结果如下:

 env:
       - name: JAVA_OPTS
         value: "-Xmx1G"
       - name: NAME
         value: "gateway"

上面在 range循环中使用 $key和 $value两个变量来接收后面列表循环的键和值。

2、赋值内置变量

{{- $releaseName := .Release.Name -}}
      {{- with .Values.label }}
        project: {{ .project }}
        app: {{ .app }}
        release: {{ $releaseName }}
        # 或者可以使用$符号,引入全局命名空间
        release: {{ $.Release.Name }}
      {{- end }}

可以看到在 with语句上面增加了一句 {{-$releaseName:=.Release.Name-}},其中 $releaseName就是后面的对象的一个引用变量,它的形式就是 $name,赋值操作使用 :=,这样 with语句块内部的 $releaseName变量仍然指向的是 .Release.Name

命名模板

命名模板能够让经常重复使用的部分与逻辑做成公共部分。
使用方法:使用define定义,template引入,在templates目录中默认下划线开头的文件为公共模板(helpers.tpl)

# [root@master templates]# cat _helpers.tpl
{{- define "name" -}}
{{- .Chart.Name -}}-{{ .Release.Name }}
{{- end -}}

# cat deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ template "name" . }}
...

试运行:

helm install web --dry-run ../../mychart

可以发现deloyment.yaml中的 name字段引用的是模板中的

  name: mychart-web

template指令是将一个模板包含在另一个模板中的方法。但是,template函数不能用于Go模板管道,就是说使用了template函数就不能用管道加入其他的函数。为了解决该问题,增加include功能。
举例:
我们加入一个标签labels

[root@master templates]# cat _helpers.tpl
{{- define "name" -}}
{{- .Chart.Name -}}-{{ .Release.Name }}
{{- end -}}

{{- define "labels" -}}
app: {{ template "name" . }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
{{- end -}}

编辑deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
#    chart: {{ .Chart.Name }}
#    app: {{ .Release.Name }}
    {{- include "labels" . | nindent 4 }}
  name: {{ template "name" . }}
spec:
....

编辑service.yaml

[root@master templates]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    {{- include "labels" . | nindent 4 }}
  name: {{ .Release.Name }}
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: {{ .Values.label }}

这里我们将几个yaml文件的公共部分都用模板代替,这样遇到繁复的地方可以简化代码和操作。
试运行可以看到使用公共模板达到了同样的目的。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐