helm version: v3.1.0
一、流程控制
https://helm.sh/docs/chart_template_guide/control_structures/
Helm 模板语言提供以下流程控制语句:
if/else
判断with
作用域range
循环
1、if/else 判断
基本结构
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
运算符
值为以下几种情况,则为假。
- 布尔值
false
- 数字
零
- 一个
空字符串
- 一个
nil
(empty or null) - 一个空的集合(
map
,slice
,tuple
,dict
,array
)
所有其他条件下,条件为真。
运算符:
eq
, ne
, lt
, gt
, and
, or
等
可以使用()
括号。
例子
看一个官方的例子:
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 }}
这个跟一般的判断不一样。 运算符写在了两个值的前面。
上面表示,
如果 .Values.favorite.drink
等于 "coffee" , 则添加 mug: true
。
添加从语句到{{ end }}
之间的所有内容。
例子:
containers:
- image: "{{ .Values.image.name }}:{{ .Chart.AppVersion }}"
name: {{ .Values.image.name }}
env:
{{ if .Values.env.rest }}
- name: host
value: localhost
- name: user
value: test
{{ end }}
- name: hello
value: world
values.yaml 里定义了:
env:
rest: False
渲染结果:
containers:
- image: "nginx:1.16.0"
name: nginx
env:
- name: hello
value: world
values.yaml:
env:
rest: True
再次测试:
containers:
- image: "nginx:1.16.0"
name: nginx
env:
- name: host
value: localhost
- name: user
value: test
- name: hello
value: world
if 判断就这样。 但是发现多了空行。
处理空行
为什么会有空行出现,因为模板引擎渲染时,会删除{{ }}之间的内容,但却会完全保留其余的空白。
可以使用特殊字符修改花括号语法:
{{-
(破折号与空格): 表示删除左边的空白。
-}}
: 表示删除右边空白,注意:换行符也是空白。
修改一下刚才的例子:
containers:
- image: "{{ .Values.image.name }}:{{ .Chart.AppVersion }}"
name: {{ .Values.image.name }}
env:
{{- if .Values.env.rest }}
- name: host
value: localhost
- name: user
value: test
{{- end }}
- name: hello
value: world
没有空格了:
containers:
- image: "nginx:1.16.0"
name: nginx
env:
- name: host
value: localhost
- name: user
value: test
- name: hello
value: world
2、with 变量作用域
with 控制变量作用域。
作用域就是变量的引用范围。
上面的变量使用都是 .Values
, 都是 .
开头,这个点其实就是一个当前范围的引用。with 就是修改这个范围,比如,设置到一个字典上。
先看一下语法,具体怎么回事下面举例子就明白了。
with 的语法类似简单的 if:
{{ with PIPELINE }}
# restricted scope
{{ end }}
没有用 with 的例子:
values.yaml:
env:
host: localhost
user: test
hello: world
deployment.yaml 的引用:
{{- if .Values.env }}
env:
- name: host
value: {{ .Values.env.host }}
- name: user
value: {{ .Values.env.user }}
- name: hello
value: {{ .Values.env.hello }}
{{- end }}
上面的变量引用都需要从.Values
开始, 有点繁琐。
with 的例子:
deployment.yaml 添加 with 以后:
{{- with .Values.env }}
env:
- name: host
value: {{ .host }}
- name: user
value: {{ .user }}
- name: hello
value: {{ .hello }}
{{- end }}
with 语句块里, 把当前范围定义在了.Values.env
这个变量上了。
查看一下这个 . 的内容
deployment.yaml 添加 . 的值。
{{- with .Values.env }}
env:
- name: host
value: {{ .host }}
- name: user
value: {{ .user }}
- name: hello
value: {{ .hello }}
- name: test
value: {{ . }}
{{- end }}
结果:
env:
- name: host
value: localhost
- name: user
value: test
- name: hello
value: world
- name: test
value: map[enable:true hello:world host:localhost user:test]
是一个 map 映射(字典)。跟不使用 with 的 .Values.env 结果一样。
但是有一个缺点,因为 with 修改了 .
的范围, 所以语句块里不能再引用 .Chart
,.Release
,.Values
等变量。
解决办法有两个:
- 使用模板变量。注意,不是上面那些变量,是模板内定义的变量。
- 使用
$
符号,$
始终指向根上下文。
3、模板变量
https://helm.sh/docs/chart_template_guide/variables/
变量的定义:
$relname := .Release.Name
定义变量跟别的语言不一样, 名称也需要 $ 符号, 赋值运算符是 :=
。
在 with
里面不能调用其他变量,那么就在 with
之外把其他变量重新定义为模板变量。
例子:
{{- $relname := .Release.Name }}
{{- with .Values.env }}
env:
- name: host
value: {{ .host }}
- name: user
value: {{ .user }}
- name: hello
value: {{ .hello }}
- name: releaseName
value: {{ $relname }}
{{- end }}
4、始终指向根上下文的 $
变量
模板变量通常都不是全局的,作用域取决于定义它的语句块。
上面的 relname 是全局的,因为他没有在其他的语句块里。
但是有一个变量始终是全局的,就是 $
变量。
$
始终指向根上下文。 上面的 with 里面想要调用 .Release.Name
,使用 $.Release.Name
就可以。
如:
{{- with .Values.env }}
env:
- name: host
value: {{ .host }}
- name: user
value: {{ .user }}
- name: hello
value: {{ .hello }}
- name: releaseName
value: {{ $.Release.Name }}
{{- end }}
5、range
循环
类似于 for 循环。直接看例子
官方的例子:
先在 values.yaml 里添加数据。
favorite 是上个例子的,主要关注 pizzaToppings。
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
创建 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 }}
关注 range 那里。
看一下渲染结果:
# Source: mytest/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kljadklf-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
toppings: |-
- "Mushrooms"
- "Cheese"
- "Peppers"
- "Onions"
range
遍历 .Values.pizzaToppings
列表。
而且也跟 with
一样,语句块里的 .
的范围变了,这里直接表示的每一次循环遍历出来的对应列表项。 .
直接就是表示可用的值了,后面的函数只是修改了数据的头字母大写以及字符串化。
例子
之前 deployment.yaml
里 env
字段的变量是手动添加的,有点傻对不对。可以使用 range
添加。
但是 values.yaml
里的数据是字典,循环出的东西会有问题,先来看一下。
{{- if .Values.env.enable }}
env:
{{- range .Values.env }}
- name: {{ . }}
value: {{ . }}
{{ end }}
{{- end }}
其实一看就知道有问题, name
与 value
的值都是一样的。
结果:
env:
- name: true
value: true
- name: world
value: world
- name: localhost
value: localhost
- name: test
value: test
对比一下 yaml.yaml
的值:
env:
enable: True
host: localhost
user: test
hello: world
只取出了 value 部分,没有取出 key。
办法也是有, 使用模板变量。
取出字典 key 与 value
如:
{{- if .Values.env.enable }}
env:
{{- range $key,$value := .Values.env }}
- name: {{ $key }}
value: {{ $value }}
{{- end }}
{{- end }}
结果:
env:
- name: enable
value: true
- name: hello
value: world
- name: host
value: localhost
- name: user
value: test
这样就可以了。
对列表也可以取出索引和值。
取出列表索引与值
上面 pizza 的例子改一下:
toppings: |-
{{- range $index,$value := .Values.pizzaToppings }}
{{ $index }}:{{ $value }}
{{- end }}
执行结果:
toppings: |-
0:mushrooms
1:cheese
2:peppers
3:onions
二、一些骚操作
1、资源开关
if
判断可以直接放在模板文件的头和尾。从而实现关闭和开启某个资源的部署。
如:
service.yaml:
{{ if .Values.service -}}
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
ports:
{{- range .Values.service }}
-
{{- range $key,$value := . }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
selector:
app: nginx
type: ClusterIP
{{ end }}
values.yaml:
service:
# - name: work
# port: 80
# protocol: TCP
# targetPort: 80
# - name: status
# port: 88
# protocol: TCP
# targetPort: 88
这样的 values.yaml,因为 service 是空的。不会渲染 service.yaml 这个文件。
values.yaml 改成这样:
service:
- name: work
port: 80
protocol: TCP
targetPort: 80
- name: status
port: 88
protocol: TCP
targetPort: 88
结果:
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
ports:
-
name: work
port: 80
protocol: TCP
targetPort: 80
-
name: status
port: 88
protocol: TCP
targetPort: 88
selector:
app: nginx
type: ClusterIP
2、values.yaml 变量的值原样添加到模板中
上面的 service port 是通过 range
循环实现。我们 values.yaml 里 service 的值字段与 k8s 的一致,可以原模原样的添加到模板中。
就可以这样:
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
ports:
{{- toYaml .Values.service | nindent 4 }}
selector:
app: nginx
type: ClusterIP
把 .Values.service
的内容直接添加到这个位置。
直接添加到这里,数据会从行首开始,导致因为缩进问题出问题。
所以使用 nindent
函数添加缩进。
nindent
函数会对每行的内容都做缩进。
indent
函数只对单行数据做缩进。
三、注释
模板中使用{{/* */}}
注释。如:
env:
- name: ttt
value: yyy
{{/*
{{- include "variables" . | nindent 12 -}}
*/}}
{{- range $x, $y := .Values.dev }}
- name: {{ $x }}
value: {{ $y }}
{{- end }}
注释的内容不会被渲染出来。
非模板的文件,如 values.yaml。 就是yaml文件的注释方式#
。
三、命名模板
https://helm.sh/docs/chart_template_guide/named_templates/
命名模板有时候也被称为 部分或子模板。
叫部分模板的原因估计是因为,这个模板只是为了定义部分内容。
相对于 deployment.yaml 这种主模板,命名模板只是定义部分通用内容,然后在各个主模板中调用。
要注意的一个细节
命名模板的名称是全局的,需要全局唯一。 而且有时候 chart 还依赖子 chart, 为了防止两个 chart 的模板名称重复。
一种流行的命名约定是在每个定义的模板前添加chart名称:{{ define "mychart.labels" }}
定义模板
刚创建的chart templates目录下有个_helpers.tpl
文件。
公共的命名模板都放在这个文件里。 但不是必须的,起个别的名字也可以用,甚至放到主模板也可以。
这只是一种规定,为了容易维护都放到这个文件里,一些非公共的模板也会放到以tpl
结尾的文件里。
命名模板使用 define
来定义。
如,这里先简单定义一个只包含字符串的模板,用作资源名称。
按照惯例,定义的模板上面都需要简单的文本描述他的作用。
[root@k8s-master3 templates]# cat _helpers.tpl
{{/* 定义资源名称 */}}
{{ define "mytest.name" -}}
mytest-name
{{ end }}
这里的定义语句也会出现空行,所以使用-}}
删除后面换行符。
里面的内容最好也是从行首开始,防止导致一些缩进问题。
template
引用模板
使用template
引用。
metadata:
labels:
app: {{ .Values.name }}
name: {{ template "mytest.name" }}
spec:
结果:
metadata:
labels:
app: web1
name: mytest-name
spec:
命名模板正确引用了, 但是还是多出来一个空行。
处理空行
这个因为定义模板的语句块,字符串mytest-name
后面有一个换行符。
而name: {{ template "mytest.name" }}
后面很显然也有一个换行符。
所以就有三个办法解决了:
1、删除定义模板里的回车符:
{{ define "mytest.name" -}}
mytest-name{{ end }}
2、不清楚为什么这样也行
{{-
官方的解释是只删除左侧的空白,可这看起来没这么简单。
{{ define "mytest.name" -}}
mytest-name
{{- end }}
3、删除引用模板语句后面的回车符:
metadata:
labels:
app: {{ .Values.name }}
name: {{ template "mytest.name" -}}
spec:
模板引用变量的范围
记得之前的with
有一个变量作用域,变量引用范围的东西。这里也是。
还是刚才的模板,改一下:
{{/* 定义资源名称 */}}
{{ define "mytest.name" -}}
{{ .Chart.Name }}-{{ .Release.Name }}
{{ end }}
为了方便看到效果,引用模板这里加点东西test-
, 不加的话会报错的:
metadata:
labels:
app: {{ .Values.name }}
name: test-{{ template "mytest.name" -}}
spec:
结果,下面这个只有test--,如果没有test-,结果只有一个 -, 当然报错啦:
metadata:
labels:
app: web1
name: test--
spec:
发现定义的模板里, .Chart.Name
与.Release.Name
是空的。
这是因为里面的.Chart.Name
与.Release.Name
没有找到。
原因是:
define
需要接收template
传递变量范围的参数。
我们只需要把范围传递就可以了:
metadata:
labels:
app: {{ .Values.name }}
name: test-{{ template "mytest.name" . -}}
spec:
这里是把当前范围.
传递过去了。这里的当前范围是根范围,模板里的变量当然都可以引用。
include
引用模板
template
引用模板只是一个动作,它不是函数,没有返回值,不能通过管道传递给其他函数处理。
include
就是解决这个问题的。
看一下这个例子, 定义label:
{{/* 定义label */}}
{{ define "mytest.label" -}}
app: {{ .Release.Name }}
release: stable
env: qa
{{ end }}
引用的话就是这样:
metadata:
labels:
{{ template "mytest.label" . }}
name: test-{{ template "mytest.name" . -}}
spec:
报缩进问题:
[root@k8s-master3 ~]# helm lint mytest
==> Linting mytest
[INFO] Chart.yaml: icon is recommended
[ERROR] templates/deployment.yaml: unable to parse YAML: error converting YAML to JSON: yaml: line 9: mapping values are not allowed in this context
Error: 1 chart(s) linted, 1 chart(s) failed
提示不允许在这个上下文。就是缩进有问题。 需要使用函数nindent
添加缩进。
metadata:
labels:
{{ include "mytest.label" . | nindent 4 }}
name: test-{{ template "mytest.name" . -}}
spec:
会有空行, 加-
慢慢调试。 最终:
metadata:
labels:
{{- include "mytest.label" . | nindent 4 }}
name: test-{{ template "mytest.name" . -}}
spec:
{{/* 定义label */}}
{{- define "mytest.label" -}}
app: {{ .Release.Name }}
release: stable
env: qa
{{- end }}
四、上传harbor
不影响harbor做镜像仓库的功能。
1、harbor支持
首先,启动harbor的时候添加参数--with-chartmuseum
[root@lvs harbor]# ./install.sh --help
Note: Please set hostname and other necessary attributes in harbor.yml first. DO NOT use localhost or 127.0.0.1 for hostname, because Harbor needs to be accessed by external clients.
Please set --with-notary if needs enable Notary in Harbor, and set ui_url_protocol/ssl_cert/ssl_cert_key in harbor.yml bacause notary must run under https.
Please set --with-clair if needs enable Clair in Harbor
Please set --with-chartmuseum if needs enable Chartmuseum in Harbor
[root@lvs harbor]# ./install.sh --with-chartmuseum
[Step 0]: checking installation environment ...
Note: docker version: 19.03.6
Note: docker-compose version: 1.25.4
[Step 1]: loading Harbor images ...
.....省略......
启动以后,在项目里就可以看到多了一个Helm Charts。
添加用于上传项目的用户。
2、helm支持
https://github.com/chartmuseum/helm-push
需要安装插件。
[root@k8s-master3 ~]# helm plugin install https://github.com/chartmuseum/helm-push
Downloading and installing helm-push v0.8.1 ...
https://github.com/chartmuseum/helm-push/releases/download/v0.8.1/helm-push_0.8.1_linux_amd64.tar.gz
Installed plugin: push
[root@k8s-master3 ~]#
3、添加仓库
把harbor添加成仓库。
[root@k8s-master3 ~]# helm repo add harbor http://172.100.102.91/chartrepo/mycharts --username sst --password Abcdefg123
"harbor" has been added to your repositories
里面的chartrepo是固定的。 mycharts是harbor上创建的项目名称。
也可以不添加,只是需要在上传的时候需要指定url。
4、helm push
使用helm push
命令推送。
有几种方式:
1、直接推送目录,自动打包
2、helm package
打包以后推送。
[root@k8s-master3 ~]# helm push mytest harbor
Pushing mytest-0.1.0.tgz to harbor...
Done.
push完成,就会发现harbor的对应项目的Helm Charts标签下多了一个上传的chart。
5、从仓库下载
更新仓库信息:
[root@k8s-master3 ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "harbor" chart repository
...Successfully got an update from the "choerodon" chart repository
...Successfully got an update from the "ali" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈
发现已经多了harbor。
搜索一下上传的那个chart:
[root@k8s-master3 ~]# helm search repo mytest
NAME CHART VERSION APP VERSION DESCRIPTION
harbor/mytest 0.1.0 1.16.0 A Helm chart for Kubernetes
安装都一样:
[root@k8s-master3 ~]# helm install webtest harbor/mytest
Error: Deployment in version "v1" cannot be handled as a Deployment: v1.Deployment.Spec: v1.DeploymentSpec.Template: v1.PodTemplateSpec.Spec: v1.PodSpec.Containers: []v1.Container: v1.Container.Env: []v1.EnvVar: v1.EnvVar.Value: ReadString: expects " or n, but found t, error found in #10 byte of ...|,"value":true},{"nam|..., bigger context ...|:{"containers":[{"env":[{"name":"enable","value":true},{"name":"hello","value":"world"},{"name":"hos|...
[root@k8s-master3 ~]# ls
我这里安装报错了, 是因为chart本来就有问题,正常情况下肯定需要详细的测试,然后才能上传。