Helm模版开发文档
Posted 张志翔 ̮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Helm模版开发文档相关的知识,希望对你有一定的参考价值。
目录
开始
Charts 的结构如下:
mychart/
Chart.yaml
values.yaml
charts/
templates/
...
templates/
:这个目录下装的是 k8s 的资源模板文件。values.yaml
:这个文件里的是这个 chart 的默认值。Chart.yaml
:这个文件里是对这个 chart 的描述。
1.2 简单示例
下面,创建一个叫 mychart 的 chart。
$ helm create mychart
Creating mychart
helm 客户端会自动的为我们创建些文件:
mychart
├── Chart.yaml
├── charts
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── ingress.yaml
│ └── service.yaml
└── values.yaml
NOTES.txt
deployment.yaml
service.yaml
_helpers.tpl
3. 第一个模板
rm -rf mychart/templates/*.*
删除掉 Helm 生成的模板文件,我们自己来实现一个模板。
我们的第一个模板是创建一个 ConfigMap。创建一个 mychart/templates/configmap.yaml
文件,并写入如下内容:
apiVersion: v1
kind: ConfigMap
metadata:
name: mychart-configmap
data:
myvalue: "Hello World"
注意:Template 目录下的文件没有严格的命名规范。但我们推荐用 .yaml
的后缀表示 YAML 文件,用 .tpl
表示帮助文件。
现在我们来部署它:
$ helm install ./mychart
NAME: full-coral
LAST DEPLOYED: Tue Nov 1 17:36:01 2016
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
mychart-configmap 1 1m
我们可以来查看下部署信息
$ 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
这条命令可以通过 release 名来打印其相关信息。
现在,删除掉刚刚发布的 release:helm delete full-coral
。
内置对象
- Release:用来描述 release 本身
- Release.NAME
- Release.Time
- Release.Namespace
- Release.Service:值总是
Tiller
- Release.Revision:release 版本号。从 1 开始,每次执行
helm upgrade
,数加 1 - Release.IsUpgrade:本次操作是否为升级
- Release.IsInstall:本次操作是否为安装
- Values:
- Chart:
Chart.yaml
里的内容 - Files:
File.Get
通过名字获取文件(.Files.Get config.ini
)File.getBytes
以字节流的方式获取,在获取类型图片时比较有用
- Capabilities:
- Template:
Values Files
前面讲了内置对像 Values
,它的值有四个来源:
values.yaml
文件- 如果这是个子 chart,其父 chart 的
Values.yaml
文件 - 在
helm install
或helm upgrade
时,通过-f
指定的文件 - 通过
--set
指定的参数( 例:helm install --set foo=bar ./mychart )
优先级从上到下依次增加,即 --set
最高。
现在让我们来编辑 mychart/values.yaml
,删除默认值,只写一个参数:
favoriteDrink:coffee
在模板中使用刚刚写的参数:
apiVersion: v1
kind: ConfigMap
metadata:
name: .Release.Name -configmap
data:
myvalue: "Hello World"
drink: .Values.favoriteDrink
注意最后一行,我们通过访问 Values 属性的方式 .Values.favoriteDrink
来获取 favoriteDrink 值。
来看下渲染的结果:
$ helm install --dry-run --debug ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart
NAME: geared-marsupi
TARGET NAMESPACE: default
CHART: mychart 0.1.0
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: geared-marsupi-configmap
data:
myvalue: "Hello World"
drink: coffee
还可以通过 --set
覆盖掉这个值:
helm install --dry-run --debug --set favoriteDrink=slurm ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart
NAME: solid-vulture
TARGET NAMESPACE: default
CHART: mychart 0.1.0
MANIFEST:
---
# 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
。
Values 文件还可以包含结构内容。
favorite:
drink: coffee
food: pizza
现在我们需要修改下模板:
apiVersion: v1
kind: ConfigMap
metadata:
name: .Release.Name -configmap
data:
myvalue: "Hello World"
drink: .Values.favorite.drink
food: .Values.favorite.food
如果你想从默认值中删除一个key,你可以通过传 null
值,这样 Helm 在合并时就会删除这个 key。
举个例子, 名为 Drupal 的 chart 中配置的有存活检测。下面是它的默认设置:
livenessProbe:
httpGet:
path: /user/login
port: http
initialDelaySeconds: 120
如果你要使用 exec
替换 httpGet
,可以通过 --set livenessProbe.exec.command=[cat,docroot/CHANGELOG.txt]
,Helm 会合并默认值和传进去的值,结果如下:
livenessProbe:
httpGet:
path: /user/login
port: http
exec:
command:
- cat
- docroot/CHANGELOG.txt
initialDelaySeconds: 120
这时,k8s 就会出错,因为你定义了两个 liveness handler
。要解决这个问题,你可以通过给 livenessProbe.httpGet
传个 null
值来删除它:
helm install stable/drupal --set image=my-registry/drupal:0.1.0 --set livenessProbe.exec.command=[cat,docroot/CHANGELOG.txt] --set livenessProbe.httpGet=null
模板函数和Pipelines
让我们从一个练习开始:当我们往 .Values
里注入一个字符串时,应当用引号将它们括起来,在模板中可以直接使用 quote
函数来实现:
apiVersion: v1
kind: ConfigMap
metadata:
name: .Release.Name -configmap
data:
myvalue: "Hello World"
drink: quote .Values.favorite.drink
food: quote .Values.favorite.food
模板函数的语法如下:functionName arg1 arg2...
。在上面的小例子中, quote .Values.favorite.drink
, 使用了 quote
函数并传递了一个参数。
通过管道可以在一行里干多件事,我们使用 pipeline 重写上面的例子:
apiVersion: v1
kind: ConfigMap
metadata:
name: .Release.Name -configmap
data:
myvalue: "Hello World"
drink: .Values.favorite.drink | quote
food: .Values.favorite.food | quote
通过 pipeline,我们可以链式的调用多个函数:
apiVersion: v1
kind: ConfigMap
metadata:
name: .Release.Name -configmap
data:
myvalue: "Hello World"
drink: .Values.favorite.drink | quote
food: .Values.favorite.food | upper | quote
这个函数允许你指定一个默认值:
drink: .Values.favorite.drink | default "tea" | quote
操作符是按照函数的方式实现的,返回一个布尔值。使用 eq, ne, lt, gt, and, or, not
时,要将它们放到句子的最前面,后面跟上对应的参数。多个操作符一起使用时,可以用小括号包起来。
/* include the body of this if statement when the variable .Values.fooString exists and is set to "foo" */
if and .Values.fooString (eq .Values.fooString "foo")
...
end
/* do not include the body of this if statement because unset variables evaluate to false and .Values.setVariable was negated with the not function. */
if or .Values.anUnsetVariable (not .Values.aSetVariable)
...
end
控制流
Helm 的模板语言提供下面几种控制结构:
if / else
with
range
提供类型for each
的循环
另外,还提供了几种方式来声明和使用命名模板:
define
template
block
这节只讨论 if
, with
, 和 range
。其它的会在之后的 “Named Templates” 那节介绍。
基本结构如下:
if PIPELINE
# Do something
else if OTHER PIPELINE
# Do something else
else
# Default case
end
注意这里我们用 pipelines
面不是 values
,是为了表明这个控制结构是可以运行完全的 pipeline
的,而仅仅只能放个值。
下面情况其值会被认为是 false:
- bool 型的 false
- 数字 0
- 空字符串
- nil
- 空集合(
map, slice, tuple, dict, array
)
让我们来修改下 ConfigMap
。当 drink
是 coffee
时,增加一个设置:
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 and .Values.favorite.drink (eq .Values.favorite.drink "coffee") mug: true end
注意, .Values.favorite.drink
必须被定义,否则当它和 coffee
作比较时会报错。最后的输出就变成:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eyewitness-elk-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: true
空白的使用在模板中是受限制的。下面把之前的代码格式修改下,使它更易阅读:
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 --dry-run --debug ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart
Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: did not find expected key
这就是因为空格导致的:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eyewitness-elk-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: true
mug
的嵌套位置不正确。让我们简单的修改下:
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 格式正确了,但是有点丑:
# 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 中有行是空的。
表示去掉左边的空格,
表示去掉右边的空格。
通过 .
可以引用当前的作用域。 .Values
告诉模板在当前作用域上查找 Values
。
可以通过 with
来调整作用域
with PIPELINE
# restricted scope
end
with
允许你把当前作用域(.
)指到一个特定的对象上。举个例子,将 .
指到 .Values.favorites
上:
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
警告,在这个特定的作用域中,你不能够访问其父领域的对象。下面的例子,访问会出错:
- with .Values.favorite
drink: .drink | default "tea" | quote
food: .food | upper | quote
release: .Release.Name
- end
Helm 可以通过 range
操作符来迭代集合。
在 values.yaml
中增加列表:
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
现在修改下 ConfigMap 的模板,来打印出上面的列表:
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
和 with
一样,range
也可以设置作用域,所以在这里,.
表示的是 pizzaToppings
这个作用域。我们能把 .
直接传递给管道使用 . | 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: |-
表示这是一个多行的字符串。
变量
在模板里,变量较少被使用。但通过使用变量,可以使 with
和 range
得到更好的使用。
这是之前的一个例子,会报错:
- with .Values.favorite
drink: .drink | default "tea" | quote
food: .food | upper | quote
release: .Release.Name
- end
在 Helm 模板里,一个变量是对一个对象的引用。格式是 $name
。使用 :=
进行赋值。我们可以通过变量重写上面的代码:
apiVersion: v1
kind: ConfigMap
metadata:
name: .Release.Name -configmap
data:
myvalue: "Hello World"
- $relname := .Release.Name -
- with .Values.favorite
drink: .drink | default "tea" | quote
food: .food | upper | quote
release: $relname
- end
在使用 with
之前,我们 $relname := .Release.Name
。现在在 with
的作用域里,$relname
仍然指向 .Release.name
。
Named Templates
本节我们学习如何定义一个命名模板,并在别处使用它。
你要注意的是:模板的名字是全局性的。当你定义了两个相同名字的模板时,最后加载的那个将被使用。
推荐的做法是在定义模板名字时,加上 chart:define "mychart.labels"
。
在开始具体写模板前,有些命令惯例值得提醒下:
template/
中的大部分文件可以认为是 k8s 的资源NOTES.txt
文件除外- 有下划线的除外
define
允许我们在模板文件中创建一个命名模板:
define "MY.NAME"
# body of template here
end
举个例子
- define "mychart.labels"
labels:
generator: helm
date: now | htmlDate
- end
现在我们把它嵌套到之前的 ConfigMap 上,然后通过 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
最终,经过渲染,文件会变成:
# 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"
习惯上 Helm 把这些模板放在一个特殊的文件里,通常是 _helpers.tpl
。让我们来移到下它吧:
/* Generate basic labels */
- define "mychart.labels"
labels:
generator: helm
date: now | htmlDate
- end
按惯例,define
函数应该有个简单的使用说明,用 /* ... */
括起来。
虽然是在 _helpers.tpl
中定义的,但仍然可以在 configmap.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
再次提醒下,命名模板是全局性的。如果定义了两个相同名字的模板,则后面定义的那个生效。
如下,我们定义了个模板:
- define "mychart.app" -
app_name: .Chart.Name
app_version: " .Chart.Version + .Release.Time.Seconds "
- 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" .
但是结果不是我们预期的:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: measly-whippet-configmap
labels:
app_name: mychart
app_version: "0.1.0+1478129847"
data:
myvalue: "Hello World"
drink: "coffee"
food: "pizza"
app_name: mychart
app_version: "0.1.0+1478129847"
可以看到 app_version
的缩进是不对的。
通过使用 nindent
来实现正确的缩进:
apiVersion: v1
kind: ConfigMap
metadata:
name: .Release.Name -configmap
labels:
- include "mychart.app" . | nindent 4
data:
myvalue: "Hello World"
- range $key, $val := .Values.favorite
$key : $val | quote
- end
- include "mychart.app" . | nindent 2
模板中访问文件
在之前的章节中我们学会了创建和访问命令模板,让我们可以方便的在一个模板中导入另一个模板。但是有些时我们要导入一个文件而不需要经过模板的渲染。
Helm 通过 .Files
对象提供了访问文件的功能。在开始例子之前,有些点需要我们注意:
- 在 Helm 中可以增加额处文件,它们也会被发给
Tiller
。但要注意,由于 k8s 存储大小的限制,Charts
的大小不能超过 1M。 - 因为安全的原因,有些文件是不能被
.Files
访问到的templates/
中的文件不能被访问- 在
.helmignore
中的文件不能被访问
我们直接在 mychar/
下创建三个文件config1.toml
:
message = Hello from config 1
config2.toml
:
message = Hello from config 2
config3.toml
:
message = Hello from config 3
我们知道它位的名字,所以我们可以使用 range
循环的将它的们内容注入到 ConfigMap
中
apiVersion: v1
kind: ConfigMap
metadata:
name: .Release.Name -configmap
data:
- $files := .Files
- range tuple "config1.toml" "config2.toml" "config3.toml"
. : |-
$files.Get .
- end
通过运行它,我们能够得到:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: quieting-giraf-configmap
data:
config1.toml: |-
message = Hello from config 1
config2.toml: |-
message = This is config 2
config3.toml: |-
message = Goodbye from config 3
使用base-64加密:
apiVersion: v1
kind: Secret
metadata:
name: .Release.Name -secret
type: Opaque
data:
token: |-
.Files.Get "config1.toml" | b64enc
# Source: mychart/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: lucky-turkey-secret
type: Opaque
data:
token: |-
bWVzc2FnZSA9IEhlbGxvIGZyb20gY29uZmlnIDEK
创建 NOTES.Txt 文件
这节,来介绍下如何给你的 chart 增加说明。在 helm install
或者 helm upgrade
的最后,会打印一些对用户有用的信息,而这些信息是可以高度定制化的。
创建 templates/NOTES.txt
文件。它是个普通文件,但是可以像模板那样来使用,能够在文件中使用模板函数和亦是。
现在来创建个简单的 NOTES.txt
文件:
Thank you for installing .Chart.Name .
Your release is named .Release.Name .
To learn more about the release, try:
$ helm status .Release.Name
$ helm get .Release.Name
现在运行 helm install mychart
,我们就能在底部看到:
RESOURCES:
==> v1/Secret
NAME TYPE DATA AGE
rude-cardinal-secret Opaque 1 0s
==> v1/ConfigMap
NAME DATA AGE
rude-cardinal-configmap 3 0s
NOTES:
Thank you for installing mychart.
Your release is named rude-cardinal.
To learn more about the release, try:
$ helm status rude-cardinal
$ helm get rude-cardinal
NOTES.txt
不是必须的,但强烈推荐你创建一个。
Subcharts and Global Values
之前我们一直在使用一个 chart,但 chart 之间是可以存在依赖关系的,称之为 subchart,它们可以有自己的值和模板。这节我们将会创建一个 subchart,来看看它是如何获取值的。
在开始前,有几个关于 subchart 的知识点需要大家知道:
- subchart 是可以独立部署的,故而它不能明确的依赖 parent chart。
- subchart 不能获取 parent chart 的值。
- parent chart 可以覆盖 subchart 的值。
- Helm 有一个 global values 的概念,可以被所有 chart 获取。
$ cd mychart/charts
$ helm create mysubchart
Creating mysubchart
$ rm -rf mysubchart/templates/*.*
2. ADDING VALUES AND A TEMPLATE TO THE SUBCHART
Debugging Templates
几种 debug 的方式:
helm lint
:检查你的 chart 是否有可以优化的地方helm install --dry-run --debug
:让 Tiller 渲染模板,并返回其生成的yaml
文件helm get manifest
:查看 k8s 部署的是什么样的模板
附录:YAML 技巧
前面全部关注在模板的书写上,现在让我们来看看 YAML 的格式。
两种类型的集合,map和队列
map:
one: 1
two: 2
three: 3
sequence:
- one
- two
- three
数字类型
count: 1
size: 2.34
如果它们被引号引起来,就变成了字符串
count: "1" # <-- string, not int
size: '2.34' # <-- string, not float
布尔类型也是如此:
isGood: true # bool
answer: "true" # string
空值由 null
表示,而不是 nil
。
需要注意的是,port: "80"
是符合 YAML 语法的,当通过模板引擎传递给 k8s 时,如果 k8s 要求此字段是 int 类型,那么会报错。
可以通过 Yaml 的标记,对类型进行强制的转换:
coffee: "yes, please"
age: !!str 21
port: !!int "80"
上面的代码,!!str
告诉分析器,age
是的 String 类型, port
是个 int
类型。
因为 Yaml 是 Json 的超集,所以任何符合 JSON 格式的文档都符合 YAML 的规范。
"coffee": "yes, please",
"coffees": [
"Latte", "Cappuccino", "Espresso"
]
上面内容的另一种表示方式:
coffee: yes, please
coffees:
- Latte
- Cappuccino
- Espresso
两者还能混合书写:
coffee: "yes, please"
coffees: [ "Latte", "Cappuccino", "Espresso"]
上面三种方式表示的内容是一致的。
这表示 values.yaml
里可以包含 JSON 格式的数据。但 Helm 不允许文件后缀名为 .json
。
附录: Go 语言数据类型和模板
因为 Helm 的模板编辑是基于 Go 语言的,而 Go 本身就是一种强类型的语言,所以模板中的变量是有类型的。
- string
- bool
- int
- float64
- a byte slice
- struct
- a slice
- a string-kyed map
获取变量类型最简单的方式是 printf "%t"
。也可以使用 typeof
和 kindf
函数来获取。
以上是关于Helm模版开发文档的主要内容,如果未能解决你的问题,请参考以下文章