1. 前言
Helm官方文档写的很棒,本文会摘录Helm官方文档中的一些基础知识和最佳实践,备忘。
更细致全面的内容,请移步阅读Helm官方文档。
参考文档:
2. Helm三大概念
Chart 代表着 Helm 包。它包含在 Kubernetes 集群内部运行应用程序,工具或服务所需的所有资源定义。你可以把它看作是 Homebrew formula,Apt dpkg,或 Yum RPM 在Kubernetes 中的等价物。
Repository(仓库) 是用来存放和共享 charts 的地方。它就像 Perl 的 CPAN 档案库网络 或是 Fedora 的 软件包仓库,只不过它是供 Kubernetes 包所使用的。
Release 是运行在 Kubernetes 集群中的 chart 的实例。一个 chart 通常可以在同一个集群中安装多次。每一次安装都会创建一个新的 release。以 MySQL chart为例,如果你想在你的集群中运行两个数据库,你可以安装该chart两次。每一个数据库都会拥有它自己的 release 和 release name。
在了解了上述这些概念以后,我们就可以这样来解释 Helm:
Helm 安装 charts 到 Kubernetes 集群中,每次安装都会创建一个新的 release。你可以在 Helm 的 chart repositories 中寻找新的 chart。
3. Helm安装资源的顺序
Helm按照以下顺序安装资源:
- Namespace
- NetworkPolicy
- ResourceQuota
- LimitRange
- PodSecurityPolicy
- PodDisruptionBudget
- ServiceAccount
- Secret
- SecretList
- ConfigMap
- StorageClass
- PersistentVolume
- PersistentVolumeClaim
- CustomResourceDefinition
- ClusterRole
- ClusterRoleList
- ClusterRoleBinding
- ClusterRoleBindingList
- Role
- RoleList
- RoleBinding
- RoleBindingList
- Service
- DaemonSet
- Pod
- ReplicationController
- ReplicaSet
- Deployment
- HorizontalPodAutoscaler
- StatefulSet
- Job
- CronJob
- Ingress
- APIService
4. Chart模板指南
内容太多,建议直接阅读Helm官方文档 - Chart模板指南,了解基础概念和语法。
本节只简单说明几个概念和语法。
4.1. 模板
templates/
目录中的.yaml
文件和.tpl
文件,就是模板。
当Helm评估chart时,会通过模板渲染引擎将所有文件发送到templates/目录中。 然后收集模板的结果并发送给Kubernetes。
values.yaml 文件也导入到了模板。这个文件包含了chart的 默认值 。这些值会在用户执行helm install 或 helm upgrade时被覆盖。
Chart.yaml 文件包含了该chart的描述。你可以从模板中访问它。charts/目录 可以 包含其他的chart(称之为 子chart)。
模板中横线的作用:
{{-`:表示在模板渲染时要去掉前面的空白字符和换行符。 - `-}}
:表示在模板渲染时要去掉后面的空白字符和换行符。
4.2. 变量和作用域
Helm模板中,变量是对另一个对象的命名引用。
变量的作用域一般不是全局的,而是其声明所在的块。所谓块,是指if/else
、with
、range
、define
、template
、block
定义的范围。
.
代表对当前作用域的引用,.Values
表示在当前作用域查找Values对象。
每个模板中,默认可以访问到的内置对象有Release、Values、Chart、Files、Capabilities和Template。
如果定义了块,那么在块中不可以通过.Values
找到Values对象。
$
代表对根作用域的引用,$.Values
表示在根作用域查找Values对象。
如果定义了块,那么在块中可以通过$.Values
找到Values对象。
变量定义格式示例:{{- $name := .Release.Name -}}
在模板顶层定义变量,变量的作用域会是整个模板;在块中定义变量,变量的作用域只在块内。
4.3. 变量类型
Helm 模板语言是用强类型Go编程语言实现的。 因此,模板中的变量是 有类型的。大多数情况下,变量将作为以下类型之一显示:
- string: 文本字符串
- bool: true 或 false
- int: 整型值(包含8位,16位,32位,和64有符号和无符号整数)
- float64: 64位浮点数(也有8位,16位,32位类型)
- 字节切片([]byte),一般用于保存(可能的)二进制数据
- struct: 有属性和方法的对象
- 上述某种类型的切片(索引列表)
- 字符串键map (
map[string]interface{}
) 值是上述某种类型
Go里面有很多其他类型,有时你需要在模板里转换。调试对象类型最简便的方式是在模板中传递给printf “%t”,这样会打印类型。 也可以使用 typeOf 和 kindOf 函数。
4.4. 命名模板
命名模板(也被称作一个 部分 或一个 子模板)名称是全局的。
一个常见的命名惯例是用chart名称作为模板前缀。使用特定chart名称作为前缀可以避免可能因为 两个不同chart使用了相同名称的模板而引起的冲突。
用define和template声明和使用模板。
按照惯例,define声明的命名模板一般放在_helpers.tpl
文件中。因为是模板名称是全局的,因此可以被.yaml
文件引用。
template渲染时,一般传入.
作为命名模板可以访问的范围。
include是template的替代,只是为了更好地处理YAML文档的输出格式,方便缩进。
4.5. NOTES.txt文件
在helm install 或 helm upgrade命令的最后,Helm会打印出对用户有用的信息。 使用模板可以高度自定义这部分信息。
要在chart添加安装说明,只需创建 templates/NOTES.txt 文件即可。该文件是纯文本,但会像模板一样处理, 所有正常的模板函数和对象都是可用的。
4.6. .helmignore 文件
.helmignore
文件用来指定你不想包含在你的helm chart中的文件。
如果该文件存在,helm package 命令会在打包应用时忽略所有在 .helmignore
文件中匹配的文件。
一些值得注意的和.gitignore不同之处:
- 不支持
**
语法。 - globbing库是Go的 ‘filepath.Match’,不是fnmatch(3)
- 末尾空格总会被忽略(不支持转义序列)
- 不支持
!
作为特殊的引导序列 - 默认不会排除自身,需要显式添加 .helmignore
4.7. YAML技术
参考文档:《YAML语言》
5. Chart开发技巧
5.1. 模板方法
Helm使用Go模板,同时增加了两个特殊模板方法:include
和required
。
include方法允许你引入另一个模板,并将结果传递给其他模板方法。
比如,这个模板片段包含了一个叫mytpl的模板,然后将其转成小写,并使用双引号括起来。
1 | value: {{ include "mytpl" . | lower | quote }} |
required方法可以让你声明模板渲染所需的特定值。如果这个值是空的,模板渲染会出错并打印用户提交的错误信息。
下面这个required方法的例子声明了一个.Values.who需要的条目,并且当这个条目不存在时会打印错误信息:
1 | value: {{ required "A valid .Values.who entry required!" .Values.who }} |
tpl方法允许开发者在模板中使用字符串作为模板。将模板字符串作为值传给chart或渲染额外的配置文件时会很有用。
语法: {{ tpl TEMPLATE_STRING VALUES }}
5.2. 使用Partials和模板引用
有时你想在chart中创建可以重复利用的部分,不管是块还是局部模板。通常将这些文件保存在自己的文件中会更干净。
在 templates/
目录中,任何以下划线(_
)开始的文件不希望输出到Kubernetes清单文件中。
因此按照惯例,辅助模板和局部模板会被放在_helpers.tpl
文件中。
5.3. 创建镜像拉取密钥
1、定义 values.yaml
1 | imageCredentials: |
2、定义辅助模板(写在 _helpers.tpl
文件里)
1 | {{- define "imagePullSecret" }} |
3、模板中使用辅助模板
1 | apiVersion: v1 |
5.4. YAML是JSON的超集
根据YAML规范,YAML是JSON的超集。这意味着任意的合法JSON结构在YAML中应该是合法的。
这有个优势:有时候模板开发者会发现使用类JSON语法更容易表达数据结构而不是处理YAML的空白敏感度。
作为最佳实践,模板应遵循类YAML语法 除非 JSON语法大大降低了格式问题的风险。
5.5. 构建复杂Chart
在CNCF的 Artifact Hub 中的很多chart是创建更先进应用的“组成部分”。但是chart可能被用于创建大规模应用实例。 在这种场景中,一个总的chart会有很多子chart,每一个是整体功能的一部分。
当前从离散组件组成一个复杂应用的最佳实践是创建一个顶层总体chart构建全局配置,然后使用charts子目录嵌入每个组件。
6. 最佳实践
6.1. 一般惯例
6.1.1. chart名称
chart名称必须是小写字母和数字。单词之间可以使用横杠分隔(-)。
6.1.2. 版本号
Helm尽可能使用 SemVer 2来表示版本号。(注意Docker镜像的tag不一定遵循SemVer, 因此被认为是一个不幸的例外规则。)
当SemVer版本存储在Kubernetes标签中时,我们通常把+字符改成_,因为标签不允许使用+作为值进行签名。
6.1.3. 格式化YAML
YAML 文件应该按照 双空格 缩进(绝不要使用tab键)。
6.1.4. Helm 和 Chart的用法
以下是几个 Helm 和 helm 的惯用方法。
- Helm 是指整个项目
- helm 是指客户端命令
- chart 不是专有名词,不需要首字母大写
- Chart.yaml 需要首字母大写,因为文件名大小写敏感
若有疑问,使用 Helm (‘H’大写)。
6.2. Values
6.2.1. 命名规范
变量名称以小写字母开头,单词按驼峰区分
6.2.2. 扁平或嵌套的Value
YAML是一种灵活格式,值可以嵌套得很深,也可以是扁平的。
大多数场景中,扁平的优于嵌套的。因为对模板开发者和用户来说更加简单。
6.2.3. 类型清楚
YAML的类型强制规则有时候是很反常的。比如,foo: false 和 foo: “false” 是不一样的。大整型数如:foo: 12345678 有时会被转换成科学计数法。
避免类型强制规则错误最简单的方式是字符串明确定义,其他都是不明确的。或者,简单来讲, 给所有字符串打引号。
通常,为了避免整数转换问题,将整型存储为字符串更好,并用 {{ int $value }}
在模板中将字符串转回整型。
在大多数场景中,显式的类型标记更好,所以 foo: !!string 1234
会将1234作为字符串对待。 但是,YAML解析器会消耗标记,因此类型数据在一次解析后会丢失。
6.2.4. 考虑用户使用value
有三种value来源:
- chart的values.yaml文件
- 由
helm install -f
提供的values文件 - 在执行
helm install
时传递给–set 或 –set-string 参数的values
当设计values的结构时,记得你的chart用户可能会通过-f 参数或–set选项覆盖他们。
由于–set在表现上更有限,编写你values.yaml文件的第一指导原则是确保它容易被–set覆盖。因此使用map构建values文件更好。
6.2.5. 给values.yaml写文档
values.yaml中每个定义的属性都应该文档化。文档字符串应该以它要描述的属性开头,并至少给出一句描述。
6.3. 模板
6.3.1. templates 结构
templates/
目录结构应该如下:
- 如果生成YAML输出,模板文件应该有扩展名.yaml。 扩展名是.tpl可用于生成非格式化内容的模板文件。
- 模板文件名称应该使用横杠符号(my-example-configmap.yaml),不用驼峰记法。
- 每个资源的定义应该在它自己的模板文件中。
- 模板文件的名称应该反映名称中的资源类型。比如:foo-pod.yaml, bar-svc.yaml
6.3.2. 定义模板的名称
定义的模板(在{{ define }}
命令中定义的模板)是可全局访问的。这就意味着chart和所有的子chart都可以访问用{{ define }}
创建的所有模板。
因此, 所有定义的模板名称应该被命名空间化。
正确的:
1 | {{- define "nginx.fullname" }} |
不正确的:
1 | {{- define "fullname" -}} |
强烈建议通过helm create
命令创建新chart,因为模板名称是根据此最佳实践自动定义的。
6.3.3. 格式化模板
模板应该使用两个 空格 缩进(永远不要用tab)。
模板命令的大括号前后应该使用空格。
正确的:
1 | {{ .foo }} |
不正确的:
1 | {{.foo}} |
6.3.4. 生成模板中的空格
最好在生成的模板中将空格量保持在最小值。尤其是大量的空行不应该相邻出现。但偶尔有空行(尤其在逻辑块之间)是没问题的。
6.3.5. 注释
YAML和Helm模板都有注释标记符。
YAML注释:
1 | # This is a comment |
模板注释:
1 | {{- /* |
6.3.6. 在模板和模板输出中使用JSON
YAML是JSON的超集。在某些情况下,使用JSON语法比其他YAML表示更具可读性。
比如,这个YAML更接近表示列表的普通YAML方法:
1 | arguments: |
但是折叠成JSON列表样式时会更易阅读:
1 | arguments: ["--dirname", "/foo"] |
使用JSON可以很好地提高易读性。然而,JSON语法不应用于表示更复杂的结构。
6.4. 标签和注释
建议使用 helm.sh/chart: NAME-VERSION
作为标签,以便操作员可以找到特定chart的所有实例。
如果元数据项不是用于查询,就应该设置为注释。
标准标签参考文档:标签和注释
6.5. Pod和Pod模板
Pod指的是Pod,Pod模板指的是Deployment、ReplicationController、ReplicaSet、DaemonSet、StatefulSet等。
容器镜像应该使用固定的tag或镜像SHA。不应该使用latest, head, canary等标签或其他被设计为“浮动的”标签。
所有的Pod模板部分应该指定一个selector。比如:
1 | selector: |
6.6. 基于角色的访问控制
RBAC 资源有:
- ServiceAccount (namespaced)
- Role (namespaced)
- ClusterRole
- RoleBinding (namespaced)
- ClusterRoleBinding
RBAC和服务账户配置应该使用独立的key。它们是独立的内容。在YAML中将这两个概念分开可以消除歧义使其更加清晰。
1 | rbac: |
rbac.create 应该是布尔值,用于控制RBAC资源是否被创建。默认是 true。用户想自己管理RBAC访问控制时可以设置为false (示例如下)。
serviceAccount.name 要设置为由chart创建的访问控制资源的ServiceAccount的名称。 如果serviceAccount.create是true,则使用该名称的ServiceAccount会被创建。如果没有设置名称, 则会使用fullname模板生成一个名称。如果serviceAccount.create是false,则不会被创建,但仍然会与相同的资源关联, 以便后续手动创建的引用它的RBAC资源可以正常工作。如果serviceAccount.create是false且没有指定名称, 会使用默认的ServiceAccount。
1 | {{/* |
7. 调试
7.1. 语法检查
1 | helm lint |
7.2. 测试本地渲染
1 | helm template --debug |
7.3. 测试服务器渲染
1 | helm install --dry-run --debug |
7.4. 忽略检查测试服务器渲染
1 | helm install --dry-run --disable-openapi-validation |
7.5. 查看安装在服务上的模板
1 | helm get manifest |