Helm

参考:

环境:

  • el7x86_64
  • helm v3.3








概述

Helm是Kubernetes生态系统中的一个软件包管理工具,主要用来管理Charts,有点类似于Ubuntu中的apt或CentOS中的yum。由go编写,是Deis公司发起的一个开源工具,有助于简化部署和管理Kubernetes应用。

在Kubernetes中,应用管理是需求最多、挑战最大的领域。Helm项目提供了一个统一软件打包方式,支持版本控制,可以大大简化Kubernetes应用分发与部署中的复杂性。

Helm Chart是用来封装 Kubernetes原生应用程序的一系列YAML文件。可以在你部署应用的时候自定义应用程序的一些 Metadata,以便于应用程序的分发。

对于应用发布者而言,可以通过 Helm 打包应用、管理应用依赖关系、管理应用版本并发布应用到软件仓库。

对于使用者而言,使用 Helm 后不用需要编写复杂的应用部署文件,可以以简单的方式在 Kubernetes 上查找、安装、升级、回滚、卸载应用程序。



The package manager for Kubernetes.

Helm is the best way to find, share, and use software built for Kubernetes.








术语

Glossary: https://helm.sh/docs/glossary/


Chart

Helm包涵盖了将k8s资源安装到k8s集群所需的足够多的信息。
Charts包含了Chart.yaml文件核模板,默认值(values.yaml),以及相关依赖。
Charts开发设计了良好定义的目录结构,并打包为chart archive。


Chart Archive

Chart包是被targzip压缩(可选签名)的chart。


Chart Dependency(Subcharts)

Chart可以依赖于其它chart。依赖有两种方式:

  • 软依赖(soft): 如果另一个chart没有在集群中安装,chart可能会无法使用
  • 硬依赖(hard): chart包含它所依赖的chart。(在charts/目录中)

当一个chart打包(helm package)时,所有的依赖都会和它绑定。


Chart Version

每个chart都需要版本号。


Chart.yaml

chart的信息说明被存储在一个特定文件(Chart.yaml)。每个chart都必须有这个文件。


helm

Helm是k8s包管理器。作为一个操作系统包管理器,使其很容易在操作系统中安装工具。Helm使得k8s集群中安装应用和资源变得异常简单。


Helm Configuration Files

Helm将配置文件存储在XDG目录中。helm第一次运行,会自动生成。

Kube Config(KUBECONFIG)

helm客户端通过Kube config配置文件来理解k8s集群。默认$HOME/.kube/config


Lint

Helm代码规范,规范一个chart是去验证其遵照Helm chart的标准规范和要求。Helm提供了helm lint命令。


Provenance

Helm chart可以由来源文件(provenance file)提供chart的出处以及它所包含的内容。

来源文件(.prov)是Helm安全的一部分。一个来源包含chart包文件的加密哈希值,Chart.yaml数据,一个签名块。当再加上一个钥匙链(keychain)时,可为chart用户提供以下能力:

  • 验证chart被可信第三方签名
  • 验证chart文件没有被篡改
  • 验证chart的元数据内容(Chart.yaml)
  • 快速匹配chart的数据来源


Release

发行版本。chart安装之后,Helm库会创建一个release来跟踪这个安装。

单个chart可以在同一个集群中安装多次,并能创建多个不同的版本。


Release Number/Version

单个版本号可以被升级多次。通过连续技术来跟踪升级发布版本。


Rollback

每一次发布会更新chart或者配置。当生成发布历史后,一次发布也可以被 rolled back 之前的发布版本号。回滚使用helm rollback命令。

重要的是, 每一次回滚版本会生成一个新的发布版本号。

1
2
3
4
5
操作			版本号
install release 1
upgrade release 2
upgrade release 3
rollback 1 release 4 (但使用release 1的配置)


Helm Library(SDK)

Helm库(或SDK)涉及到go代码,可以直接与k8s API服务交互进行安装、升级、查询 以及移除k8s资源。


Repository

Helm chart可以被存储到专用的HTTP服务器上,称之为chart仓库。

Helm客户端可以指向零个或多个chart仓库。默认没有配置仓库,可使用helm repo add添加。


Values

Values 提供了一种使用您自己的信息覆盖模板默认值的方式。

Helm Chart是参数化的, 这意味着chart开发者可以在安装时显式配置。比如说,chart可以暴露username字段, 允许为服务设置一个用户名。这些可暴露的变量在Helm用语中称为values

Values可在helm install, helm upgrage时设置。也可以在values.yaml文件中设置。








介绍

Introduction: https://helm.sh/docs/intro/


快速入门

Quickstart: https://helm.sh/docs/intro/quickstart/

如何快速安装核使用Helm。


先决条件

Prerequisites

使用Helm的前置条件:

  • k8s集群
    • 建议最新k8s稳定版
    • kubectl
  • 安装的安全配置(如果有的话)
  • 安装和配置Helm

注意Helm版本对应支持的k8s版本。




安装

Install: https://helm.sh/docs/intro/install/

从源码、或二进制安装Helm CLI。


从Helm项目

From The Helm Project


从二进制包:

1
2
3
4
5
wget https://get.helm.sh/helm-v3.3.3-linux-amd64.tar.gz

tar -zxvf helm-v3.3.3-linux-amd64.tar.gz

mv helm /usr/local/bin/helm


从脚本:

1
2
3
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh




从源码

1
2
3
git clone https://github.com/helm/helm.git
cd helm
make




初始化Helm chart

Initialize a Helm Chart Repository

Helm安装好之后,你可以添加一个chart仓库。

1
2
3
4
5
6
7
8
9
10
11
# 添加Helm官方仓库
helm repo add stable https://kubernetes-charts.storage.googleapis.com/


# 查看安装的charts列表
helm search repo stable
NAME CHART VERSION APP VERSION DESCRIPTION
stable/acs-engine-autoscaler 2.2.2 2.1.1 DEPRECATED Scales worker nodes within agent pools
stable/aerospike 0.2.8 v4.5.0.5 A Helm chart for Aerospike in Kubernetes
stable/airflow 4.1.0 1.10.4 Airflow is a platform to programmatically autho...
stable/ambassador 4.1.0 0.81.0 A Helm chart for Datawire Ambassador




安装Chart

Install an Example Chart

可以通过helm install命令安装chart。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
helm repo update

# helm install,都会创建一个新的release
# 所以一个chart在同一个集群里面可以被安装多次,每一个都可以被独立的管理和升级
helm install stable/mysql --generate-name
NAME: mysql-1600679719
LAST DEPLOYED: Mon Sep 21 17:15:23 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mysql-1600679719.default.svc.cluster.local

To get your root password run:

MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default mysql-1600679719 -o jsonpath="{.data.mysql-root-password}" | base64 --decode; echo)

To connect to your database:

1. Run an Ubuntu pod that you can use as a client:

kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il

2. Install the mysql client:

$ apt-get update && apt-get install mysql-client -y

3. Connect using the mysql cli, then provide your password:
$ mysql -h mysql-1600679719 -p

To connect to your database directly from outside the K8s cluster:
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306

# Execute the following command to route the connection:
kubectl port-forward svc/mysql-1600679719 3306

mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD}

#查看此chart的基本信息
helm show chart stable/mysql
helm show all stable/mysql




Releases

1
2
3
4
5
6
7
8
# 查看chart发行版
helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
mysql-1600679719 default 1 2020-09-21 17:15:23.169811348 +0800 CST deployed mysql-1.6.7 5.7.30


# 列出所有部署的发行
helm list




卸载Release

使用helm uninstall命令卸载realease。

1
2
helm uninstall mysql-1600679719
release "mysql-1600679719" uninstalled

它会删除和该release相关的所有资源。使用--keep-history选项,Helm将保存release history。所以你可以审计集群历史甚至使用helm rollback回滚release。








主题

Topic Guides: https://helm.sh/docs/topics/


Charts

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

Helm使用的包格式称为charts。chart就是一个描述k8s相关资源的文件集合。单个chart可以用来部署简单或复杂的服务。

Chart是作为特定目录布局的文件被创建,它们可以打包到要部署的版本存档中。

1
2
# 下载一个chart,但不安装
helm pull xxx




文件结构

chart是一个组织在文件目录中的集合。目录名称就是chart名称(没有版本信息)。

示例:

1
2
3
4
5
6
7
8
9
10
wordpress/
Chart.yaml # 包含了chart信息的YAML文件
LICENSE # 可选: 包含chart许可证的纯文本文件
README.md # 可选: 可读的README文件
values.yaml # chart 默认的配置值
values.schema.json # 可选: 一个使用JSON结构的values.yaml文件
charts/ # 包含chart依赖的其他chart
crds/ # 自定义资源的定义
templates/ # 模板目录, 当和values 结合时,可生成有效的Kubernetes manifest文件
templates/NOTES.txt # 可选: 包含简要使用说明的纯文本文件




Chart.yaml

Chart.yaml文件是chart必须的。包含以下字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: chart API 版本 (必需)
name: chart名称 (必需)
version: 语义化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
enabled: (可选) 决定是否加载chart的布尔值
import-values: # (可选)
- ImportValue 保存源值到导入父键的映射。每项可以是字符串或者一对子/父列表项
alias: (可选) chart中使用的别名。当你要多次添加相同的chart时会很有用
maintainers: # (可选)
- name: 维护者名字 (每个维护者都需要)
email: 维护者邮箱 (每个维护者可选)
url: 维护者URL (每个维护者可选)
icon: 用做icon的SVG或PNG图片URL (可选)
appVersion: 包含的应用版本(可选)。不需要是语义化的
deprecated: 不被推荐的chart (可选,布尔值)
annotations:
example: 按名称输入的批注列表 (可选).


Chart和版本控制

每个chart都必须有版本号。版本必须遵循SemVer2标准。

1
2
3
# nginx chart的版本字段version: 1.2.3
# 按照名称设置为
nginx-1.2.3.tgz

Chart.yaml文件中的version字段被很多Helm工具使用。当生成一个包时,helm package命令可以用Chart.yaml文件中找到的版本号作为包名的token。系统假设chart包名中的版本号可以与Chart.yaml文件中的版本号匹配。如果不满足这一假设会导致错误。


apiVersion字段

对于至少需要Helm3的chart,apiVersion字段应该是v2


kubeVersion字段

可选的kubeVersion字段可以在支持的k8s版本上定义语义约束,Helm 在安装chart时会验证这个版本约束, 并在集群运行不支持的k8s版本时显示失败。

版本约束可以包含空格、比较操作符、逻辑操作符。

1
2
3
4
5
>= 1.13.0 < 1.15.0

>= 1.13.0 < 1.14.0 || >= 1.14.1 < 1.15.0

1.1 - 2.3.4


deprecated字段

在Chart仓库管理chart时,有时需要废弃一个chart。deprecated字段可用来标记已弃用的chart。如果latest版本被标记为已弃用,则所有的chart都会被认为是已弃用的。以后可以通过发布未标记为已弃用的新版本来重新使用chart名称。

kubernetes/charts项目遵循的弃用charts的流程为:

  • 升级chart的Chart.yaml文件,将这个文件标记为已弃用,并更改版本
  • 在chart仓库中发布新版的chart
  • 从源仓库中移除这个chart


type字段

type字段定义了chart的类型。有两种类型:

  • application:默认类型,是可以完全操作的标准chart。
  • library:不能安装,提供针对chart构建的实用程序和功能。通常不包含任何资源对象。

应用类型chart 可以作为库类型chart使用。可以通过将类型设置为library来实现。 然后这个库就被渲染成了一个库类型chart,所有的实用程序和功能都可以使用。所有的资源对象不会被渲染。




许可证和描述

Chart LICENSE, README and NOTES

Chart也可以包含描述安装、配置和使用的文件,以及chart许可证。

LICENSE是一个包含了chart license的纯文本文件。chart可以包含一个许可证,因为在模板里不只是配置,还可能有编码逻辑。如果需要,还可以为chart安装的应用程序提供单独的许可证。

README自述文件,一般包含:

  • chart提供的应用或服务的描述
  • 运行chart的先决条件或要求
  • values.yaml的可选项和默认值的描述
  • 与chart的安装或配置相关的其它信息

chart也会包含一个简短的纯文本templates/NOTES.txt文件,这会在安装后及查看版本状态时打印出来。由于此文件是在运行helm installhelm status时打印到STDOUT的,因此建议保持内容简短,并指向自述文件以获取更多详细信息。




依赖

Chart Dependencies

Helm中,chart可能会依赖其它任意个chart。这些依赖可使用dependencies字段(Chart.yaml)动态链接,或写入charts/目录。


dependencies字段

当前chart依赖的其它chart会在dependencies字段定义为一个列表。

1
2
3
4
5
6
7
dependencies:
- name: apache
version: 1.2.3
repository: https://example.com/charts
- name: mysql
version: 3.2.1
repository: https://another.example.com/charts

必须使用helm repo add在本地添加仓库。

一旦你定义好了依赖,运行helm dependency update就会使用你的依赖文件下载所有你指定的chart到你的charts/目录。


alias字段

为依赖chart添加一个别名,会使用别名作为新依赖chart的名称。 需要使用其他名称访问chart时可以使用alias

1
2
3
4
5
6
7
8
9
10
11
12
dependencies:
- name: subchart
repository: http://localhost:10191
version: 0.1.0
alias: new-subchart-1
- name: subchart
repository: http://localhost:10191
version: 0.1.0
alias: new-subchart-2
- name: subchart
repository: http://localhost:10191
version: 0.1.0


tags和condition字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
1
2
3
4
5
6
#values.yaml
subchart1:
enabled: true
tags:
front-end: false
back-end: true
1
2
# 可以在CLI使用--set参数来设置标签和条件值
helm install --set tags.front-end=true --set subchart2.enabled=false


通过依赖导入sub values

在某些情况下,允许子chart的值作为公共默认传递到父chart中是值得的。

1
2
3
4
5
6
7
8
# parent's Chart.yaml file

dependencies:
- name: subchart
repository: http://localhost:10191
version: 0.1.0
import-values:
- data
1
2
3
4
5
# child's values.yaml file

exports:
data:
myint: 99


通过charts目录手动管理依赖

如果对依赖进行更多控制,通过将有依赖关系的chart复制到charts/目录中来显式表达这些依赖关系。

要将依赖放入charts/目录,使用helm pull命令。




Templates and Values

Helm Chart模板是按照Go模板语言书写。让我想起了Django模板语言,Jinja2模板语言。

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

模板的Value通过两种方式提供:

  • 通过values.yaml文件提供,此文件包含了默认值。
  • 用户可以提供一个包含value的yaml文件,在helm install时使用它。

当用户提供自定义的value时,会覆盖values.yaml中的值。


模板文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 }}


预定义的Values

以下值是预定义的,对每个模板都有效,并且可以被覆盖。和所有值一样,名称 区分大小写:

  • Release.Name: 版本名称(非chart的)
  • Release.Namespace: 发布的chart版本的命名空间
  • Release.Service: 组织版本的服务
  • Release.IsUpgrade: 如果当前操作是升级或回滚,设置为true
  • Release.IsInstall: 如果当前操作是安装,设置为true
  • Chart: Chart.yaml的内容。因此,chart的版本可以从Chart.Version获得, 并且维护者在Chart.Maintainers
  • Files:chart中的包含了非特殊文件的类图对象
  • Capabilities: 包含了Kubernetes版本信息的类图对象


范围

Scope, Dependencies, and Values

Values文件可以声明顶级chart的值,以及charts/目录中包含的其他任意chart。


全局Values

Helm支持特殊的global值。

1
2
global:
app: MyWordPress

这个值以.Values.global.app在所有chart中有效。


架构文件

有时候,chart容器可能想基于它们的values值定义一个结构,这可以在values.schema.json文件中定义一个架构实现。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"$schema": "https://json-schema.org/draft-07/schema#",
"properties": {
"image": {
"description": "Container Image",
"properties": {
"repo": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"type": "object"
},
"name": {
"description": "Service name",
"type": "string"
},
"port": {
"description": "Port",
"minimum": 0,
"type": "integer"
},
"protocol": {
"type": "string"
}
},
"required": [
"protocol",
"port"
],
"title": "Values",
"type": "object"
}

这个架构会应用values值并验证它。当执行以下任意命令时会进行验证: helm install, helm upgrage, helm lint, helm template




用户自定义资源

Custom Resource Definitions

k8s提供了一种声明k8s新类型对象的机制。使用CustomResourceDefinition(CRD),k8s开发者可以声明自定义资源类型。

Helm3中,CRD被视为一种特殊的对象。它们被安装在chart的其他部分之前,并受到一些限制。

CRD YAML文件应被放置在chart的crds/目录中。 多个CRD(用YAML的开始---和结束符...分隔)可以被放置在同一个文件中。Helm会尝试加载CRD目录中所有的文件到k8s。

当Helm安装新chart时,会上传CRD,暂停安装直到CRD可以被API服务使用,然后启动模板引擎, 渲染chart其他部分,并上传k8s。


CRD的限制

不像大部分k8s对象,CRD是全局安装的。因此Helm管理CRD时会采取非常谨慎的方式。 CRD受到以下限制:

  • CRD从不重新安装。 如果Helm确定crds/目录中的CRD已经存在(忽略版本),Helm不会安装或升级。
  • CRD从不会在升级或回滚时安装。Helm只会在安装时创建CRD。
  • CRD从不会被删除。自动删除CRD会删除集群中所有命名空间中的所有CRD内容。因此Helm不会删除CRD。

希望升级或删除CRD的操作员应该谨慎地手动执行此操作。




管理chart

Using Helm to Manage Charts

helm工具有一些命令用来处理chart。

1
2
3
4
5
6
7
8
# 创建新chart
helm create mychart

# 打包
helm package mychart

# 格式信息
helm lint mychart




仓库

Chart Repositories

helm用来管理本地chart目录时, 共享chart时,首选的机制就是使用chart仓库。

仓库的主要特征存在一个名为index.yaml的特殊文件,文件中包含仓库提供的包的完整列表, 以及允许检索和验证这些包的元数据。

在客户端,仓库使用helm repo命令管理。然而,Helm不提供上传chart到远程仓库的工具。 这是因为这样做会给执行服务器增加大量的必要条件,也就增加了设置仓库的障碍。




Starter Packs

helm create命令可以附带一个可选的--starter选项指定一个starter chart。Starter就只是普通chart,但是被放置在$XDG_DATA_HOME/helm/starters




Hooks

Chart Hooks: https://helm.sh/docs/topics/charts_hooks/

Helm提供了一个hook机制,使chart开发者在发行版(release)生命周期的特定点进行干预。你可以使用hooks做以下事情:

  • 安装过程中,在chart载入之前载入configmap或secret。
  • 在安装一个新chart之前,执行一个作业(job)来备份数据库,然后执行第二个作业还原数据库。
  • 在删除一个release之气,运行一个作业,在移除之前,来优雅地取出服务轮询。

hooks工作像常规模板,但它们有特殊的注释(写在annotations下),因此helm可以不同地使用它们。本章节,我们将介绍hooks的基本使用模式。

1
2
annotations:
"helm.sh/hook": post-install




可用的hooks

Annotation Value Description
pre-install - 模板渲染之后执行,但在k8s创建任何资源之前
post-install - 所有资源载入k8s后执行
pre-delete - 在从k8s删除任意资源前,执行一个删除请求
post-delete - 在所有release的资源被删除后,执行一个删除请求
pre-upgrade - 在模板渲染后,执行一个升级请求,但在任意资源升级之前
post-upgrade - 在所有资源都升级后,执行一个升级
pre-rollback - 在模板渲染后,执行一个回滚请求,但在任意资源回滚前
post-rollback - 在所有资源都被修改后,执行一个回滚请求
test - 当heml test子命令调用时执行




测试

Chart Tests: https://helm.sh/docs/topics/chart_tests/

chart包含许多k8s资源和协同工作的组件。作为包作者,你可能想编写一个测试,来验证包安装时是否如预期那样工作。

helm chart中的测试位于templates/目录下,是一个作业(job)定义,指定一个容器运行特定的命令。容器成功退出(exit 0),被认为测试成功。作业定义必须包含helm.sh/hook: test的注释。

示例测试:

  • 验证values.yaml文件被正确配置
  • 验证服务、负载均衡正常
  • 等等

可在helm中运行预定义测试,在release上使用helm test <RELEASE_NAME>命令。对于包的使用者,这是一个检测release of chart工作正常的方式。


示例

Example Test

1
2
3
4
5
6
7
8
9
10
helm repo add bitnami https://charts.bitnami.com/bitnami
helm pull bitnami/wordpress --untar

wordpress/
Chart.yaml
README.md
values.yaml
charts/
templates/
templates/tests/test-mariadb-connection.yaml

templates/tests/test-mariadb-connection.yaml的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{{- if .Values.mariadb.enabled }}
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-credentials-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: {{ .Release.Name }}-credentials-test
image: {{ template "wordpress.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy | quote }}
{{- if .Values.securityContext.enabled }}
securityContext:
runAsUser: {{ .Values.securityContext.runAsUser }}
{{- end }}
env:
- name: MARIADB_HOST
value: {{ template "mariadb.fullname" . }}
- name: MARIADB_PORT
value: "3306"
- name: WORDPRESS_DATABASE_NAME
value: {{ default "" .Values.mariadb.db.name | quote }}
- name: WORDPRESS_DATABASE_USER
value: {{ default "" .Values.mariadb.db.user | quote }}
- name: WORDPRESS_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ template "mariadb.fullname" . }}
key: mariadb-password
command:
- /bin/bash
- -ec
- |
mysql --host=$MARIADB_HOST --port=$MARIADB_PORT --user=$WORDPRESS_DATABASE_USER --password=$WORDPRESS_DATABASE_PASSWORD
restartPolicy: Never
{{- end }}


运行测试:

1
2
3
helm install quirky-walrus wordpress --namespace default

helm test quirky-walrus


注意:

  • 你可以在templates/目录下定义许多测试
  • 你可以嵌套你的测试<chart-name>/templates/tests/
  • 一个测试就是一个helm hook




Library

Library Charts: https://helm.sh/docs/topics/library_charts/

A library chart is a type of Helm chart,定义chart可通过helm模板在其它charts中共享。




完整性校验

Helm Provenance and Integrity: https://helm.sh/docs/topics/provenance/

helm有来源工具,帮助chart user验证包的来源和完整性。使用基于行业标准的PIK, GnuPG等备受推崇的包管理器,Helm 可以生成和验证签名文件。


1
2
3
4
5
6
7
8
# 生成
helm package --sign ...
helm package --sign --key 'John Smith' --keyring path/to/keyring.secret mychart

# 校验
helm install --verify
helm verify mychart-0.1.0.tgz
helm install --generate-name --verify mychart-0.1.0.tgz




仓库

Chart Repository: https://helm.sh/docs/topics/chart_repository/

官方的chart repo由Kubernetes Charts项目维护。欢迎各位参与。Helm也使得创建和运行自己的chart repo变得很容易。


创建仓库

Create a chart repository: https://helm.sh/docs/topics/chart_repository/

一个chart repo是一个HTTP服务器,它容纳了一个index.yaml文件和一些包。当你准备好分享你的charts,方法是将它们上传到一个chart repository。你可以使用GCS, S3, GitHub Pages等来创建你自己的web服务器。




注册中心

Registries: https://helm.sh/docs/topics/registries/

Helm 3 支持OCI用于包分发。 Chart包可以通过基于OCI的注册中心存储和分发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 激活对OCI的支持
export HELM_EXPERIMENTAL_OCI=1


# 运行一个注册中心
docker run -dp 5000:5000 --restart=always --name registry registry


# 认证
htpasswd -cB -b auth.htpasswd myuser mypass
docker run -dp 5000:5000 --restart=always --name registry \
-v $(pwd)/auth.htpasswd:/etc/docker/registry/auth.htpasswd \
-e REGISTRY_AUTH="{htpasswd: {realm: localhost, path: /etc/docker/registry/auth.htpasswd}}" \
registry


# 登录
helm registry login -u myuser localhost:5000


# 注销
helm registry logout localhost:5000


# 保存
helm chart save mychart/ localhost:5000/myrepo/mychart:2.7.0

# 查看
helm chart list

# 导出
helm chart export localhost:5000/myrepo/mychart:2.7.0

# 推送到远程
helm chart push localhost:5000/myrepo/mychart:2.7.0

# 从缓存中移除
helm chart remove localhost:5000/myrepo/mychart:2.7.0

# 从远程拉取
helm chart pull localhost:5000/myrepo/mychart:2.7.0


使用上述命令存储的chart会被缓存到文件系统中。OCI 镜像设计规范 严格遵守文件系统布局的。如:

1
2
3
4
5
6
7
8
9
10
11
12
tree ~/Library/Caches/helm/
└── registry
├── cache
│ ├── blobs
│ │ └── sha256
│ │ ├── 1b251d38cfe948dfc0a5745b7af5ca574ecb61e52aed10b19039db39af6e1617
│ │ ├── 31fb454efb3c69fafe53672598006790122269a1b3b458607dbe106aba7059ef
│ │ └── 8ec7c0f2f6860037c19b54c3cfbab48d9b4b21b485a93d87b64690fdb68c2111
│ ├── index.json
│ ├── ingest
│ └── oci-layout
└── config.json




架构

Helm Architecture: https://helm.sh/docs/topics/architecture/

介绍Helm在高级别的架构。


目的

The Purpose of Helm

Helm是管理称为chart的k8s包的工具。Helm可以做以下事情:

  • 从头开始创建一个新的charts
  • packages charts为归档(tgz) chart文件
  • 与chart repo交互,并存储在那
  • 安装和卸载charts到k8s集群
  • 管理已安装的charts的发行版

对于Helm,有三个重要的概念:

  • chart是创建一个k8s应用实例所需的信息束
  • config包含配置信息,可以合并到package chart来创建一个可发行的对象
  • release是一个运行的chart实例,包含特定的配置




组件

Components

Helm被实现为两个不同部分来执行:

Helm CLI客户端,负责以下事情:

  • 本地chart开发
  • 管理repo
  • 管理release
  • 与Helm Library接口
    • 发送chart安装
    • 请求升级或卸载releases

Helm Library提供了执行所有helm操作的逻辑。与k8s API接口交互,并提供以下功能:

  • 组合chart和配置来构建一个release
  • 安装chart到k8s,并提供release对象
  • 通过与k8s交互,升级和卸载chart




实现

Implementation

Helm client和library由go编写。library使用k8s client与k8s集群通信。目前,library使用REST+JSON。它存储信息在k8s内的secrets里,不需要自己的数据库。配置文件以YAML编写。




高级技术

Advanced Helm Techniques: https://helm.sh/docs/topics/advanced/


后置渲染

Post Rendering




GO SDK




后端存储

Storage backends




RBAC

Role-based Access Control: https://helm.sh/docs/topics/rbac/

k8s rbac: https://kubernetes.io/docs/reference/access-authn-authz/rbac/

介绍Helm如何与k8s RBAC进行交互。

在k8s中,授权角色给特定用户或应用的服务账号(service account),以确保应用程序的操作在特定范围内。从k8s v1.6开始,RBAC默认启用。

使用RBAC,你可以:

  • 授权特权操作给管理员
  • 限制用户在特定命名空间/集群范围创建资源的能力
  • 限制用户在特定命名空间/集群范围内查看资源的能力


管理用户账户

Managing user accounts

所有的k8s集群有两种类型的用户:

  • service accounts managed by Kubernetes
  • normal users

普通用户假定由外部进行管理,独立的服务。管理员分发私钥,用户存储密码,甚至是用户名密码列表这样的文件。在这方面,k8s不具有代表普通用户账户的对象。普通用户无法通过API调用被添加到集群。

相比之下,服务账号是由k8s API管理的用户。它们被绑定到特定的命名空间,通过API server自动创建,或通过API调用手动创建。服务账号绑定在一组凭据里,存储为secret,它被挂载到pod,允许集群内进程与k8s API进行交谈。

API请求被绑定到任何一个用户(普通用户、服务账号),或者被视为匿名请求。这意味着集群内或集群外的每一个进程,从工作站上输入kubectl的人类用户,到节点上的kubelets,到控制面板的成员,在进行请求API server时必须进行认证,或被视为匿名用户。




角色、集群角色、角色绑定、集群角色绑定

Roles, ClusterRoles, RoleBindings, and ClusterRoleBindings

在k8s中,用户账户和服务账户只能够根据授权访问来查看和修改资源。这种授权是通过使用角色(Roles)角色绑定(RoleBindings)。角色和角色绑定被绑定到特定的命名空间,它通过角色提供授权,授予用户在此命名空间内查看或修改资源的能力。

在集群范围内,这些被称为集群角色(ClusterRoles)集群角色绑定(ClusterRoleBindings)。授权用户集群角色,允许它们访问和修改整个集群的资源。这也需要查看和修改集群范围(命名空间,资源配额,节点)的资源。

集群角色可通过角色绑定的引用来绑定到特定的命名空间。admin, edit, view是最常使用的默认集群角色。

k8s有一些默认的集群角色可用,它们的本意是面向用户的角色。它们包含超级角色(cluster-admin),和细粒度访问的角色(admin, edit, view)。


Default ClusterRole Default ClusterRoleBinding 描述
cluster-admin system:masters group 允许超级用户访问对任意资源执行任意动作。
admin None 允许管理员访问,在命名空间内使用角色绑定来授权。如读写命名空间内的大部分资源,包括在命名空间内创建角色和角色绑定的能力。但不允许对资源配额或命名空间进行写操作。
edit None 允许在命名空间内读取大多数对象的权限,不允许查看或修改角色和角色绑定
view None 允许在命名空间内查看大多数对象的权限。不允许查看角色和角色绑定。不允许查看secrets。




限制用户账户使用RBAC访问

Restricting a user account access using RBAC

现在让我们了解基于角色的访问控制的基础知识,让我们讨论管理员如何限制用户的访问范围。


示例:授予用户命名空间范围的读写权限

Grant a user read/write access to a particular namespace

要限制用户对特定命名空间的读写权限,可以使用editadmin角色。

此外,你还可以使用cluster-admin来创建一个角色绑定。授予在命名空间范围内的cluster-admin来提供在此命名空间内完整控制资源的权限,包含命名空间自身。

1
2
3
4
5
6
7
8
# 创建ns
kubectl create namespace foo

#创建RoleBinding
kubectl create rolebinding sam-edit
--clusterrole edit \
--user sam \
--namespace foo


示例:授予用户集群范围的读写权限

Example: Grant a user read/write access at the cluster scope

如果用户希望安装chart,在集群范围内安装集群资源(ns, roles, crd…),它们将需要集群范围的写权限。要这样做,授予用户admincluster-admin角色权限。

1
2
3
4
5
6
7
8
kubectl create clusterrolebinding sam-view
--clusterrole view \
--user sam


kubectl create clusterrolebinding sam-secret-reader
--clusterrole secret-reader \
--user sam


示例:授予用户命名空间范围的只读权限

Example: Grant a user read-only access to a particular namespace

你可能注意到了,没有查看secret的集群角色。view集群角色没有授予用户访问secret的权限。然而,Helm默认将release metadata存储为secret。

为了使用户运行helm list,它需要读取这些secrets。为此,我们将创建一个特殊的secret-reader集群角色。

1
2
3
4
5
6
7
8
9
# cluster-role-secret-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl create -f clusterrole-secret-reader.yaml

kubectl create namespace foo

kubectl create rolebinding sam-view
--clusterrole view \
--user sam \
--namespace foo


kubectl create rolebinding sam-secret-reader
--clusterrole secret-reader \
--user sam \
--namespace foo


示例:授予用户集群范围的只读权限

Example: Grant a user read-only access at the cluster scope

如果用户想运行helm l ist --all-namespaces命令,API需要用户拥有集群范围内的读权限。

1
2
3
4
5
6
7
kubectl create clusterrolebinding sam-view
--clusterrole view \
--user sam

kubectl create clusterrolebinding sam-secret-reader
--clusterrole secret-reader \
--user sam




插件

The Helm Plugins Guide: https://helm.sh/docs/topics/plugins/

Helm plugin是一个可通过helm CLI访问的工具,但不是内置的helm基础代码的一部分。




V2迁移到V3

Migrating Helm v2 to v3: https://helm.sh/docs/topics/v2_v3_migration/




弃用的k8s api

Deprecated Kubernetes APIs: https://helm.sh/docs/topics/kubernetes_apis/




版本支持

Helm Version Support Policy: https://helm.sh/docs/topics/version_skew/




SQL存储后端的权限管理

Permissions management for SQL storage backend: https://helm.sh/docs/topics/permissions_sql_storage_backend/








最佳实践

The Chart Best Practices Guide: https://helm.sh/docs/chart_best_practices/

涵盖了Helm团队对创建chart的最佳做法。它侧重于chart应该如何构造。主要关注那些可能会公开部署的charts的最佳实践。


一般约定

General Conventions: https://helm.sh/docs/chart_best_practices/conventions/


chart名称

Chart Names

chart名称必须是小写字母和数字,可用-隔开。

chart目录必须与chart名称相同。

1
2
3
4
5
6
7
# 示例
drupal
nginx-lego
aws-cluster-autoscaler

nginx-lego
nginx-lego/




版本号

Version Numbers

只要有可能,Helm使用SemVer2来表示版本号。请注意,Docker image tag并不一定遵循SemVer,注意。




格式化YAML

Formatting YAML

YAML应该使用两个空格(别使用tab)。




Usage of the Words Helm and Chart

Helm词的一些约定:

  • Helm指作为一个整体的项目
  • helm客户端CLI
  • chart不需要大写,它不是专有名词
  • Chart.yaml需要大写,因为该文件名是大小写敏感的




Values: https://helm.sh/docs/chart_best_practices/values/

提供给你如何组织和设计chart的values.yaml文件,并使用你的值。


命名约定

Naming Conventions

变量名必须小写字母开头,使用驼峰分开:

1
2
chicken: true
chickenNoodleSoup: true

请注意,所有Helm内置变量以大写字母开头,用户可以轻松区分开:

1
2
.Release.Name
.Capabilities.KubeVersion




嵌套值

YAML是一种灵活的格式,值可以被深度嵌套。

1
2
3
server:
name: nginx
port: 80
1
2
3
{{ if .Values.server }}
{{ default "none" .Values.server.name }}
{{ end }}




使类型清晰

Make Types Clear

1
2
3
4
5
6
7
8
9
10
# YAM的类型强制规则有时是反直觉的。例如一下两者是不同的
foo: false
foo: "false"

# 避免类型转化错误的最简单的方法是要明确字符串和隐含的一切。使用引号引用字符串
# 要避免整数转换错误,将整数存储为字符串,使用以下方法来获取整数值
{{ int $value }}

# 在大多数情况下,显式类型标签被尊重。如以下1234被当作字符串
foo: !!string 1234




考虑用户如何使用你的值

Consider How Users Will Use Your Values

值有三个来源:

  • values.yaml文件
  • helm install -fhelm upgrade -f时指定的文件里
  • --set--set-string选项指定

当设计值的组织结构时,用户是希望可通过-f--set选项来覆盖它们。YAML建议写成映射(mapping),便于替换--set servers.foo.port=80

1
2
3
4
5
servers:
foo:
port: 80
bar:
port: 81




values.yaml

每个在values.yaml中定义的属性应该被记录(documented)。文档字符串应该用它描述的属性的名称开始,然后给出至少一个单句描述。

1
2
3
4
# serverHost is the host name for the webserver
serverHost: example
# serverPort is the HTTP listener port for the webserver
serverPort: 9191




模板

Templates: https://helm.sh/docs/chart_best_practices/templates/


templates目录架构

Structure of templates/

templates/目录应该是如下结构:

  • 模板文件是.yaml扩展的YAML输出。.tpl扩展可用于未经格式化的模板文件
  • 模板文件名应使用虚线(example-configmap.yaml),而不是驼峰
  • 每个资源定义应该有自己的模板文件
  • 模板文件应放映资源类型(如foo-pod.yaml, bar-svc.yaml




定义的模板的名称

Names of Defined Templates

定义的模板(模板文件内的)是全局访问的。这意味着,chart和它的subchart可以访问所有创建的模板。这样我想起了Pythond的模板语言(Django模板语言,Jinja2等等)。

出于此原因,所有定义的模板名称都应该命名空间。

1
2
3
{{- define "nginx.fullname" }}
{{/* ... */}}
{{ end -}}

It is highly recommended that new charts are created via helm create command as the template names are automatically defined as per this best practice.




格式化模板

Formatting Templates

模板应该使用两个空格,而不是tab。花括号前后应该有空格。有适当的空格和缩进。

1
2
3
4
5
6
7
{{ .foo }}
{{ print "foo" }}
{{- print "bar" -}}

{{ if $foo -}}
{{- with .Bar }}Hello{{ end -}}
{{- end -}}




生成模板中的空格

Whitespace in Generated Templates

优选的是,保持在生成的模板中的空格数量降到最低。特别是,许多空行不应出现彼此相邻。但偶尔空行还是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# This is best
apiVersion: batch/v1
kind: Job
metadata:
name: example
labels:
first: first
second: second


# This is okay
apiVersion: batch/v1
kind: Job

metadata:
name: example

labels:
first: first
second: second




注释

Comments (YAML Comments vs Template Comments)

YAML文件注释和模板注释。当一个模板记录功能时,应该使用模板注释。当Helm用户通过查看注释调试时,在模板内应该使用YANML注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# yaml 注释


{{- /*
模板注释
*/ -}}


{{- /*
mychart.shortname provides a 6 char truncated version of the release name.
*/ -}}
{{ define "mychart.shortname" -}}
{{ .Release.Name | trunc 6 }}
{{- end -}}


# This may cause problems if the value is more than 100Gi
memory: {{ .Values.maxMem | quote }}




在模板和模板输出中使用JSON

Use of JSON in Templates and Template Output

YAML是JSON的超集(superset)。在一些情况下,使用JSON语法可比其它YAML表示更具有可读性。

1
2
3
4
5
6
7
8
9
# 列表

# yaml
arguments:
- "--dirname"
- "/foo"

# json
arguments: ["--dirname", "/foo"]




依赖

Dependencies: https://helm.sh/docs/chart_best_practices/dependencies/

介绍Chart.yaml内声明的dependencies的最佳实践。


版本

Versions

如果可能的化,使用版本范围,而不是某个确切的版本。建议使用补丁级别(patch-level)版本匹配:

1
2
# >= 1.2.3, < 1.3.0
version: ~1.2.3

repo ruls,如果可能,使用HTTPS。文件URL(file://...)被认为是一个特殊,对由一个固定部署的流水线charts。




条件和标记

Conditions and Tags

条件或标记应被添加到任何依赖(可选的)。

1
2
3
4
5
6
7
# 条件的推荐格式
condition: somechart.enabled


# 标记
tags:
- webaccelerator




标签和注释

Labels and Annotations: https://helm.sh/docs/chart_best_practices/labels/

讨论chart中使用标签和注释的最佳实践。


标签还是注释

Is it a Label or an Annotation?

以下条件的元数据项应该为标签(label):

  • 它利用k8s来标识此资源
  • 暴露给查询系统的目的是有用的

如果元数据的条目不用于查询,它应该设置为注释。Helm hooks总是注释。




标准的标签

Standard Labels

下表定义了Helm chart常用的标签。Helm自身从未要求特定的标签存在。REC的标签是建议的,并应该放置到全局一致性的chart。OPT的标签是可选的。

1
2
3
4
5
6
7
8
名称    状态   描述
app.kubernetes.io/name REC 这应该是app名称。通常使用{{ template "name" . }} 这由许多k8s manifests使用,不是Helm特定的
helm.sh/chart REC chart名称和版本: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/managed-by REC 这应该始终设置为{{ .Release.Service }}
app.kubernetes.io/instance REC 这应该为{{ .Release.Name }},有助于在同意应用不同实例之间进行区分
app.kubernetes.io/version OPT 应用的版本设置为{{ .Chart.AppVersion }}
app.kubernetes.io/component OPT This is a common label for marking the different roles that pieces may play in an application
|app.kubernetes.io/part-of OPT 当多个charts或软件片一起使用来做一个应用

可在k8s 文档中,带有app.kubernetes.io前缀的文档中查看更多信息。




Pods和PodTemplates

Pods and PodTemplates: https://helm.sh/docs/chart_best_practices/pods/

以下资源列表使用PodTemplate:

  • Deployment
  • ReplicationController
  • ReplicaSet
  • DaemonSet
  • StatefulSet


镜像

Images

容器镜像应该使用确定的标记或镜像的SHA。但不应该使用latest, head, canary这样的标记。

镜像可以在values.yaml文件中定义,使其很容易替换镜像。

1
image: {{ .Values.redisImage | quote }}

镜像和标记可在values.yaml中被定义为分开的两个字段:

1
image: "{{ .Values.redisImage }}:{{ .Values.redisTag }}"




镜像拉取策略

ImagePullPolicy

helm create默认在deployment.yaml中设置imagePullPolicyIfNotPresent

1
imagePullPolicy: {{ .Values.image.pullPolicy }}
1
2
3
# values.yaml
image:
pullPolicy: IfNotPresent

同样,如果未设置impagePullPolicy,k8s默认会将其设置为IfNotPresent。如果想要修改此值,只需在values.yaml文件中更新此值。




PodTemplate应该声明选择器

PodTemplates Should Declare Selectors

所有的PodTemplate部分应该指定一个选择器。示例:

1
2
3
4
5
6
7
selector:
matchLabels:
app.kubernetes.io/name: MyName
template:
metadata:
labels:
app.kubernetes.io/name: MyName

这是一个很好的做法,因为它使set和pod相关联。

但是,这对于像Deployment这样的集更为重要。没有这一点,整个标签集(set of labels)用于选择匹配pod,如果你使用的标签发生改变(如版本或日期),这将打破匹配。




自定义资源的定义

Custom Resource Definitions

当使用自定义资源定义(CRDs),区分两种不同的片是很重要的:

  • 声明一个CRD(kind: CustomResourceDefinition)
  • 然后资源使用CRD


使用资源前安装CRD声明

Install a CRD Declaration Before Using the Resource

Helm是尽可能优化地载入更多的资源到k8s中。按照设计,k8s可以采取一整套清单(manifests),并带它们所有上线(这就是所谓的和解循环(reconciliation loop)))。

但是,CRDs有一些不同。对于CRD,在任意CRDs类型资源被使用之前,必须先注册声明。注册过程有时需要几秒。


方法1:让helm为你做此事

Method 1: Let helm Do It For You

随着Helm3的到来,出于更简单的方法,Helm移除了旧的crd-install hooks。这在是一个称为crds的新目录,在你创建的chart的此目录下保存你的CRDs。这些CRDs没有模板,但会在chart运行helm install时默认安装。如果CRD已存在,它会被跳过。你也可以通过传递--skip-crds选项来跳过CRD的安装。

一些注意事项:

目前不支持使用Helm更新或删除CRDs。这是一个经过反复讨论的明确的决定,由于存在非故意丢失数据的危险。此外,目前社区如何处理CRDs和它的生命周期没有共识,由于这种演变,Helm将添加对这些用例的支持。

helm installhelm upgrade--dry-run选项暂不支持CRDs。Dry Run的目的是去验证chart的输出将实际地工作,如果发送到服务器。但CRDs可通过服务器行为的修改。Helm无法在dry run上安装CRD,因此发现客户端将不知道自定义资源(CR),并验证将失败。你可以可选地移动CRDs到它们自己的chart,或使用helm template来代替。

围绕CRD支持的另一个重要的考虑点是如何处理模板的渲染(rendering of templates)。一个在Helm2中使用crd-install方法的明显的缺点是不能正确验证chart,由于改变API可用性(一个CRD被实际添加到另一个可用API到k8s集群)。如果一个chart安装了CRD,helm不再有一组API版本的有效集。这也是在移除从CRDs的模板支持的原因。随着安装CRD的新的crds方法,我们现在确保helm有关于当前集群状态的完整信息。


方法2:独立chart

Separate Charts

另一种方法是,把CRD定义在一个chart中,然后把所有资源使用的该CRD放在另一个chart。

在此方法中,每个char都必须单独安装。然而,这个工作流程可能是集群操作器(cluster operators)(对集群拥有admin权限)使用。




RBAC

Role-Based Access Control: https://helm.sh/docs/chart_best_practices/rbac/

RBAC资源有:

  • ServiceAccount (namespaced)
  • Role (namespaced)
  • ClusterRole
  • RoleBinding (namespaced)
  • ClusterRoleBinding


YAML配置

RBAC和ServiceAccount配置因该在单独的密钥里。它们是不同的东西。拆分这两个概念在YAML歧义消除它们,使之更清楚。

1
2
3
4
5
6
7
8
9
10
rbac:
# Specifies whether RBAC resources should be created
create: true

serviceAccount:
# Specifies whether a ServiceAccount should be created
create: true
# The name of the ServiceAccount to use.
# If not set and create is true, a name is generated using the fullname template
name:

多个服务账号可以扩展为更复杂的charts。

1
2
3
4
5
6
7
8
someComponent:
serviceAccount:
create: true
name:
anotherComponent:
serviceAccount:
create: true
name:




RBAC资源应该被默认创建

RBAC Resources Should be Created by Default

rbac.create应该是一个布尔值,由RBAC资源来控制创建。默认应该为true。希望管理RBAC访问控制的用户可以将此设置为false。




使用RBAC资源

Using RBAC Resources

serviceAccount.name应该被设置为由chart创建的访问控制资源使用的服务账号名称。如果serviceAccount.create为true,那么此名称的服务名称应该被创建。如果此名称未设置,则使用模板fullname来生成。如果为false,则它不应该被创建,但它应该与同样的资源相关联,以便创建后引用该手动创建RBAC资源正常工作。如果为false且没有指定名称,则使用默认的服务账号。

下面的助手模板应该用于服务账号:

1
2
3
4
5
6
7
8
9
10
{{/*
Create the name of the service account to use
*/}}
{{- define "mychart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "mychart.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}








模板

Chart Template: https://helm.sh/docs/chart_template_guide/

Helm‘s chart templates,重点介绍模板语言。让我想起的Django模板语言、Jinja2模板语言。

模板生成清单文件,这是k8s可理解的YAML格式的资源描述。本章重点介绍以下概念:

  • Helm模板语言
  • Values使用
  • 使用模板的技术


入门

Getting Started: https://helm.sh/docs/chart_template_guide/getting_started/

创建一个chart并添加一个模板。


Charts

1
2
3
4
5
6
mychart/
Chart.yaml
values.yaml
charts/
templates/
...

templates/目录存放模板文件。当Helm评估一个chart,它会发送所有模板目录中的文件到模板渲染引擎。然后,它收集模板的结果,并将它们发送到k8s。

values.yaml文件对模板也很重要。此文件包含了一个chart的默认值。默认值可通过命令行选项进行覆盖。

Chart.yaml文件包含对chart包的描述信息。你可在模板中访问它。charts/目录可能包含其它chats(称为subcharts)。




示例

A Starter Chart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 创建一个名为mychart的chart包
helm create mychart
Creating mychart


# 目录结构
tree ./mychart -L 2
./mychart
├── charts
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── _helpers.tpl #模板助手,你可以重新使用整个chart
│   ├── hpa.yaml #
│   ├── ingress.yaml
│   ├── NOTES.txt #chart包的帮助文本(help text),会在运行helm install显示
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
└── values.yaml


# 创建自己的模板
rm -rf mychart/templates/*

当编写生产环境的chart包时,有这些charts包的基础版本可能很有用。




第一个模板

A First Template

创建一个ConfigMap资源的模板。由于它是一个基本的资源,因此它为我们提供了一个很好的起点。

1
2
3
4
5
6
7
8
# mychart/templates/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
name: mychart-configpmap
data:
myvalue: "Hello World"

小技巧:模板名称不遵循严格的命名模式。然而,我们建议为YAML文件使用.yaml后缀,为模板助手使用.tpl后缀。

上述YAML文件是一个最基本的ConfigMap,最有最小的必要的字段。它会通过模板引擎进行发送。

一个普通的平YAML文件是蛮好的。当Helm读取此模板,它会简单地将文件原样发送给k8s。

在这个简单的例子中,我们现在有了一个可安装的chart包。安装一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
helm install full-coral mychart
NAME: full-coral
LAST DEPLOYED: Sun Sep 27 10:38:03 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None


helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
full-coral default 1 2020-09-27 10:38:03.546664865 +0800 CST deployed mychart-0.1.0 1.16.0


helm get manifest full-coral
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mychart-configmap
data:
myvalue: "Hello World"


kubectl get configmap
NAME DATA AGE
mychart-configmap 1 9m47s


# 卸载
helm uninstall full-coral


添加一个简单的模板调用

Adding a Simple Template Call

硬编码的name,通常被认为是不好的做法。每个发行版的名称应该是唯一的。因此,我们可能将生成一个名称字段来写入发行版名称。

注意,由于DNS系统的限制,name字段被限制为63字符。出于这个原因,发行版名称被限制为53字符。

1
2
3
4
5
6
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 模板指令放置于{{ xxx }} 块内
helm install clunky-serval mychart/
NAME: clunky-serval
LAST DEPLOYED: Sun Sep 27 11:16:20 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None


helm get manifest clunky-serval
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: clunky-serval-configmap
data:
myvalue: "Hello World"


# 可使用--debug查看详情
# 下面将渲染模板,返回渲染输出,不会真正安装
helm install --debug --dry-run goodly-guppy ./mychart

使用--dry-run将更容易对代码进行测试,但它不会保证k8s会接受你生成的模板。




内置对象

Built-in Objects: https://helm.sh/docs/chart_template_guide/builtin_objects/

对象动模板引擎传递到模板。你的代码可以传递对象范围(如withrange)。有一些方法可在模板中创建新的对象,如tuple函数。

对象可以很简单,它只有一个值。它们也可以包含其它对象或函数。如,Realease对象可包含几个对象(如Release.Name),Files对象有一些函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- `Release`对象
- `Release.Name`
- `Release.Namespace`
- `Release.IsUpgrade`
- `Release.IsInstall`
- `Release.Revision`
- `Release.Service`:在Helm中,总是Helm
- `Values`: `values.yaml`中传递给模板的值
- `Chart`: `Chart.yaml`文件内容
- `Files`: 访问chart包中非模板的文件
- `Files.Get`: 通过名称生成文件的函数
- `Files.GetBytes`
- `Files.Glob`: 返回文件为列表的函数
- `Files.Lines`: 一行行读取文件的函数
- `Files.AsSecrets`: 返回文件内容为base64编码字符串的函数
- `Files.AsConfig`: 返回文件内容为YAML map的函数
- `Capabilities`
- `Capabilities.APIVersions`
- `Capabilities.APIVersions.Has $version`
- `Capabilities.KubeVersion`, `Capabilities.KubeVersion.Version`
- `Capabilities.KubeVersion.Major`
- `Capabilities.KubeVersion.Minor`
- `Template`
- `Template.Name`
- `Template.BasePath`

内置的值总以大写字母开头。这与go命名方式保持一致。




值文件

Values Files: https://helm.sh/docs/chart_template_guide/values_files/

Values是一个内置的对象。它提供了访问值并传递到chart包。值文件是平YAML文件。其内容来源于多个源:

  • chart包中的values.yaml文件
  • 如果是一个subchart包,则为parent chart包的values.yaml文件
  • 通过helm install/upgrade-f myvals.yaml传递值
  • 通过helm install/upgrade--set foo=bar选项传递值
1
2
3
4
5
6
7
8
9
10
11
12
# values.yaml
favoriteDrink: coffee


# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favoriteDrink }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 渲染
helm install geared-marsupi ./mychart --dry-run --debug
HOOKS:
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: geared-marsupi-configmap
data:
myvalue: "Hello World"
drink: coffee


# 通过命令行选项覆盖值
helm install solid-vulture ./mychart --dry-run --debug --set favoriteDrink=slurm
HOOKS:
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: solid-vulture-configmap
data:
myvalue: "Hello World"
drink: slurm

值文件也可以包含更多结构化的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# values.yaml
favorite:
drink: coffee
food: pizza


# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink }}
food: {{ .Values.favorite.food }}

虽然结构化数据这种方式是可行的,但建议你保持值的浅度(shallow),有利于平整。当看到subcharts包的值时,我们将看到值是如何使用树状结构命名的。




删除一个默认键

Deleting a default key

如果你需要从默认值删除一个键,你可以覆盖这个键的值为null,在这种情况下,Helm将从覆盖值得合并中移除这个键。




模板函数和管道

Template Functions and Pipelines: https://helm.sh/docs/chart_template_guide/functions_and_pipelines/

到目前为止,我们以将看到如何将信息转换为模板。但这些信息放入未修改的模板。有时候,我们希望以一种更可用的方式来转换提供的数据。

在模板指令中调用quote函数:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ quote .Values.favorite.drink }}
food: {{ quote .Values.favorite.food }}

模板函数使用funcationName arg1 arg2...语法。上面的quote .Values.favorite.drink调用quote函数并传递一个参数。

Helm有超过60个可用的函数。一些通过go模板语言定义。大多数是Sprig template library的一部分。


管道

pipelines(|)

模板语言的一个强大功能就是它的管道(|)。管道是让几件事情依序进行的有效方式。让我们使用管道重写上面的示例:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | quote }}
food: {{ .Values.favorite.food | quote }}

使用管道,我们可以将多个函数链接在一起:

1
2
3
4
5
drink: {{ .Values.favorite.drink | repeat 5 | quote }}
food: {{ .Values.favorite.food | upper | quote }}

#drink: "coffeecoffeecoffeecoffeecoffee"
#food: "PIZZA"




default函数

default函数经常在模板中使用(default DEFAULT_VALUE GIVEN_VALUE)。此函数允许你指定一个默认值。有则替换它,无则使用默认值。

1
drink: {{ .Values.favorite.drink | default "tea" | quote }}

在实际的chart包中,所有静态默认值都应该位于values.yaml中,而不应该使用default重复。然而,default命令对于不能在values.yaml中声明的值,是完美的计算值的方法。例如:

1
drink: {{ .Values.favorite.drink | default (printf "%s-tea" (include "fullname" .)) }}




lookup函数

lookup函数可用于在正在运行的集群中查找资源。它查找apiVersion, kind, namespace, name到resource或resource list。

1
2
3
4
5
6
7
8
9
10
11
12
# apiVersion, kind, namespace, name都是string
# name, namespace两个是可选的,可以为空进行传递


# 下列将会返回mynamespace对象的注释
(lookup "v1" "Namespace" "" "mynamespace").metadata.annotations


# 当lookup返回一个列表(list)对象时,可以通过items字段访问列表对象
{{ range $index, $service := (lookup "v1" "Service" "mynamespace" "").items }}
{{/* do something with each service */}}
{{ end }}

当没有找到对象时,则返回一个空值。这可以用于检查对象是否存在。

lookup函数使用Helm现有的k8s连接配置来查询k8s。如果调用API server进行交互时返回错误,则Helm的模板处理将失败。

请记住,Helm是不应该在helm templatehelm install|update|delete|rollback --dry-run期间连接到k8s API server,因此,lookup在此情况下将会获得一个空列表。




操作符

Operators are functions

对于模板,操作符(eq, ne, lt, gt, and, or等)都被实现为函数。在管道中,操作符可使用括号()进行分组。




函数列表

Template Function List: https://helm.sh/docs/chart_template_guide/function_list/

Helm包含很多模板函数,你可以在模板中使用它们。下面按照功能列出:

  • Cryptographic and Security
  • Date
  • Dictionaries
  • Encoding
  • File Path
  • Kubernetes and Chart
  • Logic and Flow Control
  • Lists
  • Math
  • Network
  • Reflection
  • Regular Expressions
  • Semantic Versions
  • String
  • Type Conversion
  • URL
  • UUID




流程控制

Flow Control: https://helm.sh/docs/chart_template_guide/control_structures/

控制结构(在模板原语中称为行动)提供给模板作者,模板生成的控制流程的能力。Helm的模板语言提供了如下控制结构:

  • if, else:创建条件块
  • with:指定一个范围
  • range: 提供一个类似的for循环

除此之外,它为声明和使用命名模板段提供了一些动作:

  • define:在模板内声明一个新的命名模板
  • template:导入一个命名的模板
  • block:声明一个特殊的可填写的模板区域

这些都让我想起之前用Django模板语言写前端的时候,基本上一样的原理。


if和else

if, else块示例:

1
2
3
4
5
6
7
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
1
2
3
4
5
6
7
8
9
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 }}




控制空格

Controlling Whitespace

虽然我们看到了条件语句,我们也应该了解模板中的空格的控制方式。这主要是确保对于生成的YAML文件的缩进的正确性。

1
2
3
4
5
6
7
8
9
10
11
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格式:

1
2
3
4
5
6
7
8
9
10
# 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被不正确地缩进。让我们修改模板:

1
2
3
4
5
6
7
8
9
10
11
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是有效的,但显得很滑稽:

1
2
3
4
5
6
7
8
9
10
11
# 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文件中生成了几个空行。为什么?当模板引擎运行时,它将删除花括号里的内容,但它留下的剩余空格完全一样。


YAML对空白很在意,所以管理空白变得非常重要。幸运的是,Helm有一些工具来帮助我们。

1
2
3
4
# 首先,模板声明的花括号 {{ 可以使用特殊字符进行修改,来告诉模板引擎排列空白
{{- 表示空白应靠左(chomped left)
-}} 表示空白应在右边消耗(right should be consumed)
# 注意,换行也是空白(Newlines are whitespace)


1
2
3
4
5
6
7
8
9
10
11
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 }}
1
2
3
4
5
6
7
8
9
10
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: clunky-cat-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: true

小心使用排列修改器(chomping modifier)。很容易不小心做了下面的事情:

1
2
3
4
food: {{ .Values.favorite.food | upper | quote }}
{{- if eq .Values.favorite.drink "coffee" -}}
mug: true
{{- end -}}

这会生成food: "PIZZA"mug:true这样,因为它消耗了两侧的换行。

1
2
3
# 最后,有时很容易告诉模板系统如何缩进,而不是试图掌握模板指令的空格。
# 出于此原因,你有时可能会发现使用 indent函数 处理缩进是很有用的
{{ indent 2 "mug: true" }}




使用with修改范围

Modifying scope using with

另一个控制结构是with动作。这可以控制变量的范围,.是指的当前范围。因此,.Values告诉模板到当前范围下去寻找Values对象。

1
2
3
4
# with语法和if语句类似
{{ with PIPELINE }}
# restricted scope
{{ end }}

范围可以被更改。with可以允许你将当前范围(.)设置为特定对象。例如,我们使用.Values.favorite工作。让我们在.Values.favorite范围来重写ConfigMap:

1
2
3
4
5
6
7
8
9
10
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 }}
1
2
# 注意,由于我们使用 with 将范围设置在了 .Values.favorite
# 所以我们使用 .drink, .food。范围在 {{ end }} 后被还原

但这里有一个值得注意的问题!在限制的范围内,你将无法从父对象范围(.)访问其它对象。以下示例会失败:

1
2
3
4
5
6
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ .Release.Name }}
{{- end }}
release-2: {{ .Release.Name }}

由于Release.Name没有在限制的范围(.)内,会报错。但在限制的之外就没有问题。

或者,我们可以使用$符号从父范围访问Release.Name对象。$符号在开始执行时会映射到根范围内,在模板执行时也不会改变。示例如下:

1
2
3
4
5
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ $.Release.Name }}
{{- end }}

在了解range后,我们会看到模板变量,它提供了一个解决上述作用域问题的方法。




range循环

Looping with the range action

许多编程语言都是用for循环,在Helm模板语言中,它使用range操作符来实现迭代。

首先,让我们在values.yaml文件里添加一个列表。

1
2
3
4
5
6
7
8
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions

在我们的ConfigMap中获取值里面的列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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: |-
{{- ranage .Values.pizzaToppings }}
- {{ . | title | quote }}
{{- end }}

我们可以使用$来访问父范围内的Values.pizzaToppings$符号映射到根目录下,并在函数执行时不会改变。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 }}
toppings: |-
{{- range $.Values.pizzaToppings }}
- {{ .| title | quote }}
{{- end }}
{{- end }}

渲染示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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不是一个YAML list,而是一个big string。我们为什么要这样做?因为在ConfigMaps data里的数据是由键值对(k/v)组成,其中键和值都是简单的字符串。要理解为什么这样的化,请查看k8s configmap文档。

YAML里的|-符号表示一个多行字符串(multi-line string)。这可以在文件中嵌入一大块数据。

Helm模板具有一个tuple函数,来使得操作更简单。让我想起了Python中的tuple数据类型。示例如下:

1
2
3
4
sizes: |-
{{- range ruple "small" "medium" "large"}}
- {{ . }}
{{- end }}

结果如下:

1
2
3
4
sizes: |-
- small
- medium
- large

除了list和tuple,range还可以迭代具有有键值对的map和dict。我们将在后面的章节中了解它们。






变量

Variables: https://helm.sh/docs/chart_template_guide/variables/

我们可以在模板中使用变量。在Helm模板中,变量是其它对象的命名引用。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Releae.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块之前,我们赋值了一个变量。在with块内,$relname变量仍然指向版本名称。

range循环中使用变量:

1
2
3
4
toppings: |-
{{- range $index, $topping := .Values.pizzaToppings }}
{{ $index }}: {{ $topping }}
{{- end }

渲染效果:

1
2
3
4
5
toppings: |-
0: mushrooms
1: cheese
2: peppers
3: onions

有一个变量($)它永远是全局的,此变量将永远指向根上下文(root context)。放你使用range循环,并且需要知道chart的版本名称时,这非常有用。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{{- range .Values.tlsSecrets }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .name }}
labels:
# Many helm templates would use `.` below, but that will not work,
# however `$` will work here
app.kubernetes.io/name: {{ template "fullname" $ }}
# I cannot reference .Chart.Name, but I can do $.Chart.Name
helm.sh/chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}"
app.kubernetes.io/instance: "{{ $.Release.Name }}"
# Value from appVersion in Chart.yaml
app.kubernetes.io/version: "{{ $.Chart.AppVersion }}"
app.kubernetes.io/managed-by: "{{ $.Release.Service }}"
type: kubernetes.io/tls
data:
tls.crt: {{ .certificate }}
tls.key: {{ .key }}
---
{{- end }}

到目前为止,我们已看到了只在一个文件中声明的模板。但是Helm模板语言的一个强大功能是声明多个模板和使用它们。我们将在后面的章节了解到。






命名模板

Named Templates: https://helm.sh/docs/chart_template_guide/named_templates/

是时候使用多个模板了。本章中,我们将在一个文件中命名模板,然后在其它地方使用它们。这让我想起了Python写Web是的模板。命名模板(有时称为子模板)是在文件中定义的一个简单的模板。有两种方法来创建它,有几种不同的方法来使用它。

在流程控制(flow control)章节,我们介绍了define, template, block这三个声明和管理模板的动作。在本章中,我们将讨论这三种动作,并引入一种特殊目的的include函数。

命名模板的一个重要细节:模板名称是全局的。如果声明了两个相同名称的模板,whichever one is loaded last will be the one used. 由于subcharts中的模板与顶级模板一起编译,你应该小心命名。

1
2
3
# 一种流行的命名约定是使用chart名作为前缀:
{{ define "mychart.labels" }}
# 通过使用特定的chart名称作为前缀,我们可以避免相同模板名称所带来的冲突


下划线文件

Partials and _ files

目前为止,我们使用的一个文件中包含一个模板。但是Helm模板语言允许你创建命名嵌套模板,可通过名称在其它任何地方进行访问。

在我们开始编写这些模板之前,我们需要注意一下命名规范:

  • templates/下的大多数文件被视为包含k8s manifests
  • NOTES.txt是一个例外
  • 以下划线(_)开头的文件被假定为不包含k8s manifests。这些文件不会被渲染为k8s对象定义,但可在任意chart templates中使用。

这些文件用来存储特定(partials)和助手(helpers)。实际上,当我们第一次创建mychart,我们会看到_helpers.tpl文件,此文件是默认的template partials。




声明和使用模板

Declaring and using templates with define and template

define动作允许我们在一个模板文件中创建命名模板(named template)。语法如下:

1
2
3
{{ define "MY.NAME "}}
# body of template here
{{ end }}

栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{{- define "mychart.labels" }}
labels:
generator: helm
data: {{ now | htmlDate }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" }}
data:
myvalue: "Hello World"
{{- range $key, $va1 := .Values.favorite }}
{{ $key }}: {{ $va1 | quote }}
{{- end }}

渲染之后的效果:

1
2
3
4
5
6
7
8
9
10
11
12
# 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"

define仅定义,只有在模板中调用时才会产生输出。

按照惯例,Helm charts将这些模板放在partials文件中(通常是_helpers.tpl),如:

1
2
3
4
5
6
{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}


1
2
# 按照管理,define函数 应该有一个简单的文档块 {{/*...*/}}
# 如上。然后在其它模板文件中访问它。




设置模板范围

Setting the scope of a template

在上面定义的模板中,我们没有使用任何对象。让我们做些修改:

1
2
3
4
5
6
7
8
{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
labels:
generator: helm
data: {{ now | htmlData }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}

上面定义的名称和版本是动态的,会根据不同的模板生成不同的值。

之前的引用并没有床底范围,因此在模板内我们不能使用.来访问任何事物。现在我们对模板加上范围:

1
2
3
4
5
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" . }}

注意上面在模板调用处使用的点(.)。我们可以非常容易地传递.Values.Values.favorite或任何我们需要的范围。但是,我们需要的是顶级范围。

现在运行渲染(helm install --dry-run --debug plinking-anaco ./mychart)来预览下:

1
2
3
4
5
6
7
8
9
10
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: plinking-anaco-configmap
labels:
generator: helm
date: 2016-11-02
chart: mychart
version: 0.1.0




include

The include function

假设我们定义了如下一个简单模板:

1
2
3
4
{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}"
{{- end -}}


一个错误的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
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" . }}

渲染的结果并不正确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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"

Because the template that is substituted in has the text aligned to the right. Because template is an action, and not a function, there is no way to pass the output of a template call to other functions; the data is simply inserted inline.

To work around this case, Helm provides an alternative to template that will import the contents of a template into the present pipeline where it can be passed along to other functions in the pipeline.

因为模板是靠右对齐的文本,因为template是一个动作,不是一个函数,因此无法传递调用其它函数的template的输出,数据被简单的插入内联。


现在我们需要使用ident来告诉模板正确的缩进,栗子:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ include "mychart.app" . | indent 4 }}
data:
myvalue: "Hello World"
{{- range $key, $va1 := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end }}
{{ include "mychart.app" . | indent 2 }}

正确的渲染结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: edgy-mole-configmap
labels:
app_name: mychart
app_version: "0.1.0+1478129987"
data:
myvalue: "Hello World"
drink: "coffee"
food: "pizza"
app_name: mychart
app_version: "0.1.0+1478129987"

在Helm template中使用includetemplate被认为更好,这使得输出格式可以为YAML文档更好地处理。

有时候,我们想要导入内容,但不作为模板。也就是逐字导入文件。我们可以通过.Files对象访问文件来实现这一目标。




在模板内访问文件

Accessing Files Inside Templates: https://helm.sh/docs/chart_template_guide/accessing_files/

Helm提供了.Files对象来访问文件。在开始模板示例之前,有些事需要注意下:

  • 可以添加额外的文件到Helm chart。这些文件将被捆绑。要注意,charts必须小于1MB,因为k8s对象的存储限制。
  • 某些文件无法通过.Files对象访问,通常出于安全原因
    • templates/目录内的文件无法访问
    • .helmignore中包含的文件无法访问
  • Charts不保留UNIX mode信息,当设计到.Files对象时,文件级别的权限对一个文件的可用性没有影响。


示例

Basic example

添加三个位于mychart/目录下的文件。

1
2
3
4
5
6
7
8
# config1.toml
message = Hello from config 1

# config1.tom2
message = Hello from config 2

# config1.tom3
message = Goodbye from config 3

在模板中访问文件:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
{{- $files := .Files }}
{{- range tuple "config1.tom1" "config2.toml" "config3.toml" }}
{{ .}}: |-
{{ $files.Get .}}
{{- end }}


1
2
3
# 首先,创建了一个 $files变量 来保存.Files对象的引用
# 我们同样使用 tuple函数来创建循环的文件列表
# 接着打印每个文件名 {{ . }}: |- 后面接着文件内容 {{ $files.Get . }}

渲染效果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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




路径助手

Path helpers

使用文件时,对文件路径执行一些标准的操作是有用的。为了帮助处理,Helm从go path包中引入了许多函数供你使用:

  • Base
  • Dir
  • Ext
  • IsAbs
  • Clean




Glob模式

Glob patterns

随着你的chart包的增长,你会发现你有一个更大的需来组织你的文件,因此我们提供了Files.Glob(pattern string)方法,以帮助提取某些文件与glob patterns的所有灵活性。

.Glob返回一个Files类型,因此你可以在返回的对象上调用任意Files方法。

1
2
3
4
5
6
# 示例的目录结构
foo/:
foo.txt foo.yaml

bar/:
bar.go bar.conf baz.yaml

使用Globs的多种选项:

1
2
3
4
5
6
{{ $currentScope := .}}
{{ range $path, $_ := .Files.Glob "**.yaml" }}
{{- with $currentScope}}
{{ .Files.Get $path }}
{{- end }}
{{ end }}

或者:

1
2
3
{{ range $path, $_ :=  .Files.Glob  "**.yaml" }}
{{ $.Files.Get $path }}
{{ end }}




ConfigMap和Secrets的实用功能

ConfigMap and Secrets utility functions

将文件内容放置到K8s ConfigMap或Secrets中非常常见,然后在运行的时候挂载到容器。为了帮助实现此功能,我们在Files类型上提供了几种实用的方法:

  • AsCoinfig
  • AsSecrets

栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: ConfigMap
metadata:
name: conf
data:
{{ (.Files.Glob "foo/*").AsConfig | indent 2 }}
---
apiVersion: v1
kind: Secret
metadata:
name: very-secret
type: Opaque
data:
{{ (.Files.Glob "bar/*").AsSecrets | indent 2 }}




编码

Encoding

你可以导入一个文件,并实用base64编码来确保成功传输:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
name: {{ .Release.Name }}-secret
type: Opaque
data:
token: |-
{{ .Files.Get "config1.toml" | b64enc }}

渲染后的效果:

1
2
3
4
5
6
7
8
9
# Source: mychart/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: lucky-turkey-secret
type: Opaque
data:
token: |-
bWVzc2FnZSA9IEhlbGxvIGZyb20gY29uZmlnIDEK




Lines

有时候需要在模板中访问一个文件中的每行内容。我们为此提供了Lines方法。

示例:

1
2
3
data:
some-file.txt: {{ range .Files.Lines "foo/bar.txt" }}
{{ . }}{{ end }}




NOTES.txt

Creating a NOTES.txt File: https://helm.sh/docs/chart_template_guide/notes_files/

helm installhelm upgrade结束,Helm可以为用户打印一块有用的信息。此信息使用模板且高度可定制。

要为你的chart包添加安装说明,简单地创建一个templates/NOTES.txt文件。此文件是纯文本文件,但它像作为模板一样处理,并可访问所有正常模板函数和对象。

NOTES.txt文件示例:

1
2
3
4
5
6
7
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 }}

接下来运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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包的有用信息。




Subcharts

Subcharts and Global Values: https://helm.sh/docs/chart_template_guide/subcharts_and_globals/

之前我们只有一个chart,但charts可能会有依赖(dependencies),称为subcharts。subcharts也有自己的值和模板。本章我们将会创建subchart,并看看我们可以从模板访问值的不同的方式。

subcharts的一些重要详情:

  • 一个subchart被认为是独立的(stand-alone),这意味着一个subchart不能明确依赖它的parent chart
  • 出于此原因,subchart不能访问parent chart的值
  • parent chart可以覆盖subcharts的值
  • Helm有一个全局值(global values)的概念,这些全局值可被所有charts访问


创建一个subchart

Creating a Subchart

1
2
3
4
5
cd mychart/charts

helm create mysubchart

rm -rf mysubchart/templates/*.*




对subchart添加值和模板

Adding Values and a Template to the Subchart

为subchart添加一个简单的值和模板:

1
2
# values.yaml
dessert: cake
1
2
3
4
5
6
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-cfgmap2
data:
dessert: {{ .Values.dessert }}

因为每个subchart都是独立的chart,我们可以测试mysubchart:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
helm install --generate-name --dry-run --debug mychart/charts/mysubchart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/helm.sh/helm/_scratch/mychart/charts/mysubchart
NAME: newbie-elk
TARGET NAMESPACE: default
CHART: mysubchart 0.1.0
MANIFEST:
---
# Source: mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: newbie-elk-cfgmap2
data:
dessert: cake




从parent chart覆盖值

现在,mychart是mysubchart的parent chart。因为mychart是parent chart,我们可以在mychart中指定配置,并将配置推送到mysubchart中。

1
2
3
4
5
6
7
8
9
10
11
12
# mychart/values.yaml
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions

mysubchart:
dessert: ice cream

我们在parent chart(mychart)的值文件里添加了mysubchart的值,mysubchart这部分值会发送到mysubchart包,这回覆盖mysubchart的值。

1
2
3
4
5
6
7
8
9
helm install --dry-run --debug mychart

# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: unhinged-bee-cfgmap2
data:
dessert: ice cream




全局值

Global Chart Values

有时候你需要将值提供给所有模板,这可以使用全局值(global chart values)。全局值可被任意chart或subchart通过相同的名称来访问。全局需要明确地声明。

值数据类型保留在称为Values.global的区域,此区域可以设置全局值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# mychart/values.yaml

favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions

mysubchart:
dessert: ice cream

global:
salad: caesar


1
2
3
4
5
6
7
8
# 任意chart和subchart都可以使用 {{ .Values.global.salad }} 来访问这个值
# mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
salad: {{ .Values.global.salad }}
1
2
3
4
5
6
7
8
# mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-cfgmap2
data:
dessert: {{ .Values.dessert }}
salad: {{ .Values.global.salad }}

渲染输出效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: silly-snake-configmap
data:
salad: caesar

---
# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: silly-snake-cfgmap2
data:
dessert: ice cream
salad: caesar




共享模板

Sharing Templates with Subcharts

parent charts和subcharts可以共享模板。任意在chart中定义的block(块)都可以被其它charts所使用。

定义一个简单的模板栗子:

1
{{- define "labels" }}from: mychart {{ end }}

尽管chart开发者可以在includetemplate之间选择,但使用include的优点是它可以动态引用模板:

1
{{ include $mytemplate }}

上面间接引用$mytemplatetemplate函数,相反它只接受一个字符串。




避免使用块

Avoid Using Blocks

go模板语言提供了一个block关键字,来允许开发者提供一个覆盖的默认实现。在Helm chart中,块(block)并不是覆盖的最佳工具,因为如果提供了相同块的多个实现,选择的那个是不可预测的。

建议使用include来代替。




.helmignore

The .helmignore file: https://helm.sh/docs/chart_template_guide/helm_ignore_file/

.helmignore也就类似于.gitignore, .dockerignore,指定不需要包含在chart包中的文件。

如果此文件存在,helm package命令将忽略.helmignore里面匹配到的文件打包到应用的包里。

一个.helmignore文件的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# comment

# 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?




模板调试

Debugging Templates: https://helm.sh/docs/chart_template_guide/debugging/

有几个命令可帮助调试模板:

  • helm lint: 验证chart最佳实践的工具
  • helm install --dry-run --debughelm template --debug:渲染模板并返回k8s manifest文件
  • helm get manifest:查看安装了哪些模板




YAML技巧

YAML Techniques: https://helm.sh/docs/chart_template_guide/yaml_techniques/








Helm命令

Helm Commands: https://helm.sh/docs/helm/








社区指南

Community Guides: https://helm.sh/docs/community/








FAQ

Frequently Asked Questions: https://helm.sh/docs/faq/