Helm一文全解

Helm简介

Helm 是 Kubernetes 的开源包管理器。它提供了一种查找、共享和使用为 Kubernetes 构建的软件的简便方法。该项目使用一种称为图表的打包格式,它是描述一组相关 Kubernetes 资源的文件集合。

Helm 于 2015 年在 Deis 创建,后来被微软收购。现在称为 Helm Classic 的是在当年 11 月的首届 KubeCon 上推出的。2016 年 1 月,Helm Classic 与谷歌的 Kubernetes 部署管理器合并到现在是 Helm 主要项目的存储库中。2018 年 6 月 加入 CNCF 。2018年年底推出了Helm Hub用于存储chart。2019年11月发布V3版本。

官网:https://helm.sh/zh/docs/

三大概念

  • Chart:代表着 Helm 包。它包含在 Kubernetes 集群内部运行应用程序,工具或服务所需的所有资源定义。你可以把它看作是 Homebrew formula,Apt dpkg,或 Yum RPM 在Kubernetes 中的等价物。

  • Repository:仓库,是用来存放和共享 charts 的地方。

  • Release:类似于版本号,Release 是运行在 Kubernetes 集群中的 chart 的实例。一个 chart 通常可以在同一个集群中安装多次。每一次安装都会创建一个新的 release

特色

  • 轻松创建和托管自己的应用(以应用为一个维度,而不是k8s的一个deploy、cm等)

  • 将软件包安装到任何 K8S 集群中

  • 查询集群以查看已安装和正在运行的程序包

  • 更新、删除、回滚或查看已安装软件包的历史记录

目标

  • 从头开始创建新的chart

  • 将chart打包成归档(tgz)文件

  • 与存储chart的仓库进行交互

  • 在现有的Kubernetes集群中安装和卸载chart

  • 管理与Helm一起安装的chart的发布周期

版本形式和版本偏差

Helm 的版本用 x.y.z 的形式描述,x 是主版本,y 是次版本,z 是补丁版本。

Helm的版本与k8s存在版本的兼容性问题:Helm 没有向上兼容机制,故推荐安装下表进行版本选择。

https://helm.sh/docs/topics/version_skew/

Helm Version Supported Kubernetes Versions
3.11.x 1.26.x - 1.23.x
3.10.x 1.25.x - 1.22.x
3.9.x 1.24.x - 1.21.x
3.8.x 1.23.x - 1.20.x
3.7.x 1.22.x - 1.19.x
3.6.x 1.21.x - 1.18.x
3.5.x 1.20.x - 1.17.x
3.4.x 1.19.x - 1.16.x
3.3.x 1.18.x - 1.15.x
3.2.x 1.18.x - 1.15.x
3.1.x 1.17.x - 1.14.x
3.0.x 1.16.x - 1.13.x
2.16.x 1.16.x - 1.15.x
2.15.x 1.15.x - 1.14.x
2.14.x 1.14.x - 1.13.x
2.13.x 1.13.x - 1.12.x

V2和V3版本变化

2019年11月,helm团队发布了第一个helm v3稳定版,其与helm v2主要的变化如下:

1、架构变化:最显著的变化,就是删除了Tiller而采用kubeconfig来授权和调用k8s api.

2、Release变化:Release名称可以在不通NameSpace中重用

3、chart存储变化:支持将chart推送到OCI注册中心(类似于Docker Registry)

4、支持JSONschema的Chart Values

5、其他

helm delete  更名  helm uninstall
helm inspect 更名  helm show
helm fetch   更名  helm pull
helm reset (已删除)
helm serve (已删除)
  • 移除了用于本地临时搭建Chart Repository的命令helm serve

  • 不再需要requirements.txt,将其合并到Chart.yaml

社区chart仓库

https://artifacthub.io/packages/search?kind=0

安装Helm

helm v3(推荐)

下载想要的版本:[https://github.com/helm/helm/tags)


# 安装二进制命令
wget https://get.helm.sh/helm-v2.17.0-linux-amd64.tar.gz
tar -zxvf helm-v2.17.0-linux-amd64.tar.gz
cp helm-v2.17.0-linux-amd64/helm /usr/local/sbin/

# 安装Tiller:https://github.com/orgs/helm/packages/container/package/tiller
cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
EOF

# init安装tiller
helm init --service-account tiller --skip-refresh
kubectl get pod -n kube-system -l app=helm

helm v2(不推荐)

下载想要的版本:https://github.com/helm/helm/releases/tag/v2.17.0

# 安装二进制命令
wget https://get.helm.sh/helm-v2.17.0-linux-amd64.tar.gz
tar -zxvf helm-v2.17.0-linux-amd64.tar.gz
cp helm-v2.17.0-linux-amd64/helm /usr/local/sbin/

# 安装Tiller:https://github.com/orgs/helm/packages/container/package/tiller
cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: ServiceAccount
metadata:
 name: tiller
 namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
 name: tiller
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: ClusterRole
 name: cluster-admin
subjects:
 - kind: ServiceAccount
 name: tiller
 namespace: kube-system
EOF

# init安装tiller
helm init --service-account tiller --skip-refresh
kubectl get pod -n kube-system -l app=helm

Helm常用命令

命令自动补全

yum install -y bash-completion
source <(helm completion bash)
helm completion bash > /etc/bash_completion.d/helm
# 查看helm相关环境变量
helm env

# 添加公用或私有仓库
helm repo add stable http://mirror.azure.cn/kubernetes/charts
helm repo add  elastic    https://helm.elastic.co
helm repo add  gitlab     https://charts.gitlab.io
helm repo add  bitnami    https://charts.bitnami.com/bitnami
helm repo add  incubator  https://kubernetes-charts-incubator.storage.googleapis.com
helm repo add  stable     https://kubernetes-charts.storage.googleapis.com
helm repo add  harbor     https://helm.goharbor.io

# 在 Repo 中查询可安装的 chart 包(支持模糊匹配)
$ helm search repo harbor
NAME          CHART VERSION  APP VERSION  DESCRIPTION 
harbor/harbor  1.11.1        2.7.1       An open source trusted cloud native reg...
$ helm search repo harbor --version 1.5
NAME          CHART VERSION  APP VERSION  DESCRIPTION 
harbor/harbor  1.5.6         2.1.6       An open source trusted cloud native reg...


# 下载一个chart到本地(--untar 表示解压,不加下载后为.tgz)
helm pull harbor/harbor --untar

# 检查一个chart是否合法
helm lint mychart


# 安装名为nginx的chart包在bitnmai仓库,并制定命令空间
helm install [NAME] [CHART] [-n namespace]
helm install nginx bitnami/nginx -n mycloud
helm install nginx bitnami/nginx  --debug --dry-run   # 用于调试,并不真正执行

# 查看全部应用(包含安装和卸载的)
helm list -n mydlqcloud --all

# 查看应用状态
helm status nginx -n mycloud

# 卸载应用,并保留安装记录
helm uninstall nginx -n mycloud --keep-history

# 卸载应用,不保留安装记录
helm delete nginx -n mycloud

# 渲染模板(查看通过指定的参数渲染的 Kubernetes 部署资源模板)
helm template bitnami/nginx

# 查看chart 可配置参数
helm show values bitnami/nginx

# 查看已安装chart配置的参数
helm get values nginx

# 应用升级
helm upgrade -f values.yaml nginx bitnami/nginx -n mycloud

# 应用回滚
helm history nginx
helm rollback nginx 1

Chart打包上传到chartmusem仓库(以Harbor为例)

需要helm 插件:https://github.com/chartmuseum/helm-push

注意:上传比较规划的公网公开chat还应该做签名,并生成一个*.prov

  • helm package --sign --key 'helm signing key' --keyring path/to/keyring.secret mychart
# 打包上传Chart
# * 打包chart(版本默认由<chart_name>/Chart.yaml 中的version决定)
helm package <chart_name>

# * 生成索引
helm repo index .

# * 方式一:下载push插件
$ helm plugin install https://github.com/chartmuseum/helm-push
Downloading and installing helm-push v0.10.3 ...
https://github.com/chartmuseum/helm-push/releases/download/v0.10.3/helm-push_0.10.3_linux_amd64.tar.gz

# * 方式二:helm plugin install 失败,可以wget下载然后解压打插件目录
wget https://github.com/chartmuseum/helm-push/releases/download/v0.10.3/helm-push_0.10.3_linux_amd64.tar.gz

$ helm env | grep PLUGIN
HELM_PLUGINS="/root/.local/share/helm/plugins"

$ mkdir /root/.local/share/helm/plugins/cm-push

tar xf helm-push_0.10.3_linux_amd64.tar.gz -C /root/.local/share/helm/plugins/cm-push

# 查看helm插件
helm plugin list

# 添加要上传的仓库
helm repo add  --username admin --password Harbor12345 --ca-file /harbor/tls/ca.crt localchart https://harbor.wzp.com/chartrepo/library

# 上传Chart到仓库
helm cm-push --ca-file /harbor/tls/ca.crt mycahrt-0.1.0.tgz localchart

Chart详解

helm默认本地存储的配置文件

helm env可以查看

System Cache Path Configuration Path Data Path
Linux $HOME/.cache/helm $HOME/.config/helm $HOME/.local/share/helm
macOS $HOME/Library/Caches/helm $HOME/Library/Preferences/helm $HOME/Library/helm
Windows %TEMP%\helm %APPDATA%\helm %APPDATA%\helm

什么是Chart?

Helm 使用一种称为Chart的打包格式。Chart是描述一组相关 Kubernetes 资源的文件集合。单个Chart可以用于部署简单的东西,如 memcached pod,或复杂的东西,如带有 HTTP 服务器、数据库、缓存等的完整 Web 应用程序堆栈。部署时通过helm客户端提交给k8s apiserver去完成

Chart的文件结构

mychart/

├── Chart.yaml                  # 包含了chart信息的YAML文件
├── LICENSE                      # 可选: 包含chart许可证的纯文本文件
├── README.md                       # 可选: 可读的README文件
├── values.yaml                  # chart 默认的配置值
├── values.schema.json           # 可选: 一个使用JSON结构的values.yaml文件
├── templates                  # 资源模版,配置 values 进行渲染,生成有效的 K8s manifest
│   ├── deployment.yaml
│   ├── _helpers.tpl          # 定义可以复用的子模板文件
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt              # 可选: 包含简要使用说明的纯文本文件
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests                  # 用于测试的文件
│       └── test-connection.yaml  # 具体做测试的容器或是k8s能识别的资源类型
├── crds/                        # 自定义资源的定义
├── Chart.lock                  # 保存先前版本的依赖
├── charts/                         # 包含chart依赖的其他chart
├── .helmignore                  # 指定忽略 Helm 包中哪些文件和目录
└── xxxx                      # 其他自定义内容,可以是conf/xxx.conf 一些配置文件
文件/目录 定义 是否必须
Chart.yaml 该文件中包含了Chart的元数据
templates/ 存放chart的资源模板文件,这些文件是 YAML 格式 是(除非依赖项在 Chart.yaml 文件中声明)
templates/NOTES.txt chart的说明文件。安装、升级或执行 notes 命令会输出此文件内容
values.yaml chart的默认存放变量的文件 否,但作为最佳实践,最好包含此文件
.helmignore 指定忽略 Helm 包中哪些文件和目录
charts/ 存放chart依赖的其他chart 无需明确提供,Helm 的依赖管理系统会自动创建此目录
Chart.lock 保存先前版本的依赖 无需明确提供,Helm 的依赖管理系统会自动创建此文件
crds/ 存储自定义(CRD)资源的目录。YAML 格式。这些资源会在 templates/ 中的资源之前安装。
README.md chart安装和使用说明的自述文件 否,但作为最佳实践,最好包含此文件
LICENSE license 文件
values.schema.json JSON 格式的chart values文件

Chart文件详解

https://helm.sh/zh/docs/topics/charts/

Chart.yaml文件

  • 注意:从v3.3.2之后该文件不允许,设置如下字段以外的任何字段,推荐的方法是在 annotations 中添加自定义元数据。
apiVersion: # 必需: chart API 版本, v2 版本为 helm3 语法,v1 版本仍然保留使用
name: # 必需: chart名称

# 遵循规范 https://semver.org/spec/v2.0.0.html
# 打包Chart时,以此版本作为后缀和包版本
version:        # 必需: 版本标识 示例:1.0.0 < 1.0.0-alpha < 1.0.0-beta < 2.0.0 < 2.1.0

# 一旦设置,会检测k8s版本,不匹配会显式报错
kubeVersion: # 可选: 兼容的k8s版本标识 # 示例:>= 1.13.0 < 1.14.0 || >= 1.14.1 < 1.15.0
description: # 可选: 项目的描述


# 有两种类型: application(默认)和 library(不可用于安装helm install)
# - library类型:定义了可以由其他chart中Helm 模板共享的chart原语或定义。这允许用户通过chart分享可复用得代码片段来避免重复并保持chart 干燥。
type: # 可选:chart类型

keywords: # 可选: 项目的关键字标识
 - xxx
 - xxx
home: # 可选:项目home页面的URL
sources: # 可选:项目源码的URL列表
 - https://xxxx
 - https://xxxx

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: # 可选
 - 关联values.yaml文件,可以用于指定启用或禁用所有的带tag的chart。
 import-values: # (可选)
 - ImportValue 保存源值到导入父键的映射。每项可以是字符串或者一对子/父列表项
 alias: (可选) chart中使用的别名。当你要多次添加相同的chart时会很有用
maintainers: # 可选
 - name: 维护者名字 (每个维护者都需要)
 email: 维护者邮箱 (每个维护者可选)
 url: 维护者URL (每个维护者可选)
icon: # 可选:用做icon的SVG或PNG图片URL
appVersion: # 可选:包含的应用版本。不需要是语义化,建议使用引号

# 表示是否被弃用,latest版本被标记为已弃用,则所有的chart都会被认为是已弃用的
deprecated: # 可选:是否被启用,布尔值

# 自定义元数据
annotations:
 example: 按名称输入的批注列表 (可选).

LICENSE文件

许可证(LICENSE)是一个包含了chart license的纯文本文件。

README.md文件

Markdown格式的说明文件,一般应包含:

  • chart提供的应用或服务的描述

  • 运行chart的先决条件或要求

  • values.yaml的可选项和默认值的描述

  • 与chart的安装或配置相关的其他任何信息

crds/目录

用于存放crd资源的目录,其下存放具体的CRD

  • Helm确定crds/目录中的CRD已经存在,Helm不会安装或升级。

  • 在升级或回滚时不会更新CRD。Helm只会在安装时创建CRD

  • CRD从不会被删除。自动删除CRD会删除集群中所有命名空间中的所有CRD内容。因此Helm不会删除CRD。

# crds/crontab.yaml
# 需要注意的是:该文件中不能存在模板语言,不可引用变量
kind: CustomResourceDefinition
metadata:
 name: crontabs.stable.example.com
spec:
 group: stable.example.com
 versions:
 - name: v1
 served: true
 storage: true
 scope: Namespaced
 names:
 plural: crontabs
 singular: crontab
 kind: CronTab

values.yaml文件

  • 存放变量的位置,YAML格式,支持层次

  • 变量名称以小写字母开头,单词按驼峰区分:

chicken: true
chickenNoodleSoup: true

.helmignore文件

用来指定你不想包含在你的helm chart中的文件。

helm package 命令会在打包应用时忽略所有在.helmignore文件中匹配的文件。

# comment

# Match any file or path named .helmignore
.helmignore

# Match any file or path named .git
.git

# Match any text file
*.txt

# Match only directories named mydir
mydir/

# Match only text files in the top-level directory
/*.txt

# Match only the file foo.txt in the top-level directory
/foo.txt

# Match any file named ab.txt, ac.txt, or ad.txt
a[b-d].txt

# Match any file under subdir matching temp*
*/temp*

*/*/temp*
temp?
  • 不支持'!'作为特殊的引导序列

  • 默认不会排除自身,需要显式添加 .helmignore

  • 末尾空格总会被忽略(不支持转义序列)

  • 不支持'**'语法。

NOTES.txt文件

helm installhelm upgrade命令的最后,Helm会打印出对用户有用的信息。

要在chart添加安装说明,只需创建templates/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 }}

Chart模板详解

Helm Chart 模板是按照 Go模板语言书写, 增加了50个左右的附加模板函数 来自 Sprig库 和一些其他 指定的函数

所有模板文件存储在chart的 templates/ 文件夹。 当Helm渲染chart时,它会通过模板引擎遍历目录中的每个文件。

模板的Value来源:

  • Chart开发者可以在chart中提供一个命名为 values.yaml (默认)的文件。

  • Chart用户可以在执行helm install -f values.yamlhelm install --set key=value来提供一个包含了value的YAML文件或变量

    • 提供的文件中的value会覆盖chart包中的values.yaml中的value
  • 如果是子chart,就是父chart中的values.yaml文件

yaml 文件写法 set 的写法
name: value --set name=value
a: b c: d --set a=b,c=d
outer: inner: value --set outer.inner=value
name: - a - b -c --set name={a, b, c}
servers: - port: 80 --set servers[0].port=80
servers: - port: 80 host: example --set servers[0].port=80,servers[0].host=example
name: "value1,value2" --set name=value1,value2
nodeSelector: kubernetes.io/role: master --set nodeSelector."kubernetes.io/role"=master

模板中各资源的先后加载顺序

# 从上到下依次执行
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

模板使用方式

在模板的使用过程中不论是变量还是函数都通过{{ }}中引用

Values 通过模板中{{ .Values }}对象可访问的values.yaml文件,也可以通过 {{ .Chart }} 对象可访问的 Chart.yaml ,以此类推。

内置Values

此外helm内置了一些values

  • .Release

    • 该对象描述了版本发布本身。

    • 使用方法如,{{ .Release.Name }} (获取 release 的名称)

.Release.Name        # 版本名称(非chart的)
.Release.Namespace   # 发布的chart版本的命名空间
.Release.Service     # 组织版本的服务,通常都是Helm
.Release.IsUpgrade    # 如果当前操作是升级或回滚,设置为true
.Release.IsInstall    # 如果当前操作是安装,设置为true
  • .Values

    • 访问 values.yaml 的内容。

    • 使用方法如,{{ .Values.image.repository }} (获取 values.yaml 中 image - repository 的值)

  • .Chart

    • 访问 Chart.yaml 的内容。

    • 使用方法如, {{ .Chart.Version }}

    • 注意:Chart.yaml中的字段在调用时首字母大写

    • 语法风格遵循 go 模版规则,类似 jinja2。是一种层级检索的方式,. 表示层级,最顶层的 .

  • .Files

    • 访问 chart 中存在的文件的内容 (.helmignore 除外)。

    • 使用方法如,{{ index .Files "file.name" }} (获取该文件中 name的值),它等效于 {{ .Files.Get name }} 方法。

    • {{ .Files.GetBytes }} 方法等效于访问文件中的 []byte 对象

    • {{ (.Files.Glob "conf/*.conf").AsConfig }} 以configmap的形式获取文件名和内容

    • {{ (.Files.Glob "conf/*.conf").AsSecrets }}以secret的形式获取文件名和内容

  • .Capabilities

    • 访问 K8s 的 Capabilities 对象。

    • 使用方法如,{{ .Capabilities.KubeVersion }} (获取 K8s 版本)、{{ .Capabilities.APIVersions.Has "batch/v1" }}(获取支持的 K8s API 版本)

.Capabilities.APIVersions          # 返回所有api列表
.Capabilities.APIVersions.Has $version  # 判断是否支持对应的api,布尔值
.Capabilities.KubeVersion          # k8s 版本
.Capabilities.KubeVersion.Version  # k8s 版本
.Capabilities.KubeVersion.Major      # k8s 主版本号
.Capabilities.KubeVersion.Minor      # k8s 次版本号
  • .Template

    • 当前被执行的当前模板信息

    • .Template.Name: 当前模板的命名空间文件路径 (e.g. mychart/templates/mytemplate.yaml)

    • .Template.BasePath: 当前chart模板目录的路径 (e.g. mychart/templates)

使用示例

$ helm create mychart
rm -rf mychart/templates/*

cat << EOF > mychart/values.yaml
who: wzp
 user: test
EOF

cat << EOF > mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-cm
data:
 rel_name: {{ .Release.Name }}
 rel_namespace: {{ .Release.Namespace }}
 rel_service: {{ .Release.Service }}
 rel_isupgrade: {{ .Release.IsUpgrade }}
 rel_install: {{ .Release.IsInstall }}
 value_who: {{ .Values.who }}
 value_user: {{ .Values.who.user  }}
 chart_name: {{ .Chart.Name }}
 chart_Version: {{ .Chart.Version }}
 k8s_metadata: |
 {{ .Capabilities.APIVersions.Has "batch/v1" }}
 {{ .Capabilities.KubeVersion }}
 {{ .Capabilities.KubeVersion.Version }}
 {{ .Capabilities.KubeVersion.Major }}
 {{ .Capabilities.KubeVersion.Minor }}
EOF

$ tree mychart
mychart
├── charts
├── Chart.yaml
├── templates
│   └── configmap.yaml
└── values.yaml


# 测试执行
helm install mycm mychart/ --debug --dry-run
NAME: mycm
LAST DEPLOYED: Tue Mar 21 07:26:47 2023
NAMESPACE: default
...
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: mycm-cm
data:
 rel_name: mycm
 rel_namespace: default
 rel_service: Helm
 rel_isupgrade: false
 rel_install: true
 value_who: map[user:test]
 value_user: test
 chart_name: mychart
 chart_Version: 0.1.0
 k8s_metadata: |
 true
 v1.23.13
 v1.23.13
 1
 23

内置函数

函数的使用格式

  • 语法:func_name arg1 arg2 ...

  • 倒置命令:arg1 | func_name arg2 ... 推荐用法

在倒置命令中的| 也被称之为管道符,类似于unix的管道概念通过管道符|将一系列的模板语言紧凑地将多个流式处理结果合并的工具。

不过值得注意是管道仅能将第一个参数传递给函数使用,在大多数仅有一个参数的函数使用时很直观

常用函数

quote <str> 或 squote <str>      # 将指定字符串添加上双引号或单引号

upper <str> 或 lower <str>      # 将指定字符串变成全部大写或小写

indent                              # 用于指定字符串缩进指定长度,以上一行为标准开始缩进

nindent                              # 用于指定字符串缩进指定长度,以行首为标准开始缩进,且会在上方添加一个新行

toYAML                              # 引用一整块YAMl内容,像健康检查,资源配额resources,或者端口,这都是一块一块的内容,通过toYaml引用很方便,(默认定格将后部分内容引入,所以需要连用缩进)

uuid                                 # 生成随机uuid

repeat <str> <n>                  # 将指定字符串重复n次

trim <str>                          # 去除字符串两边的空格

trimAll  <> <str>                  # 用于移除两边字符串中指定字符

trimPerfix <> <str>                  # 用于移除字符串指定的前缀

trimSuffix <> <str>                  # 用于移除字符串指定后缀

default <str> <default_value>      # 指定一个默认值,当引用值不存在时,使用默认值

unixEpoch                          # 返回时间戳,now | unixEpoch

sha256sum <str>                      # 用于计算字符串的SHA256值进行加密

htpasswd <user> <pass>              # 根据传入的username和paasword生成一个密码的bcrypt哈希值,可以用于HTTP Server的基础认证

encryptAES <盐> <要加密的内容>      # 加密函数,使用一个AES-256 CBC加密文本并返回一个base64编码字符串

decryptAES <盐> <加密后的内容>      # 解密函数,接收一个AES-256 CBC编码的字符串返回解密文本

b64enc                              # 将字符串以base64进行编码

printf "%s is %s" "wzp" "boby"      # 格式化输出 

lookup "apiVersion" "kind" "namespace" "name"  # 用于获取k8s的一些信息,类似于kubectl
# k8s 命令                         lookup函数
kubectl get pod mypod -n myns      lookup "v1" "Pod" "myns" "mypod"
kubectl get pod -n myns              lookup "v1" "Pod" "myns" ""
kubectl get pod -A                  lookup "v1" "Pod" "" ""
kubectl get ns myns                  lookup "v1" "Namespace" "myns" ""
kubectl get ns                       lookup "v1" "Namespace" "" ""

结尾附大部分函数说明

使用示例

helm create mychart
rm -rf mychart/templates/*

cat << EOF > mychart/values.yaml
who:
 user: test
trim: "  space   "
str: "hello world hello"
hello:
 nindent1: test
 nindent2: test2
EOF

cat << EOF > mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-cm
data:
 quote: {{ .Release.Name | quote }}                      # .Release.Name 加双引号
 squote: {{ .Release.Name | squote }}                    # .Release.Name 加单引号
 indent: {{ .Release.Name | indent 2 | quote }}          # 以当前行的位置,缩进
 nindent: {{ .Release.Name | nindent 2 | quote }}        # 增加一个换行,然后也行首为标准缩进
 {{- with .Values.hello }}
 {{- toYaml . | nindent 2 }}
 {{- end }}
 upper: {{ .Release.Name | upper | quote }}              # .Release.Name 全大写
 lower: {{ .Release.Name | lower | quote }}              # .Release.Name 全小写
 repeat3: {{ .Release.Name | repeat 3 | quote }}         # 重复.Release.Name 三次,并加上双引号(quote)
 default: {{ .Release.Haha | default "默认值" | quote }} # Haha并不存在,给一个默认值,并加上双引号(quote) 
 all_ns: {{ "v1" | lookup "Namespace" "" "" | quote }}   # 相当于kubectl get ns, 加了--dry-run时不会获取值
 unixepoch: {{ now | unixEpoch }}                        # 返回时间戳
 trim: {{ .Values.trim | trim | quote }}                         # 去掉.Values.trim字段两边的空格
 trimall: {{ trimAll "wo" "wohello worldwo" }}           # 去掉wohello worldwo 两边的 wo
 trimprefix: {{ trimPrefix "wo" "wohello worldwo" }}     # 去掉wohello worldwo 前边的 wo
 trimsuffix: {{ trimSuffix "wo" "wohello worldwo" }}     # 去掉wohello worldwo 后边的 wo
 sha256sum: {{ sha256sum "hello world" }}                # sha256加密 hello world
 htpasswd: {{ htpasswd "user1" "password" }}             # httppass加密user1和password
 b64enc: {{ b64enc "hello world" }}                      # base64编码 hello world
 encryptaes: {{ encryptAES "testkey" "testvalues" }}     # 以testky为加密秘钥,加密testvalues
 decryptaes: {{ decryptAES "testkey" "1BEUTeflfCLvsg5ivwh3OJ50zDfsHYqM1lYPndFNDck=" }} # 以testkey为秘钥,解密加密内容
 uuid: {{ uuidv4 }}
EOF

$ tree mychart
mychart
├── charts
├── Chart.yaml
├── templates
│   └── configmap.yaml
└── values.yaml

# 测试执行
helm install mycm mychart/ --debug --dry-run
install.go:172: [debug] Original chart version: ""
install.go:189: [debug] CHART PATH: /root/wzp/mychart
NAME: mycm
...
HOOKS:
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: mycm-cm
data:
 quote: "mycm"                      # .Release.Name 加双引号
 squote: 'mycm'                    # .Release.Name 加单引号
 indent: "  mycm"          # 以当前行的位置,缩进
 nindent: "\n  mycm"        # 增加一个换行,然后也行首为标准缩进
 nindent1: test
 nindent2: test2
 upper: "MYCM"              # .Release.Name 全大写
 lower: "mycm"              # .Release.Name 全小写
 repeat3: "mycmmycmmycm"         # 重复.Release.Name 三次,并加上双引号(quote)
 default: "默认值" # Haha并不存在,给一个默认值,并加上双引号(quote) 
 all_ns: "map[]"   # 相当于kubectl get ns, 加了--dry-run时不会获取值
 unixepoch: 1679646539                        # 返回时间戳
 trim: "space"                         # 去掉.Values.trim字段两边的空格
 trimall: hello world           # 去掉wohello worldwo 两边的 wo
 trimprefix: hello worldwo     # 去掉wohello worldwo 前边的 wo
 trimsuffix: wohello world     # 去掉wohello worldwo 后边的 wo
 sha256sum: b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9                # sha256加密 hello world
 htpasswd: user1:$2a$10$HLqEoWCIK0E6NCved.r7me8CiIM0USPMiet9AbE1u6mEuOBRYJ75K             # httppass加密user1和password
 b64enc: aGVsbG8gd29ybGQ=                      # base64编码 hello world
 encryptaes: dEziheGC1mXzWq+VEHhqN/ebvsl0Km1MJb3Jgs/T5hg=     # 以testky为加密秘钥,加密testvalues
 decryptaes: testvalues # 以testkey为秘钥,解密加密内容
 uuid: 24e17b0e-bfaf-4e52-9a4a-ec3394e691cd

流程控制语句

控制结构(在模板语言中称为"actions")提供控制模板迭代流的能力。Helm的模板语言提供了以下控制结构:

  • if/else, 用来创建条件语句

  • with, 用来指定范围

  • range, 提供"for each"类型的循环

if/else

条件控制语句

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

如下值为被解析为false:

  • 布尔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 }}

# 我们看一下结果,多了一个空行,为什么?
helm install mycm mychart/ --debug --dry-run
# 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认为空白是有意义的,因此管理空白变得很重要。helm模板中也给我们提供了处理该问题的方法

首先,模板声明的大括号语法可以通过特殊的字符修改,并通知模板引擎取消空白。{{-(包括添加的横杠和空格)表示向左删除空白, 而-}}表示右边的空格应该被去掉。 一定注意空格就是换行

要确保-和其他命令之间有一个空格。 {{- 3 }} 表示“删除左边空格并打印3”,而{{-3 }}表示“打印-3”。

使用这个语法,我们就可修改我们的模板,去掉新加的空白行:

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 }}


# 注意不要{{-   -}}  前后都加-,都加上会变成把两边的新行都删除了
 food: {{ .Values.favorite.food | upper | quote }}
 {{- if eq .Values.favorite.drink "coffee" -}}
 mug: "true"
 {{- end -}}
# 这样会变成food: "PIZZA"mug:"true",因为这把两边的新行都删除了。

比较复杂的用法

 annotations:
{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }}
 checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }}
{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }}
 checksum/tls: {{ include (print $.Template.BasePath "/core/core-tls.yaml") . | sha256sum }}
{{- end }}

 spec:
{{- if .Values.core.serviceAccountName }}
 serviceAccountName: {{ .Values.core.serviceAccountName }}

with

这个用来控制变量范围。回想一下,.是对 当前作用域 的引用。因此 .Values就是告诉模板在当前作用域查找Values对象。而with就是用来改变.的查找作用域

{{ 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 }}

 {{- with .Values.favorite }}
 drink: {{ .drink | default "tea" | quote }}
 food: {{ .food | upper | quote }}
 {{- end }}
 release: {{ .Release.Name }}              # 拿到 {{ end }} 之外可以正常执行

range

类似for循环的机制。Helm的模板语言中,在一个集合中迭代的方式是使用range操作符。

range可被用于列表和元组、用于迭代有键值对的集合(像mapdict

range 如果给定两个变量接收 列表或元组,那么第一个变量为索引(0开始),第二个为值。

range 如果给定两个变量接收 map或字典,那么第一个变量为key,第二个为值。

# values.yaml
favorite:
 drink: coffee
 food: pizza
pizzaToppings: # 列表(模板中称为切片)
 - mushrooms
 - cheese
 - peppers
 - onions

# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
data:
 {{- with .Values.favorite }}
 drink: {{ .drink | default "tea" | quote }}
 food: {{ .food | upper | quote }}
 {{- end }}
 toppings: |-
 {{- range .Values.pizzaToppings }}
 - {{ . | title | quote }}
 {{- end }}

# range方法“涵盖”(迭代)pizzaToppings列表。
# 就像with设置了.的作用域,range操作符也做了同样的事。
# 每一次循环,.都会设置为当前的作用域。 也就是说,第一次.设置成了mushrooms,第二次迭代设置成了cheese,等等。



helm install mycm mychart/ --debug --dry-run
# 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"

变量

在helm3 中,变量一般我们不会直接使用,通常搭配 withrange 一起使用。

变量的定义格式:$name := value , :=为赋值运算符。

作用域

变量一般不是"全局的"。作用域是其声明所在的块。

with可以改变他的作用域,下面我们看个例子

data:
 {{- with .Values.favorite }}
 drink: {{ .drink | default "tea" | quote }}
 food: {{ .food | upper | quote }}
 release: {{ .Release.Name }}
 {{- end }}
  • 在这个例子中,执行会失败,因为with将其作用域改到了.Values.favorite

  • 那么在去调用.Release.Name就变成了.Values.favorite.Release.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 }}

在模板的顶层赋值了$relname。变量的作用域会是整个模板。

变量在range循环中特别有用。可以用于类似列表的对象,以获取索引和值:

pople:
 - name
 - sex
 - num


data:
 pople: |-
 {{- range $index, $value := .Values.pople }}
 # 整型索引(从0开始)赋值给$index并将值赋值给$value
 {{ $index }}: {{ $value }}
 {{- end }}

# 此时我们可以获取到类似于带索引的列表
data:
 pople: |-
 0: name
 1: sex
 2: num

对于数据结构有key和value,可以使用range获取key和value。

# values.yaml
favorite:
 myvalue: "Hello World"
 drink: "coffee"
 food: "pizza"


apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
data:
 myvalue: "Hello World"
 {{- range $key, $val := .Values.favorite }}
 {{ $key }}: {{ $val | quote }}
 {{- end }}

# 此时我们可以连带着获取key和value
data:
 myvalue: "Hello World"
 drink: "coffee"
 food: "pizza"

上with的例子中说在模板的顶层赋值了$relname。变量的作用域会是整个模板。

而本次例子$key$value作用域会在{{ range... }}{{ end }}块内。

这里有一个特殊的变量 $ 这个变量一直是指向根的上下文。当在一个范围内循环同时要用一些根的一些变量很有用

apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
data:
 {{- with .Values.favorite }}
 drink: {{ .drink | default "tea" | quote }}
 food: {{ .food | upper | quote }}
 release: {{ $.Release.Name }}
 chart: {{ $.Chart.Name }}
 {{- end }}

命名模板和引用

命名模板 (有时称作一个 部分 或一个 子模板)仅仅是在文件内部定义的模板,并使用了一个名字。有两种创建方式和几种不同的使用方法。

  • 局部命名(每个模板文件中)

  • __helpers.tpl (类似于全局,__helpers.tpl是默认名称)

  • templates/目录中,任何以下划线(_)开始的文件(惯例使用__helpers.tpl)

不管何种方式定义的子模板,模板名称都是全局的,名称相同时最后一个加载的覆盖之前的

命名模板通常用于多行需要被重复使用或被变量化的内容。

声明和管理模板的方法:

  • define

    • 声明命名模块块
  • template

    • 引用子模板,接受1或2个参数(命名模板名称和变量的获取范围)

    • 无法被 其他函数修饰,(常见于缩进修饰)

  • block

  • include(推荐)

    • 引用子模板,接受2个参数(命名模板名称和变量的获取范围)

    • 可以被 其他函数修饰 (常见于缩进修饰)

定义和引用

define来声明模板

{{/* Generate basic labels */}}  # 按照惯例我们应该使用注释说明作用
{{- define "MY.NAME" -}}
 # body of template here
{{- end -}}

templateinclude(推荐)来引用

{{ template "MY.NAME" }}

{{ include "MY.NAME" }}

template示例

# 在主模板中
# templates/configmap.yaml 
{{- define "mychart.labels" }}
 labels:
 generator: helm
 date: {{ now | htmlDate }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-cm
 {{ template "mychart.labels" }}
data:
 myvalue: "Hello World"

注意:当一个(使用define创建的)命名模板被渲染时,会接收被templateinclude调用传入的内容。 若引用了对象,默认情况是无法用.访问任何内容。所以引用对象时必须传递一个范围给模板

template注意示例

# templates/configmap.yaml 
{{- define "mychart.labels" }}
 labels:
 generator: helm
 date: {{ now | htmlDate }}
 chart: {{ .Chart.Name }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-cm
 {{ template "mychart.labels" . }}    # 将'.'根位置传递进去,这时渲染时就可以调用.Chart.Name了,否则报错
data:
 myvalue: "Hello World"

为什么有了template后还要增加一个include方法?我们看个案例

{{- define "mychart.labels" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}"
{{- end -}}

apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
 labels:
 {{ template "mychart.labels" . }}
data:
 myvalue: "Hello World"
{{ template "mychart.labels" . }}

当我们尝试渲染上述模板时直接报错了

$ helm install mycm mychart/  --dry-run 
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 mycm mychart/  --dry-run --disable-openapi-validation 
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: mycm-configmap
 labels:
 app_name: mychart
app_version: "0.1.0"          # 错乱的位置
data:
 myvalue: "Hello World"
app_name: mychart              # 错乱的位置
app_version: "0.1.0"          # 错乱的位置

很显然这参数的摆放位置并不是我们想要的,这是因为被替换的模板中文本是左对齐的。由于template是一个行为,不是方法,无法将 template调用的输出传给其他方法,数据只是简单地按行插入。

这时引入了include , 它是一个方法可以被函数修饰,所以在helm中使用include被认为是更好的方式,可以更好地处理YAML文档的输出格式

这时调整我们的模板,通过indent 函数修饰为正确的样子

{{- define "mychart.labels" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}"
{{- end -}}

apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
 labels:
{{ include  "mychart.labels" . | indent 4 }}
data:
 myvalue: "Hello World"
{{ include  "mychart.labels" . | indent 2 }}


$ helm install mycm mychart/  --dry-run 
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: mycm-configmap
 labels:
 app_name: mychart
 app_version: "0.1.0"
data:
 myvalue: "Hello World"
 app_name: mychart
 app_version: "0.1.0"

在模板中获取文件内容

有时想导入的不是模板的文件,而是无需模板渲染的文件内容时使用此方式。

Helm 提供了通过.Files对象访问文件的方法。不过,在我们使用模板示例之前,有些事情需要注意:

  • 可以添加额外的文件到chart中。但是要小心,由于Kubernetes对象的限制,Chart必须小于1M。

  • 通常处于安全考虑,一些文件无法通过.Files对象访问:

    • 无法访问templates/中的文件

    • 无法访问使用.helmignore排除的文件

    • 之Chart无法使用父Chart的文件

.Files使用方法

  • .Files.Get:获取文件内容

  • .Files.Glob:获取文件名和内容(支持正则指定多个文件)

    • .Files.Glob.AsConfig:获取后文件内容以configmap的形式展示
    • .Files.Glob.AsSecret:获取后文件内容以secret的形式展示
  • .Files.Lines:获取文件的每一行

路径方法

  • base

    • 返回路径的最后一个元素(即文件名称)
  • dir

    • 返回路径中的目录
  • ext

    • 返回文件扩展名(即最后一个.和其后面的内容)
  • isAbs

    • 布尔值,是否为绝对路径
  • clean

    • 清除路径中间部分的..和前一个路径,如/foo/bar/../hello.txt,清除后为/foo/hello.txt

使用示例

读取三个文件使用.Files.Get方法获取内容

# mychart/hello.txt
hello world 

# mychart/nginx.conf
nginx conf ....

# mychart/conf.toml
message = hello from conf.toml

# mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
data:
 {{- range tuple "hello.txt" "nginx.conf" "conf.toml" }}
 {{ . }}: |-
 {{ .Files.Get . }}
 {{- end }}
 token: |-
 {{ .Files.Get "conf.toml" | b64enc }}

# 调试渲染模板
$ helm template --debug mychart
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: RELEASE-NAME-configmap
data:
 hello.txt: |-
 hello world

 nginx.conf: |-
 nginx conf ....

 conf.toml: |-
 message = hello from conf.toml
 token: |-
 bWVzc2FnZSA9IGhlbGxvIGZyb20gY29uZi50b21sCg==

使用.Files.Glob获取多个Files对象

这意味着.Files.Glob返回的对象还可以继续调用Files对象的方法

示例1

# mychart/hello.txt
hello world 

# mychart/nginx.conf
nginx conf ....

# mychart/mysql.conf
mysql conf

# mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
data:
{{- range $path, $_ := .Files.Glob "**.conf" }}
 path: {{ $path }}
 {{ $path }}: {{ $.Files.Get $path }}
{{- end }}


# 调试渲染模板
helm template --debug mychart
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: RELEASE-NAME-configmap
data:
 path: mysql.conf
 mysql.conf: mysql conf

 path: nginx.conf
 nginx.conf: nginx conf ....

示例2

# mychart/hello.txt
hello world 

# mychart/nginx.conf
nginx conf ....

# mychart/mysql.conf
mysql conf

# mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
data:
{{ (.Files.Glob "**.conf").AsConfig | indent 2 }}
{{ (.Files.Glob "**.conf").AsSecrets | indent 2 }}


# 调试渲染模板
$ helm template --debug mychart
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: RELEASE-NAME-configmap
data:
 mysql.conf: |
 mysql conf
 nginx.conf: |
 nginx conf ....
 mysql.conf: bXlzcWwgY29uZgo=
 nginx.conf: bmdpbnggY29uZiAuLi4uCg==

使用.Files.Lines配合range遍历文件每一行

# mychart/lines.txt
the line1
the line2
the line3
the line4

# mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
data:
{{- range $index, $line := .Files.Lines "lines.txt" }}
 {{- if $line }}
 {{ $index }}: {{ $line | quote }}
 {{- end }}
{{- end }}
 lines.txt: {{ range .Files.Lines "lines.txt" }}
 {{ . }}{{ end }}


# 调试渲染模板
$ helm template --debug mychart
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: RELEASE-NAME-configmap
data:
 0: "the line1"
 1: "the line2"
 2: "the line3"
 3: "the line4"
 lines.txt: 
 the line1
 the line2
 the line3
 the line4

路径示例

apiVersion: v1
kind: ConfigMap
metadata:
 name: {{ .Release.Name }}-configmap
data:
 base: {{ "/root/wzp.txt" | base }}
 dir: {{ "/root/wzp.txt" | dir }}
 dir: {{ "mychart/template/wzp.txt" | dir }}
 clean: {{ "/root/wzp.txt" | clean }}
 clean: {{ "/opt/test/../mq/wzp.txt" | clean }}
 clean: {{ "opt/test/../mq/wzp.txt" | clean }}
 ext: {{ "/root/wzp.txt" | ext }}
 isAbs: {{ "/root/wzp.txt" | isAbs }}


# 调试渲染模板
$ helm template --debug mychart
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: RELEASE-NAME-configmap
data:
 base: wzp.txt
 dir: /root
 dir: mychart/template
 clean: /root/wzp.txt
 clean: /opt/mq/wzp.txt
 clean: opt/mq/wzp.txt
 ext: .txt
 isAbs: true

模板调试技巧

  • helm lint 用于验证chart是否遵循最佳实践的首选工具。

  • helm template --debug 在本地测试渲染chart模板。

  • helm install --dry-run --debug:服务器渲染模板,并返回生成的清单文件。

  • helm get manifest: 查看安装在服务器上的模板。

另外一个可能在我们渲染模板由于格式错误等问题,无法返回我们yaml清单,这时可以通过--dry-run --disable-openapi-validation来显示最终的效果方便我们排查错误

helm install  xxx -f mychart/ --dry-run --disable-openapi-validation

Chart开发小技巧

1. 模板渲染required

在正常情况下,模板渲染所需的特定值。如果这个值是空的,模板渲染会出错并打印用户提交的错误信息。

这时如果可以提前预见有些情况某些值就是可能由于使用上的不小心会存在空值,可以通过required来声明,且在为空时打印自定义提示

例如

# 正常情况下,当.Values.who不存在时,渲染模板会直接报错
value: {{ .Values.who }}

# 当.Values.who不存在时会打印错误信息
value: {{ required "A valid .Values.who entry required!" .Values.who }}

2. 字符串引号括起来,但整型不用

由于chart不会提前声明变量类型,类似于python自动识别,所以如果确认是字符串应当使用引号括起来保证,在得到数字时也是字符串类型而不是整形

# 使用字符串数据时,将字符串括起来而不是露在外面
name: {{ .Values.MyName | quote }}

# 使用整型时 不要把值括起来。在很多场景中那样会导致Kubernetes内解析失败。
port: {{ .Values.Port }}

# 当然使用整形时,不要括起来不适用于环境变量为整形时,即使表现为整型
env:
 - name: HOST
 value: "http://host"
 - name: PORT
 value: "1234"

3. image_pull_secret

在拉取私有仓库的镜像时,通过要挂载一个secret用于存放镜像仓库地址、用户名和密码,但创建时需要用base64跑一会影响速度,所以我们可以通过模板用于承载秘钥

# values.yaml
registry: quay.io
username: someone
password: someone
email: wzp@163.com
# 声明命名模板
{{- define "imagePullSecret" }}
{{- with .Values.imageCredentials }}
{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username .password .email (printf "%s:%s" .username .password | b64enc) | b64enc }}
{{- end }}
{{- end }}

# templates/registry-secret.yaml
apiVersion: v1
kind: Secret
metadata:
 name: myregistrykey
type: kubernetes.io/dockerconfigjson
data:
 .dockerconfigjson: {{ template "imagePullSecret" . }}
  1. 滚动部署

由于配置configmapsecret作为配置文件注入容器以及其他外部依赖更新导致经常需要滚动部署pod。

但可能存在helm upgrade更新chart时,由于delpyment本身没有更改并使用原有配置保持运行,导致部署不一致。

此时可以通过sha256sum方法保证在另一个文件发生更改时deployment也跟着变更

kind: Deployment
spec:
 template:
 metadata:
 annotations:
 checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
[...]


# 另外一种纯随机的方式
kind: Deployment
spec:
 template:
 metadata:
 annotations:
 rollme: {{ randAlphaNum 5 | quote }}
[...]
  1. 避免全部卸载

有时在执行helm uninstall时有些资源不应该被卸载。Chart的开发者可以在资源中添加额外的说明避免被卸载。

kind: Secret
metadata:
 annotations:
 "helm.sh/resource-policy": keep
[...]

"helm.sh/resource-policy": keep 允许指示Helm操作(比如helm uninstallhelm upgradehelm rollback)要删除时跳过删除这个资源,然而,这个资源会变成孤立的。Helm不再以任何方式管理它。

如果在已经卸载的但保留资源的版本上使用helm install --replace会出问题。

Chart 钩子

Helm 提供了一个 hook 机制允许chart开发者在发布生命周期的某些点进行干预。比如你可以使用hook用于:

  • 安装时在加载其他chart之前加载配置映射或密钥

  • 安装新chart之前执行备份数据库的任务,然后在升级之后执行第二个任务用于存储数据。

  • 在删除发布之前执行一个任务以便在删除服务之前退出滚动。

钩子的工作方式与常规模板类似,但因为Helm对其不同的使用方式,会有一些特殊的注释。

参考:https://helm.sh/docs/topics/charts_hooks/

大部分函数

逻辑比较函数

大部分都返回一个布尔值

函数名 说明
eq 用于判断两个参数是否相等
ne 用于判断两个参数是否不相等
lt 用于判断第一个参数是否小于第二个参数
le 用于判断第一个参数是否小于等于第二个参数
gt 用于判断第一个参数是否大于第二个参数
ge 用于判断第一个参数是否大于等于第二个参数
and 返回两个参数的逻辑与结果(布尔值)
or 返回两个参数的逻辑或结果(布尔值)
not 用于对参数的布尔值取反
default 用于设置默认值,参数为空时使用默认值
empty 用于判断给定的参数是否为空
coalesce 用于扫描一个给定的列表,并返回第一个非空数值
ternary 接收两个参数和一个test,如果test为true返回第一个参数,为false返回第二个参数

字符串函数

函数名 说明
print 将所有参数格式化输出
println 将所有参数格式化输出,并且每个字符串后增加一个空格,结尾增加一个换行符
printf 将所有参数格式化输出,并且支持占位符 {{ printf "the float is %.2f" 3.1415 }}
trim 去除字符串两边的空格
trimAll 用于移除字符串中指定字符
trimPrefix 用于移除字符串指定的前缀
trimSuffix 用于移除字符串指定的后缀
lower 字符串字母全部大写
upper 字符串字母全部小写
title 字符串首字母转换成大写
untitle 字符串首字母转换成小写
snakecase 用于将驼峰写法转换成下划线命名法
camelcase 用于将下换线名称法转成驼峰命名
kebabcase 用于将驼峰写法转换成纵横杠写法
swapcase 用于切换字符串大小写 a)大写字母变小写 b)首字母变小写 c)空格后或开头的小写字母转换成大写 d)其他小写字母换成大写
substr 用于切割字符串(指定切割起、始位置),并返回切割后的字符串
trunc 用于截断字符串,用正整数或负整数,表示从左向右或从右向左截取字符的个数
abbrev 切割字符串,保留指定长度,结尾用...,省略号占三个长度
randAlpha 使用a-zA-Z,生成随机字符串
randAlphaNum 使用0-9a-zA-Z,生成随机字符串
randNumeric 使用0-9,生成随机字符串
randAscii 使用所有ASCII字符,生成随机字符串
contains 用于测试一个字符串是否包含在另一个中
hasPrefix 用于测试一个字符串是否是指定的前缀
hasSuffix 用于测试一个字符串是否是指定的后缀
repeat 用于将指定字符串重复n次
nospace 去掉字符串的所有空格
initials 截取指定字符串的每个单词的首字母,并拼接成新的字符串
wrapWith 用于在文档字符串中指定的列添加内容
quote 给字符串增加双引号
squote 给字符串增加单引号
cat 合并多个字符串,并用空格分隔
replace 执行字符串替换,三个参数:待替换的字符,替换后的字符串,源字符串
shuffle 用于对字符串重新排序,
indent 用于指定字符串缩进指定长度,以上一行为标准开始缩进
nindent 用于指定字符串缩进指定长度,以行首为标准开始缩进,且会在上方添加一个新行
plural 判断字符串长度,根据值不同,返回不同的字符串 {{ len "a" plural "one" "many" }} a) 长度为1,返回第一个字符串 b)长度不为1,返回第二个字符串

类型转换和正则函数

函数名 说明
kindOf 判断字符串类型
atoi 将字符串转换为整型
float64 转换为float64类型
int 转换为整型
int64 转换为int64类型
toString 转换成字符串
toDecimal 将 unix 八进制转成 int64
toStrings 将列表、切片、数组转换成字符串列表
toJson 将列表、切片、数组、字典或对象转换成Json
toPrettyJson 将列表、切片、数组、字典或对象转换成格式化Json
toRawJson 将列表、切片、数组、字典或对象转换成格式化Json(不转义HTML字符)
regexFind 根据正则匹配查找匹配的内容,并返回第一个匹配的结果
regexFindAll 用于获取字符串中匹配正则表达式的所有子字符串,并在该函数的最后指定一个整数来表示返回多少个正则匹配的字符串
regexMatch 用指定正则,匹配字符串,匹配正常返回true 匹配邮箱:{{ regexMatch "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$" "test@xxx.com" }}
regexReplaceAll 用指定的字符串替换正则匹配到的字符
regexReplaceAllLiteral 将通过正则匹配的内容,替换成其他内容
regexSplit 指定一个分隔符,将正则匹配到的内容替换为分隔符,切割字符串,并返回指定的分片,-1表示所有
各个regex开头的都有must版 例如:mustRegexMatch、mustRegexFindAll 区别在于有表达式错误时,不带must的直接报错,must版会想模板引擎返回错误

加密函数和编解码函数

函数名 说明
sha1sum 用于计算字符串的SHA1值进行加密
sha256sum 用于计算字符串的SHA256值进行加密
adler32sum 用于计算字符串的Adler-32校验和进行加密
htpasswd 根据传入的username和paasword生成一个密码的bcrypt哈希值,可以用于HTTP Server的基础认证
encryptAES 加密函数,使用一个AES-256 CBC加密文本并返回一个base64编码字符串
decryptAES 解密函数,接收一个AES-256 CBC编码的字符串返回解密文本
b64enc 将字符串以base64进行编码
b32enc 将字符串以base32进行编码

日期函数

now 返回当前日期和时间
date 将日期信息格式化,必须使用'2006-01-02'或'2006/01/02'来表明,否则出错 {{ now date "02/01/2006" }}
dateInZone 与date基本一致,增加了指定时区 {{ dateInZone "2006-01-02" (now) "UTC" }}
duration 将给定的秒数转换为好阅读类型,如95 -> 1m35s
durationRound 用于取整保留最大的时间单位,2h10m5s -> 2h
unixEpoch 返回时间戳,now unixEpoch
dateModify 将给定时间加减后返回新的时间 now dateModify "-2h"
toDate 将给定字符串转换成时间格式,toDate "2006-01-02" "2023-01-01"

字典函数

dict 创建一个字典,key必须是字符串 {{- $myDict := "k1" "v1" "k2" "v2" }}
get 通过key获取字典的值
set 向已有字典新加键值对,也可以修改原来的
unset 删除字典中指定的key
keys 获取一个或多个字典中的所有key,并返回一个列表
haskey 判断字典中是否存在指定key
pluck 根据一个key在多个字典中获取所有匹配的value,返回value组成的列表
merge 合并一个或多个字典,key相同时以第一个为准
mergeOverwrite 合并一个或多个字典,key相同时以最后一个为准
values 用于获取一个字典的所有值返回一个列表
pick 根据字典指定的key获取value,返回一个新字典
omit 与pick相反,忽略指定key,获取未指定的key和value,返回新字典
deepCopy 深度拷贝一个字典

列表函数

list 生成一个列表 {{ $myList := list 1 2 3 "onw" "two" }}
first 获取列表的第一个值
rest 获取除第一个值以外的所有值
last 获取列表的最后一个值
initial 获取列表中除最后一个以外的所有值
append 在已有列表中追加一项(最后)
prepend 在列表的最前面加入一个新值
concat 将任意数量的列表合并一个新的列表
reverse 返回一个反转后的列表
uniq 去除列表的重复项
without 用于过滤掉某个值(即不要某个值)
has 判断一个值是否包含在列表中
compact 删除一个列表中的空值
slice 用于对列表切片, slice [n] [m]
until 用于构建一个指定整数范围内的列表
untilStep 与Until类似,可以指定步长,untilStep [n] [m] [步长]
seq 生成指定范围内的整数列表
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容