一、Helm 简介
① 什么是 Helm?
- 我们可以将 Helm 看作 Kubernetes 下的 apt-get/yum,Helm 是 kubernetes 的包管理器,Helm 仓库里面只有配置清单文件,而没有镜像,镜像还是由镜像仓库来提供,比如 hub.docker.com、私有仓库。
- 想了解更多 Helm 的信息,请参考:官方文档。
② Helm 架构
③ Helm 安装
- 可以到 Helm的下载地址 下载安装,Helm 安装的步骤如下:
# 下载包
$ wget https://get.helm.sh/helm-v3.9.4-linux-amd64.tar.gz
# 解压压缩包
$ tar -xf helm-v3.9.4-linux-amd64.tar.gz
# 制作软连接
$ ln -s /opt/helm/linux-amd64/helm /usr/local/bin/helm
# 验证
$ helm version
$ helm help
④ Helm 组件及相关术语
- Helm——Helm 是一个命令行下的客户端工具,主要用于 Kubernetes 应用程序 Chart 的创建、打包、发布以及创建和管理本地和远程的 Chart 仓库;
- Chart——Chart 代表着 Helm 包,它包含在 Kubernetes 集群内部运行应用程序,工具或服务所需的所有资源定义,可以把它看作是 Homebrew formula、Apt dpkg 或 Yum RPM 在 Kubernetes 中的等价物;
- Release——Release 是运行在 Kubernetes 集群中的 chart 的实例,一个 chart 通常可以在同一个集群中安装多次,每一次安装都会创建一个新的 release;
- Repoistory——Repository(仓库) 是用来存放和共享 charts 的地方,它就像 Perl 的 CPAN 档案库网络或是 Fedora 的软件包仓库,只不过它是供 Kubernetes 包所使用的。
二、Helm Chart
① Chart 目录结构
- 可以通过 helm create 命令创建一个新的 chart 包:
helm create nginx
tree nginx
nginx/
├── charts #依赖其他包的charts文件
├── Chart.yaml # 该chart的描述文件,包括ico地址,版本信息等
├── templates # #存放k8s模板文件目录
│ ├── deployment.yaml # 创建k8s资源的yaml 模板
│ ├── _helpers.tpl # 下划线开头的文件,可以被其他模板引用
│ ├── hpa.yaml # 弹性扩缩容,配置服务资源CPU 内存
│ ├── ingress.yaml # ingress 配合service域名访问的配置
│ ├── NOTES.txt # 说明文件,helm install之后展示给用户看的内容
│ ├── serviceaccount.yaml # 服务账号配置
│ ├── service.yaml # kubernetes Serivce yaml 模板
│ └── tests # 测试模块
│ └── test-connection.yaml
└── values.yaml # 给模板文件使用的变量
wordpress/
...
LICENSE # 可选: 包含chart许可证的纯文本文件
README.md # 可选: 可读的README文件
values.schema.json # 可选: 一个使用JSON结构的values.yaml文件
charts/ # 包含chart依赖的其他chart
crds/ # 自定义资源的定义
...
② Chart.yaml 文件
apiVersion: chart API 版本 (必需)
name: chart名称 (必需)
version: chart 版本,语义化2 版本(必需)
kubeVersion: 兼容Kubernetes版本的语义化版本(可选)
description: 一句话对这个项目的描述(可选)
type: chart类型 (可选)
keywords:
- 关于项目的一组关键字(可选)
home: 项目home页面的URL (可选)
sources:
- 项目源码的URL列表(可选)
dependencies: # chart 必要条件列表 (可选)
- name: chart名称 (nginx)
version: chart版本 ("1.2.3")
repository: (可选)仓库URL ("https://example.com/charts") 或别名 ("@repo-name")
condition: (可选) 解析为布尔值的yaml路径,用于启用/禁用chart (e.g. subchart1.enabled)
tags: # (可选)
- 用于一次启用/禁用 一组chart的tag
import-values: # (可选)
- ImportValue 保存源值到导入父键的映射。每项可以是字符串或者一对子/父列表项
alias: (可选) chart中使用的别名。当你要多次添加相同的chart时会很有用
maintainers: # (可选)
- name: 维护者名字 (每个维护者都需要)
email: 维护者邮箱 (每个维护者可选)
url: 维护者URL (每个维护者可选)
icon: 用做icon的SVG或PNG图片URL (可选)
appVersion: 包含的应用版本(可选),不需要是语义化,建议使用引号
deprecated: 不被推荐的chart (可选,布尔值)
annotations:
example: 按名称输入的批注列表 (可选)
-
- 从 v3.3.2,不再允许额外的字段,推荐的方法是在 annotations 中添加自定义元数据。每个 chart 都必须有个版本号(version),版本必须遵循“语义化版本 2”标准,不像经典 Helm, Helm v2 以及后续版本会使用版本号作为发布标记,仓库中的包通过名称加版本号标识。比如 nginx chart 的版本字段 version: 1.2.3 按照名称被设置为:
nginx-1.2.3.tgz
- 注:appVersion 字段与 version 字段并不相关,这是指定应用版本的一种方式。比如,这个 drupal chart 可能有一个 appVersion: “8.2.1”,表示包含在 chart(默认)的 Drupal 的版本是 8.2.1。
③ Chart 依赖管理(dependencies)
- 当前 chart 依赖的其他 chart 会在 dependencies 字段定义为一个列表:
dependencies:
- name: apache
version: 1.2.3
repository: https://example.com/charts
- name: mysql
version: 3.2.1
repository: https://another.example.com/charts
- 说明:
-
-
- version 字段是需要的 chart 的版本;
-
- repository 字段是 chart 仓库的完整 URL,必须使用 helm repo add 在本地添加仓库;
-
- 示例演示:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm pull bitnami/wordpress
tar -xf wordpress
cat wordpress/Chart.yaml
- 一旦定义好依赖,运行 helm dependency update 就会使用依赖文件下载所有指定的 chart 到 charts/ 目录:
helm dependency update ./wordpress
- 当 helm dependency update 拉取 chart 时,会在 charts/目录中形成一个 chart 包,因此对于上面的示例,会在 chart 目录中期望看到以下文件:
wordpress/charts/
├── common
├── common-2.0.1.tgz
├── mariadb
├── mariadb-11.2.2.tgz
├── memcached
└── memcached-6.2.3.tgz
- 依赖中的 tag 和条件字段:除了上面的其他字段外,每个需求项可以包含可选字段 tags 和 condition,所有的 chart 会默认加载。如果存在 tags 或者 condition 字段,它们将被评估并用于控制它们应用的 chart 的加载。
-
- Condition:条件字段 field 包含一个或多个 YAML 路径(用逗号分隔),如果这个路径在上层 values 中已存在并解析为布尔值,chart 会基于布尔值启用或禁用 chart,只会使用列表中找到的第一个有效路径,如果路径为未找到则条件无效;
-
- Tags:tag 字段是与 chart 关联的 YAML 格式的标签列表,在顶层 value 中,通过指定 tag 和布尔值,可以启用或禁用所有的带 tag 的 chart。
# parentchart/Chart.yaml
dependencies:
- name: subchart1
repository: http://localhost:10191
version: 0.1.0
condition: subchart1.enabled, global.subchart1.enabled
tags:
- front-end
- subchart1
- name: subchart2
repository: http://localhost:10191
version: 0.1.0
condition: subchart2.enabled,global.subchart2.enabled
tags:
- back-end
- subchart2
# parentchart/values.yaml
subchart1:
enabled: true
tags:
front-end: false
back-end: true
- 在上面的示例中,所有带 front-end tag 的 chart 都会被禁用,但只要上层的 value 中 subchart1.enabled 路径被设置为 ‘true’,该条件会覆盖 front-end 标签且 subchart1 会被启用。
- 一旦 subchart2 使用了 back-end 标签并被设置为了 true,subchart2 就会被启用,也要注意尽管 subchart2 指定了一个条件字段, 但是上层 value 没有相应的路径和 value,因此这个条件不会生效。
- –set 参数可以用来设置标签和条件值:
helm install --set tags.front-end=true --set subchart2.enabled=false
- 标签和条件的解析:
-
- 条件 (当设置在 value 中时)总是会覆盖标签 第一个 chart 条件路径存在时会忽略后面的路径;
-
- 标签被定义为 '如果任意的 chart 标签是 true,chart 就可以启用;
-
-
④ 通过依赖导入子 Value
- 在某些情况下,允许子 chart 的值作为公共默认传递到父 chart 中是值得的,使用 exports格式的额外好处是它可是将来的工具可以自检用户可设置的值。
- 被导入的包含值的 key 可以在父 chart 的 dependencies 中的 import-values字段以 YAML 列表形式指定,列表中的每一项是从子 chart 中 exports 字段导入的 key。
- 导入 exports key 中未包含的值,使用 子-父格式。
- 使用导出格式:如果子 chart 的 values.yaml 文件中在根节点包含了 exports 字段,它的内容可以通过指定的可以被直接导入到父 chart 的 value 中, 如下所示:
# parent's Chart.yaml file
dependencies:
- name: subchart
repository: http://localhost:10191
version: 0.1.0
import-values:
- data
# child's values.yaml file
exports:
data:
myint: 99
- 只要再导入列表中指定了键 data,Helm 就会在子 chart 的 exports 字段查找 data 键并导入它的内容。
- 最终的父级 value 会包含我们的导出字段,父级键 data 没有包含在父级最终的 value 中,如果想指定这个父级键,要使用’子-父’ 格式:
# parent's values
myint: 99
- 如下所示的示例中的 import-values 指示 Helm 去拿到能再 child:路径中找到的任何值,并拷贝到 parent:的指定路径:
# parent's Chart.yaml file
dependencies:
- name: subchart1
repository: http://localhost:10191
version: 0.1.0
...
import-values:
- child: default.data
parent: myimports
- 在 subchart1 里面找到的 default.data 的值会被导入到父 chart 的 myimports 键中,细节如下:
# parent's values.yaml file
myimports:
myint: 0
mybool: false
mystring: "helm rocks!"
# subchart1's values.yaml file
default:
data:
myint: 999
mybool: true
三、Templates and Values
① Templates and Values 简介
- Helm Chart 模板是按照 Go 模板语言书写,增加了 50 个左右的附加模板函数来自 Sprig 库和一些其它指定的函数。
- 所有模板文件存储在 chart 的 templates/ 文件夹,当 Helm 渲染 chart 时,它会通过模板引擎遍历目录中的每个文件。
- 模板的 Value 通过两种方式提供:
-
- Chart 开发者可以在 chart 中提供一个命名为 values.yaml 的文件,这个文件包含了默认值;
-
- Chart 用户可以提供一个包含了 value 的 YAML 文件,可以在命令行使用 helm install 命令时通过 -f 指定 value 文件。
- 模板示例:
apiVersion: v1
kind: ReplicationController
metadata:
name: deis-database
namespace: deis
labels:
app.kubernetes.io/managed-by: deis
spec:
replicas: 1
selector:
app.kubernetes.io/name: deis-database
template:
metadata:
labels:
app.kubernetes.io/name: deis-database
spec:
serviceAccount: deis-database
containers:
- name: deis-database
image: {{ .Values.imageRegistry }}/postgres:{{ .Values.dockerTag }}
imagePullPolicy: {{ .Values.pullPolicy }}
ports:
- containerPort: 5432
env:
- name: DATABASE_STORAGE
value: {{ default "minio" .Values.storage }}
- charts 是一个 Kubernetes 副本控制器的模板,可以使用下面四种模板值(一般被定义在 values.yaml 文件):
-
- imageRegistry: Docker 镜像的源注册表;
-
- dockerTag: Docker 镜像的 tag;
-
- pullPolicy: Kubernetes 的拉取策略;
-
- storage: 后台存储,默认设置为"minio"。
② 预定义的 Values
- Values 通过模板中.Values 对象可访问的 values.yaml 文件(或者通过 --set 参数)提供, 但可以模板中访问其他预定义的数据片段。以下值是预定义的,对每个模板都有效,并且可以被覆盖,和所有值一样,名称区分大小写:
-
- Release.Name: 版本名称(非 chart 的);
-
- Release.Namespace: 发布的 chart 版本的命名空间;
-
- Release.Service: 组织版本的服务;
-
- Release.IsUpgrade: 如果当前操作是升级或回滚,设置为 true;
-
- Release.IsInstall: 如果当前操作是安装,设置为 true;
-
- Chart: Chart.yaml 的内容,因此 chart 的版本可以从 Chart.Version 获得, 并且维护者在 Chart.Maintainers 里;
-
- Files: chart 中的包含了非特殊文件的类图对象,这将不允许您访问模板, 但是可以访问现有的其他文件(除非被 .helmignore 排除在外),使用{{ index .Files “file.name” }}可以访问文件或者使用{{.Files.Get name }}功能,也可以使用{{ .Files.GetBytes }}作为[]byte 访问文件内容;
-
- Capabilities: 包含了 Kubernetes 版本信息的类图对象。({{ .Capabilities.KubeVersion }}) 和支持的 Kubernetes API 版本({{ .Capabilities.APIVersions.Has “batch/v1” }});
- 考虑到前面部分的模板,values.yaml文件提供的必要值如下:
imageRegistry: "quay.io/deis"
dockerTag: "latest"
pullPolicy: "Always"
storage: "s3"
- values 文件被定义为 YAML 格式,chart 会包含一个默认的 values.yaml 文件,Helm 安装命令允许用户使用附加的 YAML values 覆盖这个 values:
helm install --generate-name --values=myvals.yaml wordpress
③ 范围,依赖和值
- Values 文件可以声明顶级 chart 的值,以及charts/目录中包含的其他任意 chart。或者换个说法,values 文件可以为 chart 及其任何依赖项提供值。比如,上面示范的 WordPress chart 同时有 mysql 和 apache 作为依赖,values 文件可以为以下所有这些组件提供依赖:
title: "My WordPress Site" # Sent to the WordPress template
mysql:
max_connections: 100 # Sent to MySQL
password: "secret"
apache:
port: 8080 # Passed to Apache
- 更高阶的 chart 可以访问下面定义的所有变量,因此 WordPress chart 可以用 Values.mysql.password 访问 MySQL 密码。但是低阶的 chart 不能访问父级 chart,所以 MySQL 无法访问 title 属性,同样也无法访问 apache.port。
④ 全局 Values
- 从 2.0.0-Alpha.2 开始,Helm 支持特殊的 global 值,设想一下前面的示例中的修改版本:
title: "My WordPress Site" # Sent to the WordPress template
global:
app: MyWordPress
mysql:
max_connections: 100 # Sent to MySQL
password: "secret"
apache:
port: 8080 # Passed to Apache
- 添加了 global 部分和一个值 app: MyWordPress,这个值以 .Values.global.app 在所有 chart 中有效。比如,mysql 模板可以以{{.Values.global.app}}访问 app,同样 apache chart 也可以访问。实际上上面的 values 文件会重新生成为这样:
title: "My WordPress Site" # Sent to the WordPress template
global:
app: MyWordPress
mysql:
global:
app: MyWordPress
max_connections: 100 # Sent to MySQL
password: "secret"
apache:
global:
app: MyWordPress
port: 8080 # Passed to Apache
四、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
五、Helm 安装 Chart 包的三种方式
- Helm 自带一个强大的搜索命令,可以用来从两种来源中进行搜索:
-
- helm search hub 从 Artifact Hub https://artifacthub.io/ 中查找并列出 helm charts,Artifact Hub 中存放了大量不同的仓库;
-
- helm search repo 从添加(使用 helm repo add)到本地 helm 客户端中的仓库中进行查找,该命令基于本地数据进行搜索,无需连接互联网。
# 添加bitnami仓库源
helm repo add bitnami https://charts.bitnami.com/bitnami
# 从bitnami源查找所有chart包,不指定具体源的话,会查找本地添加的所有源地址的所有chart包
helm search repo bitnami
① values 传参
- 安装过程中有两种方式传递配置数据:
-
- –values (或 -f):使用 YAML 文件覆盖配置,可以指定多次,优先使用最右边的文件;
-
- 如果同时使用两种方式,则 --set 中的值会被合并到 --values 中,但是 --set 中的值优先级更高。在–set 中覆盖的内容会被被保存在 ConfigMap 中,可以通过 helm get values 来查看指定 release 中 --set 设置的值,也可以通过运行 helm upgrade 并指定 --reset-values 字段来清除 --set 中设置的值。示例如下:
echo '{mariadb.auth.database: user0db, mariadb.auth.username: user0}' > values.yaml
helm install -f values.yaml bitnami/wordpress --generate-name
helm install mysql bitnami/mysql
helm list
# 先删除
helm uninstall mysql
# 拉包到本地
helm pull bitnami/mysql
# 不解压直接安装
helm install mysql ./mysql-9.3.1.tgz
helm list
# 拉包到本地
helm pull bitnami/mysql
# 解压安装
tar -xf mysql-9.3.1.tgz
# 开始安装
helm install mysql ./mysql \
--namespace=mysql \
--create-namespace \
--set image.registry=myharbor.com \
--set image.repository=bigdata/mysql \
--set image.tag=8.0.30 \
--set primary.service.type=NodePort \
--set service.nodePorts.mysql=30306
# 查看在运行的Release
helm list
# 卸载
helm uninstall mysql -n mysql
六、Helm 基础语法
① 变量
- 模板(templates/)中的变量都放在{{}}中,比如:{{ .Values.images }} 表示 Values 对象下的 images 字段。Values 来源于values.yaml文件或者 -f 指定的 yaml 文件,或者 --set 设置的变量。
- 使用-删除空格和换行符,要想删除那行其他的空格和换行符可以用{{-或者-}},一个是删除左边的空格和换行符,一个是删除右边的空格和换行符。
② 内置对象
- Release:Release 对象描述了版本发布本身,包含了以下对象:
-
-
- Release.Namespace:版本中包含的命名空间(如果 manifest 没有覆盖的话);
-
- Release.IsUpgrade:如果当前操作是升级或回滚的话,该值将被设置为 true
-
- Release.IsInstall:如果当前操作是安装的话,该值将被设置为 true
-
- Release.Revision:此次修订的版本号,安装时是 1,每次升级或回滚都会自增;
-
- Release.Service:该 service 用来渲染当前模板,Helm 里始终 Helm。
- Values:Values 对象是从values.yaml文件和用户提供的文件传进模板的,默认为空;
- Chart:Chart.yaml 文件内容,Chart.yaml 里的所有数据在这里都可以可访问的,比如 {{ .Chart.Name }}-{{ .Chart.Version }} 会打印出 mychart-0.1.0;
- Template:包含当前被执行的当前模板信息:
-
- Template.Name: 当前模板的命名空间文件路径 (e.g. mychart/templates/mytemplate.yaml);
-
- Template.BasePath: 当前 chart 模板目录的路径 (e.g. mychart/templates)。
③ 常用的内置函数
- quote and squote:
-
- 该函数将值转换成字符串用双引号(quote)或者**单引号(squote)**括起来。示例如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | quote }}
food: {{ .Values.favorite.food | upper | quote }}
-
- 倒置命令是模板中的常见做法,可以经常看到 .val | quote 而不是 quote .val,实际上两种操作都是可以的。
- default:
-
- 这个函数允许在模板中指定一个默认值,以防这个值被忽略:
# 如果.Values.favorite.drink是非空值,则使用它,否则会返回tea。
drink: {{ .Values.favorite.drink | default "tea" | quote }}
# 还可以这样写,如果.Bar是非空值,则使用它,否则会返回foo。
default "foo" .Bar
整型: 0
字符串: ""
列表: []
字典: {}
布尔: false
以及所有的nil (或 null)
- print:
-
- 返回各部分组合的字符串,非字符串类型会被转换成字符串:
print "Matt has " .Dogs " dogs"
-
- 当相邻两个参数不是字符串时会在它们之间添加一个空格。
- println:和 print 效果一样,但会在末尾新添加一行。
- printf:
-
printf "%s has %d dogs." .Name .NumberDogs
{{- printf "%d" (.Values.externalCache.port | int ) -}}
{{- printf "%s" .Values.existingSecret -}}
{{- printf "%v" .context.Values.redis.enabled -}}
# %s 字符串占位符,未解析的二进制字符串或切片
# %d 数字占位符,十进制
# %v 默认格式的值,当打印字典时,加号参数(%+v)可以添加字段名称
trim " hello "
trimAll "$" "$5.00"
lower "HELLO"
upper "hello"
title "hello world"
start (int)
end (int)
string (string)
substr 0 5 "hello world"
abbrev 5 "hello world"
# 第一个参数:最大长度
# 第二个参数:字符串
-
- contains:测试字符串是否包含在另一个字符串中:
contains "cat" "catch"
cat "hello" "beautiful" "world"
-
- 上述结果为:hello beautiful world。
- indent:
-
- indent 以指定长度缩进给定字符串所在行,在对齐多行字符串时很有用:
indent 4 $lots_of_text
-
- nindent:
-
- nindent 函数和 indent 函数一样,但可以在字符串开头添加新行:
nindent 4 $lots_of_text
-
- 上述结果会在字符串所在行缩进 4 个字符,并且在开头新添加一行。
- replace:
-
# 下面两行等价
replace " " "-" "I Am Henry VIII"
"I Am Henry VIII" | replace " " "-"
# 参数1:待替换字符串
# 参数2:要替换字符串
# 参数3:源字符串
-
- date:
-
- date 函数格式化日期,日期格式化为 YEAR-MONTH-DAY:
now | date "2006-01-02"
④ 类型转换函数
- Helm 提供了以下类型转换函数:
-
-
-
-
-
- toDecimal: 将 unix 八进制转换成 int64;
-
-
- toStrings: 将列表、切片或数组转换成字符串列表;
-
- toJson (mustToJson): 将列表、切片、数组、字典或对象转换成 JSON;
-
- toPrettyJson (mustToPrettyJson): 将列表、切片、数组、字典或对象转换成格式化 JSON;
-
- toRawJson (mustToRawJson): 将列表、切片、数组、字典或对象转换成 HTML 字符未转义的 JSON。
⑤ 正则表达式(Regular Expressions)
- Helm 包含以下正则表达式函数:
-
- -regexFindAll(mustRegexFindAll)
- -regexMatch (mustRegexMatch)
- -regexReplaceAll (mustRegexReplaceAll)
- -regexReplaceAllLiteral(mustRegexReplaceAllLiteral)
- -regexSplit (mustRegexSplit)
⑥ 编码和解码函数
- Helm 有以下编码和解码函数:
- -b64enc/b64dec: 编码或解码 Base64
- -b32enc/b32dec: 编码或解码 Base32
⑦ Dictionaries and Dict Functions
- Helm 提供了一个 key/value 存储类型称为 dict("dictionary"的简称,Python 中也有),dict 是无序类型,字典的 key 必须是字符串。但值可以是任意类型,甚至是另一个 dict 或 list。
- 创建字典(dict),如下是创建三个键值对的字典:
$myDict := dict "name1" "value1" "name2" "value2" "name3" "value 3"
- 获取值(get):
-
- 给定一个映射和一个键,从映射中获取值,结果为 “value1”:
get $myDict "name1"
-
- 注意如果没有找到,会简单返回"",不会生成 error。
- 添加键值对(set):
-
$_ := set $myDict "name4" "value4"
-
- 注意 set 返回字典(Go 模板函数的一个要求),因此可能需要像上面那样使用 $_ 赋值来获取值。
- 删除(unset):
-
- 给定一个映射和 key,从映射中删除这个 key:
$_ := unset $myDict "name4"
-
- 判断 key(hasKey):
-
- hasKey 函数会在给定字典中包含了给定 key 时返回 true:
hasKey $myDict "name1"
-
- pluck:
-
- pluck 函数给定一个键和多个映射,并获得所有匹配项的列表:
pluck "name1" $myDict $myOtherDict
-
- 上述会返回的 list 包含了每个找到的值([value1 otherValue1])。
- 合并 dict(merge, mustMerge):将两个或多个字典合并为一个, 目标字典优先:
$newdict := merge $dest $source1 $source2
- 获取所有 keys:
-
- keys 函数会返回一个或多个 dict 类型中所有的 key 的 list,由于字典是无序的,key 不会有可预料的顺序,可以使用 sortAlpha 存储:
keys $myDict | sortAlpha
-
- 当提供了多个词典时,key 会被串联起来,使用 uniq 函数和 sortAlpha 获取一个唯一有序的键列表:
keys $myDict $myOtherDict | uniq | sortAlpha
- 获取所有 values:
-
- values 函数类似于 keys,返回一个新的 list 包含源字典中所有的 value(只支持一个字典)。
$vals := values $myDict
-
- 上述结果为:list[“value1”, “value2”, “value 3”]。注意 values 不能保证结果的顺序;如果需要顺序,可以使用 sortAlpha。
⑧ Lists and List Functions
- Helm 提供了一个简单的 list 类型,包含任意顺序的列表。类似于数组或切片,但列表是被设计用于不可变数据类型。
- 创建列表:
$myList := list 1 2 3 4 5
-
- 获取列表第一项(first, mustFirst):
-
first $myList
# 返回 1
-
- first 有问题时会出错,mustFirst 有问题时会向模板引擎返回错误。
- 获取列表的尾部内容(rest, mustRest):
-
- 获取列表的尾部内容(除了第一项外的所有内容),使用 rest:
rest $myList
# 返回 [2 3 4 5]
-
- rest 有问题时会出错,mustRest 有问题时会向模板引擎返回错误。
- 获取列表的最后一项(last, mustLast):
-
last $myList
# 返回 5。这大致类似于反转列表然后调用first。
- 获取列表所有内容(initial, mustInitial):
-
initial $myList
# 返回 [1 2 3 4]
-
- initial 有问题时会出错,但是 mustInitial 有问题时会向模板引擎返回错误。
- 末尾添加元素(append, mustAppend):
-
$new = append $myList 6
-
- 上述语句会设置为 [123456],myList 会保持不变,
-
- append 有问题时会出错,但 mustAppend 有问题时会向模板引擎返回错误。
- 前面添加元素(prepend, mustPrepend):
-
prepend $myList 0
-
- 上述语句会生成 [0 1 2 3 4 5],$myList 会保持不变。
-
- prepend 有问题时会出错,但 mustPrepend 有问题时会向模板引擎返回错误。
- 多列表连接(concat):
-
concat $myList ( list 6 7 ) ( list 8 )
-
- 上述语句会生成 [1 2 3 4 5 6 7 8],$myList 会保持不变。
- 反转(reverse, mustReverse):
-
reverse $myList
-
-
- reverse 有问题时会出错,但 mustReverse 有问题时会向模板引擎返回错误。
- 去重(uniq, mustUniq):
-
list 1 1 1 2 | uniq
-
-
- uniq 有问题时会出错,但 mustUniq 有问题时会向模板引擎返回错误。
- 过滤(without, mustWithout):
-
without $myList 3
# 上述语句会生成 [1 2 4 5]
without $myList 1 3 5
# 这样会得到: [2 4]
-
- without 有问题时会出错,但 mustWithout 有问题时会向模板引擎返回错误。
- 判断元素是否存在(has, mustHas):
-
has 4 $myList
-
- 上述语句会返回 true, 但 has “hello” $myList 就会返回 false。
-
- has 有问题时会出错,但 mustHas 有问题时会向模板引擎返回错误。
- 删除空项(compact, mustCompact):
-
$list := list 1 "a" "foo" ""
$copy := compact $list
-
- compact 会返回一个移除了空值(比如, “”)的新列表。
-
- compact 有问题时会出错,但 mustCompact 有问题时会向模板引擎返回错误。
- index:
-
- 使用 index list [n]获取列表的第 n 个元素,使用 index list [n] [m] …获取多位列表元素:
index $myList 0 返回 1,同 myList[0]
index $myList 0 1 同 myList[0][1]
- 获取部分元素(slice, mustSlice):从列表中获取部分元素,使用 slice list [n] [m],等同于 list[n:m]:
-
- slice $myList 返回 [1 2 3 4 5],等同于 myList[:];
-
- slice $myList 3 返回 [4 5]等同于 myList[3:];
-
- slice $myList 1 3 返回 [2 3]等同于 myList[1:3];
-
- slice $myList 0 3 返回 [1 2 3]等同于 myList[:3]。
-
- slice 有问题时会出错,但 mustSlice 有问题时会向模板引擎返回错误。
- 构建一个整数列表(until):
-
until 5
-
- 上述语句会生成一个列表:[0, 1, 2, 3, 4]。
-
- 对循环语句很有用:range e := until 5。
- seq:
seq 5 => 1 2 3 4 5
seq -3 => 1 0 -1 -2 -3
seq 0 2 => 0 1 2
seq 2 -2 => 2 1 0 -1 -2
seq 0 2 10 => 0 2 4 6 8 10
seq 0 -2 -5 => 0 -2 -4
⑨ 数学函数(Math Functions)
- 求和(add):使用 add 求和,接受两个或多个输入。
- 自加 1(add1):自增加 1,使用 add1。
- 相减(sub):相减使用 sub。
- 除(div):整除使用 div。
- 取模(mod):取模使用 mod。
- 相乘(mul):相乘使用 mul,接受两个或多个输入。
- 获取最大值(max):返回一组整数中最大的整数。
- 获取最小值(min):返回一组数中最小的数。
- 获取长度(len):以整数返回参数的长度。
- Network Functions:Helm 提供了几个网络函数:
-
- getHostByName接收一个域名返回 IP 地址;
-
- getHostByName "www.google.com"会返回对应的 www.google.com 的地址。
⑩ 条件语句
eq: 等于(equal to)
ne: 不等于(not equal to)
lt: 小于(less than)
le: 小于等于(less than or equal to)
gt: 大于(greater than)
ge: 大于等于(greater than or equal to)
{{if 命令}}
…
{{else if 命令}}
…
{{else}}
…
{{end}}
布尔false
数字0
空字符串
nil (空或null)
空集合(map, slice, tuple, dict, array)
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 }}
⑪ 变更作用域 with
- 下一个控制结构是 with 操作,这个用来控制变量范围。回想一下,.是对当前作用域 的引用,因此 .Values 就是告诉模板在当前作用域查找 Values 对象。
- with 的语法与 if 语句类似:
{{ with PIPELINE }}
# restricted scope
{{ end }}
- 作用域可以被改变,with 允许特定对象设定当前作用域(.)。比如,已经在使用.Values.favorite,修改配置映射中的.的作用域指向.Values.favorite:
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 }}
- 这样会报错因为 Release.Name 不在.限定的作用域内。但是如果对调最后两行就是正常的, 因为在{{ end }}之后作用域被重置:
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
{{- end }}
release: {{ .Release.Name }}
- 或者可以使用 从父作用域中访问 R e l e a s e . N a m e 对象,当模板开始执行后 从父作用域中访问 Release.Name 对象,当模板开始执行后 从父作用域中访问Release.Name对象,当模板开始执行后会被映射到根作用域,且执行过程中不会更改。如下也可以正常工作:
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ $.Release.Name }}
{{- end }}
- 也可以在外边定义变量,遵循 $name 变量的格式且指定了一个特殊的赋值运算符::=,可以使用针对 Release.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 块开始之前,赋值 r e l n a m e : = . R e l e a s e . N a m e ,现在在 w i t h 块中, relname := .Release.Name,现在在 with 块中, relname:=.Release.Name,现在在with块中,relname 变量仍会执行版本名称。
⑫ rang 循环语句
- 很多编程语言支持使用 for 循环、foreach 循环,或者类似的方法机制。在 Helm 的模板语言中,在一个集合中迭代的方式是使用 range 操作符。
- 定义 values:
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
- 现在有了一个 pizzaToppings 列表(模板中称为切片),修改模板把这个列表打印到配置映射中:
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 }}
- 有时能在模板中快速创建列表然后迭代很有用,Helm 模板的 tuple 可以很容易实现该功能。在计算机科学中, 元组表示一个有固定大小的类似列表的集合,但可以是任意数据类型,这大致表达了 tuple 的用法:
sizes: |-
{{- range tuple "small" "medium" "large" }}
- {{ . }}
{{- end }}
上述模板会生成以下内容:
sizes: |-
- small
- medium
- large
⑬ 命名模板
- 此时需要越过模板,开始创建其它内容,该部分可以看到如何在一个文件中定义命名模板,并在其他地方使用。命名模板(有时称作一个“部分”或一个“子模板”)仅仅是在文件内部定义的模板,并使用了一个名字。
- 三种声明和管理模板的方法:define,template,和 block,在这部分将使用这三种操作并介绍一种特殊用途的 include方法,类似于 template 操作。
- 命名模板时要记住一个重要细节:模板名称是全局的,如果想声明两个相同名称的模板,哪个最后加载就使用哪个,因为在子 chart 中的模板和顶层模板一起编译,命名时要注意 chart 特定名称。
- 一个常见的命名惯例是用 chart 名称作为模板前缀:{{ define “mychart.labels” }},使用特定 chart 名称作为前缀可以避免可能因为两个不同 chart 使用了相同名称的模板而引起的冲突。
- 在编写模板细节之前,文件的命名惯例需要注意:
-
- templates/中的大多数文件被视为包含 Kubernetes 清单;
-
-
- 命名以下划线(_)开始的文件则假定没有包含清单内容,这些文件不会渲染为 Kubernetes 对象定义,但在其他 chart 模板中都可用。
- 这些文件用来存储局部和辅助对象,实际上当我们第一次创建 mychart 时,会看到一个名为 _helpers.tpl 的文件,这个文件是模板局部的默认位置。
- 用 define 和 template 声明和使用模板:
-
- define 操作允许我们在模板文件中创建一个命名模板,语法如下:
{{- define "MY.NAME" }}
# body of template here
{{- end }}
-
- 可以定义一个模板封装 Kubernetes 的标签:
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}
-
- 现在将模板嵌入到了已有的配置映射中,然后使用 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 }}
-
- 当模板引擎读取该文件时,它会存储 mychart.labels 的引用直到 template "mychart.labels"被调用,然后会按行渲染模板,因此结果类似这样:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: running-panda-configmap
labels:
generator: helm
date: 2022-09-04
data:
myvalue: "Hello World"
drink: "coffee"
food: "pizza"
-
- 按照惯例,Helm chart 将这些模板放置在局部文件中,一般是_helpers.tpl,把这个方法移到那里:
{{}}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}
- 设置模板范围:
-
- 在上面定义的模板中,没有使用任何对象,仅仅使用了方法,修改定义好的模板让其包含 chart 名称和版本号:
{{}}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}
{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}"
{{- 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" . }}
$ helm install --dry-run measly-whippet ./mychart
Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: [ValidationError(ConfigMap): unknown field "app_name" in io.k8s.api.core.v1.ConfigMap, ValidationError(ConfigMap): unknown field "app_version" in io.k8s.api.core.v1.ConfigMap]
-
- 要查看渲染了什么,可以用–disable-openapi-validation参数重新执行:helm install --dry-run --disable-openapi-validation measly-whippet ./mychart。输入不是想要的:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: measly-whippet-configmap
labels:
app_name: mychart
app_version: "0.1.0"
data:
myvalue: "Hello World"
drink: "coffee"
food: "pizza"
app_name: mychart
app_version: "0.1.0"
-
- 注意两处的 app_version 缩进都不对,这是为什么呢?因为被替换的模板中文本是左对齐的,由于template是一个行为,不是方法,无法将 template 调用的输出传给其他方法,数据只是简单地按行插入。为了处理这个问题,Helm 提供了一个include,可以将模板内容导入当前管道,然后传递给管道中的其他方法。如下所示,使用 indent 正确地缩进了 mychart.app 模板:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ include "mychart.app" . | indent 4 }}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end }}
{{ include "mychart.app" . | indent 2 }}
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: edgy-mole-configmap
labels:
app_name: mychart
app_version: "0.1.0"
data:
myvalue: "Hello World"
drink: "coffee"
food: "pizza"
app_name: mychart
app_version: "0.1.0"
-
- include 相较于使用 template,在 helm 中使用 include 被认为是更好的方式 只是为了更好地处理 YAML 文档的输出格式。
⑭ NOTES.txt 文件
- 在 helm install 或 helm upgrade 命令的最后,Helm 会打印出对用户有用的信息,使用模板可以高度自定义这部分信息。要在 chart 添加安装说明,只需创建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 all {{ .Release.Name }}
- 如果执行 helm install rude-cardinal ./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 all rude-cardinal
- 使用 NOTES.txt 这种方式是给用户提供关于如何使用新安装的 chart 细节信息的好方法。尽管并不是必需的,强烈建议创建一个NOTES.txt文件。
⑮ 模板调试
- 调试模板可能很棘手,因为渲染后的模板发送给了 Kubernetes API server,可能会以格式化以外的原因拒绝 YAML 文件。以下命令有助于调试:
-
- helm lint 是验证 chart 是否遵循最佳实践的首选工具;
-
- helm install --dry-run --debug 或 helm template --debug:这是让服务器渲染模板的好方法,然后返回生成的清单文件;
-
- helm get manifest: 这是查看安装在服务器上的模板的好方法。
- 当 YAML 文件解析失败,但想知道生成了什么,检索 YAML 一个简单的方式是注释掉模板中有问题的部分, 然后重新运行 helm install --dry-run --debug:
apiVersion: v2
# some: problem section
# {{ .Values.foo | quote }}
apiVersion: v2
# some: problem section
# "bar"