Helm模版开发文档

Posted 张志翔 ̮

tags:

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

目录

开始

内置对象

Values Files

模板函数和Pipelines

控制流

变量

Named Templates

模板中访问文件

创建 NOTES.Txt 文件

Subcharts and Global Values

Debugging Templates

附录:YAML 技巧

附录: Go 语言数据类型和模板


开始

1.1 Charts

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 

1. 删除默认Key

如果你想从默认值中删除一个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 函数并传递了一个参数。

1. Pipelines

通过管道可以在一行里干多件事,我们使用 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 

2. 使用 Default 函数

这个函数允许你指定一个默认值:

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

3. OPERATORS ARE FUNCTIONS

操作符是按照函数的方式实现的,返回一个布尔值。使用 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

这节只讨论 ifwith, 和 range。其它的会在之后的 “Named Templates” 那节介绍。

1. IF / ELSE

基本结构如下:

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

注意这里我们用 pipelines 面不是 values,是为了表明这个控制结构是可以运行完全的 pipeline 的,而仅仅只能放个值。

下面情况其值会被认为是 false:

  1. bool 型的 false
  2. 数字 0
  3. 空字符串
  4. nil
  5. 空集合(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

2. 空白管理

空白的使用在模板中是受限制的。下面把之前的代码格式修改下,使它更易阅读:

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 中有行是空的。

 表示去掉左边的空格,表示去掉右边的空格。

3. 修改作用域

通过 . 可以引用当前的作用域。 .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 

4. range 操作

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"

1. 特殊文件

在开始具体写模板前,有些命令惯例值得提醒下:

  • template/中的大部分文件可以认为是 k8s 的资源
  • NOTES.txt文件除外
  • 有下划线的除外

2. 使用 define 和 template

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 

再次提醒下,命名模板是全局性的。如果定义了两个相同名字的模板,则后面定义的那个生效。

3. 给模板设定作用域

4. include 函数

如下,我们定义了个模板:

- 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 中的文件不能被访问

1. Basic Example

我们直接在 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

2. Path Helpers

3. Glob Patterns

5. Encoding

使用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

6. Lines

创建 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 的知识点需要大家知道:

  1. subchart 是可以独立部署的,故而它不能明确的依赖 parent chart。
  2. subchart 不能获取 parent chart 的值。
  3. parent chart 可以覆盖 subchart 的值。
  4. Helm 有一个 global values 的概念,可以被所有 chart 获取。

1. Creating A Subchart

$ 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 的格式。

1. Scalars and Collections

两种类型的集合,map和队列

map:
  one: 1
  two: 2
  three: 3

sequence:
  - one
  - two
  - three

2. Yaml 中的类型

数字类型

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 类型。

3. Yaml 中的字符串

5. Yaml 是 Json 的超集

因为 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

Yaml Anchor

附录: Go 语言数据类型和模板

因为 Helm 的模板编辑是基于 Go 语言的,而 Go 本身就是一种强类型的语言,所以模板中的变量是有类型的。

  • string
  • bool
  • int
  • float64
  • a byte slice
  • struct
  • a slice
  • a string-kyed map

获取变量类型最简单的方式是 printf "%t"。也可以使用 typeof 和 kindf函数来获取。

以上是关于Helm模版开发文档的主要内容,如果未能解决你的问题,请参考以下文章

Helm入门文档和介绍

Helm入门文档和介绍

k8s结合helm部署

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

k8s之Helm(快速下载yaml文件模板)

helm模板文件chart编写语法详解