Skip to content

24.Helm

Helm 可以帮助我们管理 Kubernetes 应用程序 - Helm Charts 可以定义、安装和升级复杂的 Kubernetes 应用程序,Charts 包很容易创建、版本管理、分享和分布。Helm 对于 Kubernetes 来说就相当于 yum 对于 Centos 来说,如果没有 yum 的话,我们在 Centos 下面要安装一些应用程序是极度麻烦的,同样的,对于越来越复杂的 Kubernetes 应用程序来说,如果单纯依靠我们去手动维护应用程序的 YAML 资源清单文件来说,成本也是巨大的。接下来我们就来了解了 Helm 的使用方法。

安装

首先当然需要一个可用的 Kubernetes 集群,然后在我们使用 Helm 的节点上已经配置好可以通过 kubectl 访问集群,因为 Helm 其实就是读取的 kubeconfig 文件来访问集群的。

由于 Helm V2 版本必须在 Kubernetes 集群中安装一个 Tiller 服务进行通信,这样大大降低了其安全性和可用性,所以在 V3 版本中移除了服务端,采用了通用的 Kubernetes CRD 资源来进行管理,这样就只需要连接上 Kubernetes 即可,而且 V3 版本已经发布了稳定版,所以我们这里来安装最新的 v3.11.1 版本,软件包下载地址为:https://github.com/helm/helm/releases,我们可以根据自己的节点选择合适的包下载安装即可。

下载到本地解压后,将 helm 二进制包文件移动到任意的 PATH 路径下即可:

☸ ➜ helm version
version.BuildInfo{Version:"v3.11.1", GitCommit:"293b50c65d4d56187cd4e2f390f0ada46b4c4737", GitTreeState:"clean", GoVersion:"go1.18.10"}

看到上面的版本信息证明已经成功了。

一旦 Helm 客户端准备成功后,我们就可以添加一个 chart 仓库,当然最常用的就是官方的 Helm stable charts 仓库,但是由于官方的 charts 仓库地址需要科学上网,我们可以使用微软的 charts 仓库代替:

☸ ➜ helm repo add stable http://mirror.azure.cn/kubernetes/charts/
☸ ➜ helm repo list
NAME            URL
stable          http://mirror.azure.cn/kubernetes/charts/

安装完成后可以用 search 命令来搜索可以安装的 chart 包:

☸ ➜ helm search repo stable
NAME                                    CHART VERSION   APP VERSION             DESCRIPTION
crossplane-stable/crossplane            1.10.1          1.10.1                  Crossplane is an open source Kubernetes add-on ...
stable/acs-engine-autoscaler            2.2.2           2.1.1                   DEPRECATED Scales worker nodes within agent pools
stable/aerospike                        0.3.5           v4.5.0.5                DEPRECATED A Helm chart for Aerospike in Kubern...
stable/airflow                          7.13.3          1.10.12                 DEPRECATED - please use: https://github.com/air...
stable/ambassador                       5.3.2           0.86.1                  DEPRECATED A Helm chart for Datawire Ambassador
stable/anchore-engine                   1.7.0           0.7.3                   Anchore container analysis and policy evaluatio...
stable/apm-server                       2.1.7           7.0.0                   DEPRECATED The server receives data from the El...
stable/ark                              4.2.2           0.10.2                  DEPRECATED A Helm chart for ark
......

示例

为了安装一个 chart 包,我们可以使用 helm install 命令,Helm 有多种方法来找到和安装 chart 包,但是最简单的方法当然是使用官方的 stable 这个仓库直接安装:

首先从仓库中将可用的 charts 信息同步到本地,可以确保我们获取到最新的 charts 列表:

☸ ➜ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈

比如我们现在安装一个 mysql 应用:

☸ ➜ helm install stable/mysql --generate-name
NAME: mysql-1678181101
LAST DEPLOYED: Tue Mar  7 17:25:02 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mysql-1678181101.default.svc.cluster.local

To get your root password run:

    MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default mysql-1678181101 -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-1678181101 -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-1678181101 3306

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

我们可以看到 stable/mysql 这个 chart 已经安装成功了,我们将安装成功的这个应用叫做一个 release,由于我们在安装的时候指定了--generate-name 参数,所以生成的 release 名称是随机生成的,名为 mysql-1678181101。我们可以用下面的命令来查看 release 安装以后对应的 Kubernetes 资源的状态:

☸ ➜ kubectl get all -l release=mysql-1678181101
NAME                                    READY   STATUS             RESTARTS   AGE
pod/mysql-1678181101-5db75878df-97xhh   0/1     ImagePullBackOff   0          108s

NAME                       TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/mysql-1678181101   ClusterIP   10.96.14.93   <none>        3306/TCP   108s

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mysql-1678181101   0/1     1            0           108s

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/mysql-1678181101-5db75878df   1         1         0       108s

我们也可以 helm show chart 命令来了解 MySQL 这个 chart 包的一些特性:

☸ ➜ helm show chart stable/mysql
apiVersion: v1
appVersion: 5.7.30
deprecated: true
description: DEPRECATED - Fast, reliable, scalable, and easy to use open-source relational
  database system.
home: https://www.mysql.com/
icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png
keywords:
- mysql
- database
- sql
name: mysql
sources:
- https://github.com/kubernetes/charts
- https://github.com/docker-library/mysql
version: 1.6.9

如果想要了解更多信息,可以用 helm show all 命令:

☸ ➜ helm show all stable/mysql
......

需要注意的是无论什么时候安装 chart,都会创建一个新的 release,所以一个 chart 包是可以多次安装到同一个集群中的,每个都可以独立管理和升级。

同样我们也可以用 Helm 很容易查看到已经安装的 release:

☸ ➜ helm ls
NAME                NAMESPACE   REVISION    UPDATED                                 STATUS      CHART       APP VERSION
mysql-1678181101    default     1           2023-03-07 17:25:02.376711 +0800 CST    deployed    mysql-1.6.9 5.7.30

如果需要删除这个 release,也很简单,只需要使用 helm uninstall 命令即可:

☸ ➜ helm uninstall mysql-1678181101
release "mysql-1678181101" uninstalled
☸ ➜ kubectl get all -l release=mysql-1678181101
No resources found in default namespace.
☸ ➜ helm status mysql-1678181101
Error: release: not found

uninstall 命令会从 Kubernetes 中删除 release,也会删除与 release 相关的所有 Kubernetes 资源以及 release 历史记录。也可以在删除的时候使用 --keep-history 参数,则会保留 release 的历史记录,可以获取该 release 的状态就是 UNINSTALLED,而不是找不到 release 了:

☸ ➜ helm uninstall mysql-1678181101 --keep-history
release "mysql-1678181101" uninstalled
☸ ➜ helm status mysql-1678181101
helm status mysql-1678181101
NAME: mysql-1678181101
STATUS: uninstalled
...
☸ ➜ helm ls -a
NAME                NAMESPACE   REVISION    UPDATED                                 STATUS      CHART       APP VERSION
mysql-1678181101    default     1           2023-03-07 17:25:02.376711 +0800 CST    uninstalled mysql-1.6.9 5.7.30

因为 Helm 会在删除 release 后跟踪你的 release,所以你可以审查历史甚至取消删除 release(使用 helm rollback 命令)。

定制

上面我们都是直接使用的 helm install 命令安装的 chart 包,这种情况下只会使用 chart 的默认配置选项,但是更多的时候,是各种各样的需求,索引我们希望根据自己的需求来定制 chart 包的配置参数。

我们可以使用 helm show values 命令来查看一个 chart 包的所有可配置的参数选项:

☸ ➜ helm show values stable/mysql
## mysql image version
## ref: https://hub.docker.com/r/library/mysql/tags/
##
image: "mysql"
imageTag: "5.7.30"

strategy:
  type: Recreate

busybox:
  image: "busybox"
  tag: "1.32"

testFramework:
  enabled: true
  image: "bats/bats"
  tag: "1.2.1"
  imagePullPolicy: IfNotPresent
  securityContext: {}

## Specify password for root user
##
## Default: random 10 character string
# mysqlRootPassword: testing

## Create a database user
##
# mysqlUser:
## Default: random 10 character string
# mysqlPassword:

## Allow unauthenticated access, uncomment to enable
##
# mysqlAllowEmptyPassword: true

## Create a database
##
# mysqlDatabase:

## Specify an imagePullPolicy (Required)
## It's recommended to change this to 'Always' if the image tag is 'latest'
## ref: http://kubernetes.io/docs/user-guide/images/#updating-images
##
imagePullPolicy: IfNotPresent
......

上面我们看到的所有参数都是可以用自己的数据来覆盖的,可以在安装的时候通过 YAML 格式的文件来传递这些参数:

☸ ➜ cat config.yaml
mysqlUser:
  user0
mysqlPassword: user0pwd
mysqlDatabase: user0db
persistence:
  enabled: false
☸ ➜ helm install -f config.yaml mysql stable/mysql
NAME: mysql
LAST DEPLOYED: Tue Mar  7 17:33:06 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mysql.default.svc.cluster.local
......

release 安装成功后,可以查看对应的 Pod 信息:

☸ ➜ kubectl get pod -l release=mysql
NAME                     READY   STATUS             RESTARTS   AGE
mysql-86c678565c-jhnhh   0/1     ImagePullBackOff   0          28s
☸ ➜ kubectl describe pod mysql-86c678565c-jhnhh
......
Environment:
      MYSQL_ROOT_PASSWORD:  <set to the key 'mysql-root-password' in secret 'mysql'>  Optional: false
      MYSQL_PASSWORD:       <set to the key 'mysql-password' in secret 'mysql'>       Optional: false
      MYSQL_USER:           user0
      MYSQL_DATABASE:       user0db
......

可以看到环境变量 MYSQL_USER=user0,MYSQL_DATABASE=user0db 的值和我们上面配置的值是一致的。在安装过程中,有两种方法可以传递配置数据:

  • --values(或者 -f):指定一个 YAML 文件来覆盖 values 值,可以指定多个值,最后边的文件优先
  • --set:在命令行上指定覆盖的配置

如果同时使用这两个参数,--values(-f) 将被合并到具有更高优先级的 --set,使用 --set 指定的值将持久化在 ConfigMap 中,对于给定的 release,可以使用 helm get values <release-name> 来查看已经设置的值,已设置的值也通过允许 helm upgrade 并指定 --reset 值来清除。

--set 选项接收零个或多个 name/value 对,最简单的用法就是 --set name=value,相当于 YAML 文件中的:

name: value

多个值之间用字符串“,”隔开,用法就是 --set a=b,c=d,相当于 YAML 文件中的:

a: b
c: d

也支持更加复杂的表达式,例如 --set outer.inner=value,对应 YAML:

outer:
  inner: value

对于列表数组可以用 {} 来包裹,比如 --set name={a, b, c},对应 YAML:

name:
  - a
  - b
  - c

从 Helm 2.5.0 开始,就可以使用数组索引语法来访问列表中某个项,比如 --set servers[0].port=80,对应的 YAML 为:

servers:
  - port: 80

也可以这样设置多个值,比如 --set servers[0].port=80,servers[0].host=example,对应的 YAML 为:

servers
  - port: 80
    host: example

有时候你可能需要在 --set 选项中使用特殊的字符,这个时候可以使用反斜杠来转义字符,比如 --set name=value1\,value2,对应的 YAML 为:

name: "value1,value2"

类似的,你还可以转义.,当 chart 模板中使用 toYaml 函数来解析 annotations、labels 以及 node selectors 之类的时候,这非常有用,比如 --set nodeSelector."kubernetes\.io/role"=master,对应的 YAML 文件:

nodeSelector:
  kubernetes.io/role: master

深度嵌套的数据结构可能很难使用 --set 来表示,所以一般推荐还是使用 YAML 文件来进行覆盖,当然在设计 chart 模板的时候也可以结合考虑到 --set 这种用法,尽可能的提供更好的支持。

更多安装方式

helm install 命令可以从多个源进行安装:

  • chart 仓库(类似于上面我们提到的)
  • 本地 chart 压缩包(helm install foo-0.1.1.tgz)
  • 本地解压缩的 chart 目录(helm install foo path/to/foo)
  • 在线的 URL(helm install fool https://example.com/charts/foo-1.2.3.tgz)

升级和回滚

当新版本的 chart 包发布的时候,或者当你要更改 release 的配置的时候,你可以使用 helm upgrade 命令来操作。升级需要一个现有的 release,并根据提供的信息对其进行升级。因为 Kubernetes charts 可能很大而且很复杂,Helm 会尝试以最小的侵入性进行升级,它只会更新自上一版本以来发生的变化:

☸ ➜ helm upgrade -f config.yaml mysql stable/mysql
Release "mysql" has been upgraded. Happy Helming!
NAME: mysql
LAST DEPLOYED: Tue Mar  7 17:34:36 2023
NAMESPACE: default
STATUS: deployed
REVISION: 2
...

我们这里 mysql 这个 release 用相同的 chart 包进行升级,但是新增了一个配置项:

mysqlRootPassword: passw0rd

我们可以使用 helm get values 来查看新设置是否生效:

☸ ➜ helm get values mysql
USER-SUPPLIED VALUES:
mysqlDatabase: user0db
mysqlPassword: user0pwd
mysqlRootPassword: passw0rd
mysqlUser: user0
persistence:
  enabled: false

helm get 命令是查看集群中 release 的非常有用的命令,正如我们在上面看到的,它显示了 panda.yaml 中的新配置值被部署到了集群中,现在如果某个版本在发布期间没有按计划进行,那么可以使用 helm rollback [RELEASE] [REVISION] 命令很容易回滚到之前的版本:

☸ ➜ helm ls
NAME    NAMESPACE   REVISION    UPDATED                                 STATUS      CHART       APP VERSION
mysql   default     2           2023-03-07 17:34:36.973512 +0800 CST    deployed    mysql-1.6.9 5.7.30
☸ ➜ REVISION    UPDATED                     STATUS      CHART       APP VERSION DESCRIPTION
1           Tue Mar  7 17:33:06 2023    superseded  mysql-1.6.9 5.7.30      Install complete
2           Tue Mar  7 17:34:36 2023    deployed    mysql-1.6.9 5.7.30      Upgrade complete
☸ ➜ helm rollback mysql 1
Rollback was a success! Happy Helming!
☸ ➜ kubectl get pods -l release=mysql
NAME                    READY   STATUS    RESTARTS   AGE
mysql-86c678565c-jhnhh   1/1     Running   0          3h25m
☸ ➜ helm get values mysql
USER-SUPPLIED VALUES:
mysqlDatabase: user0db
mysqlPassword: user0pwd
mysqlUser: user0
persistence:
  enabled: false

可以看到 values 配置已经回滚到之前的版本了。上面的命令回滚到了 release 的第一个版本,每次进行安装、升级或回滚时,修订号都会加 1,第一个修订号始终为 1,我们可以使用 helm history [RELEASE] 来查看某个版本的修订号。

除此之外我们还可以指定一些有用的选项来定制 install/upgrade/rollback 的一些行为,要查看完整的参数标志,我们可以运行 helm <command> --help 来查看,这里我们介绍几个有用的参数:

  • --timeout: 等待 Kubernetes 命令完成的时间,默认是 300(5 分钟)
  • --wait: 等待直到所有 Pods 都处于就绪状态、PVCs 已经绑定、Deployments 具有处于就绪状态的最小 Pods 数量(期望值减去 maxUnavailable)以及 Service 有一个 IP 地址,然后才标记 release 为成功状态。它将等待与 --timeout 值一样长的时间,如果达到超时,则 release 将标记为失败。注意:在 Deployment 将副本设置为 1 并且作为滚动更新策略的一部分,maxUnavailable 未设置为 0 的情况下,--wait 将返回就绪状态,因为它已满足就绪状态下的最小 Pod 数量
  • --no-hooks: 将会跳过命令的运行 hooks
  • --recreate-pods: 仅适用于 upgrade 和 rollback,这个标志将导致重新创建所有的 Pods。(Helm3 中启用了)

Helm 使用一种名为 charts 的包格式,一个 chart 是描述一组相关的 Kubernetes 资源的文件集合,单个 chart 可能用于部署简单的应用,比如 memcached pod,或者复杂的应用,比如一个带有 HTTP 服务、数据库、缓存等等功能的完整 web 应用程序。

Charts 是创建在特定目录下面的文件集合,然后可以将它们打包到一个版本化的存档中来部署。接下来我们就来看看使用 Helm 构建 charts 的一些基本方法。

文件结构

chart 被组织为一个目录中的文件集合,目录名称就是 chart 的名称(不包含版本信息),下面是一个 WordPress 的 chart,会被存储在 wordpress/ 目录下面,基本结构如下所示:

wordpress/
  Chart.yaml          # 包含当前 chart 信息的 YAML 文件
  LICENSE             # 可选:包含 chart 的 license 的文本文件
  README.md           # 可选:一个可读性高的 README 文件
  values.yaml         # 当前 chart 的默认配置 values
  values.schema.json  # 可选: 一个作用在 values.yaml 文件上的 JSON 模式
  charts/             # 包含该 chart 依赖的所有 chart 的目录
  crds/               # Custom Resource Definitions
  templates/          # 模板目录,与 values 结合使用时,将渲染生成 Kubernetes 资源清单文件
  templates/NOTES.txt # 可选: 包含简短使用使用的文本文件

另外 Helm 会保留 charts/crds/ 以及 templates/ 目录以及上面列出的文件名的使用。

Chart.yaml 文件

对于一个 chart 包来说 Chart.yaml 文件是必须的,它包含下面的这些字段:

apiVersion: chart API 版本 (必须)
name: chart 名 (必须)
version: SemVer 2版本 (必须)
kubeVersion: 兼容的 Kubernetes 版本 (可选)
description: 一句话描述 (可选)
type: chart 类型 (可选)
keywords:
  - 当前项目关键字集合 (可选)
home: 当前项目的 URL (可选)
sources:
  - 当前项目源码 URL (可选)
dependencies: # chart 依赖列表 (可选)
  - name: chart 名称 (nginx)
    version: chart 版本 ("1.2.3")
    repository: 仓库地址 ("https://example.com/charts")
maintainers: # (可选)
  - name: 维护者名字 (对每个 maintainer 是必须的)
    email: 维护者的 email (可选)
    url: 维护者 URL (可选)
icon: chart 的 SVG 或者 PNG 图标 URL (可选).
appVersion: 包含的应用程序版本 (可选). 不需要 SemVer 版本
deprecated: chart 是否已被弃用 (可选, boolean)

其他字段默认会被忽略。

版本

每个 chart 都必须有一个版本号,版本必须遵循 SemVer2 标准,和 Helm Classic 不同,Kubernetes Helm 使用版本号作为 release 的标记,仓库中的软件包通过名称加上版本号来标识的。

例如,将一个 nginx 的 chart 包 version 字段设置为:1.2.3,则 chart 最终名称为:

nginx-1.2.3.tgz

还支持更复杂的 SemVer2 名称,例如版本:1.2.3-alpha.1+ef365,但是需要注意的是系统明确禁止使用非 SemVer 的名称。

Chart.yaml 中的 version 字段被很多 Helm 工具使用,包括 CLI 工具,生成包的时候,命令 helm package 将使用该字段作为包名称中的标记,系统是默认 Chart 包中的版本号与 chart.yaml 中的版本号匹配的,所以如果不匹配的话就导致一系列错误。

apiVersion 字段

对于 Helm 3 以上的版本 apiVersion 字段应该是 v2,之前版本的 Chart 应该设置为 1,并且也可以有 Helm 3 进行安装。

appVersion 字段

要注意 appVersion 字段与 version 字段无关,这是一种指定应用程序版本的方法,比如 drupal 的 Chart 包可能有一个 appVersion: 8.2.1 的字段,表示 Chart 中包含的 drupal 版本是 8.2.1,该字段仅供参考,对 Chart 版本的计算不会产生影响。

弃用 Charts

当在 Chart 仓库中管理 charts 的时候,有时候需要弃用一个 chart,Chart.yaml 中的可选字段 deprecated 可以用来标记一个 chart 为弃用状态。如果将仓库中最新版本的 chart 标记为弃用,则整个 chart 都会被当做弃用状态了。以后可以通过发布一个未被标记为弃用状态的新版本来重新使用该 chart。弃用 charts 的工作流程如下所示:

  • 更新 chart 的 Chart.yaml 来标记 chart 为弃用状态
  • 发布该新版本到 Chart 仓库
  • 从源码仓库(比如 git)中删除 chart

Chart 类型

type 字段定义 chart 的类型,可以定义两种类型:应用程序(application)和库(library)。应用程序是默认的类型,它是一个可以完整操作的标准 chart,库或者辅助类 chart 为 chart 提供了一些实用的功能,library 不同于应用程序 chart,因为它没有资源对象,所以无法安装。

一个应用 chart 也可以当作库进行使用。通过将类型设置为 library,然后该 chart 就会渲染成一个库,可以在其中使用所有的实用性功能,chart 的所有资源对象都不会被渲染。

LICENSE, README 和 NOTES

Chart 还可以包含用于描述 chart 的安装、配置、用法和许可证书的文件。

LICENSE 是一个纯文本文件,其中包含 chart 的许可证书。chart 可以包含一个许可证书,因为它可能在模板中具有编程逻辑,所以不只是配置,如果需要,chart 还可以为应用程序提供单独的 license(s)。

Chart 的 README 文件应该采用 Markdown(README.md)格式,并且通常应该包含如下的一些信息:

  • chart 提供的应用程序的描述信息
  • 运行 chart 的任何先决条件或者要求
  • values.yaml 和默认值中的一些选项说明
  • 与 chart 的安装或配置有关的任何其他信息

chart 还可以包含简短的纯文本模板或者 NOTES.txt 文件,该文件将在安装后以及查看 release 状态的时候打印出来。该文件会被当成模板文件,并且可以用于显示使用说明,后续步骤或与 release 有关的任何其他信息。例如,可以提供用于连接到数据或访问 Web UI 的指令。由于在运行 helm install 或者 helm status 的时候该文件会打印到 STDOUT 中,所以建议该文件内容保持内容简短然后可以指向 README 文件来获取更多详细信息。

依赖

在 Helm 中,一个 chart 包可能会依赖许多其他的 chart。这些依赖关系可以使用 Chart.yaml 中的依赖关系字段动态链接,也可以引入到 charts/ 目录手动进行管理。

  1. 使用 dependencies 字段管理依赖

当前 chart 所需的依赖 chart 需要在 dependencies 字段中进行定义,如下所示:

dependencies:
  - name: apache
    version: 1.2.3
    repository: https://example.com/charts
  - name: mysql
    version: 3.2.1
    repository: https://another.example.com/charts
  • name 字段是所依赖的 chart 的名称
  • version 字段是依赖的 chart 版本
  • repository 字段是 chart 仓库的完整 URL,不过需要注意,必须使用 helm repo add 在本地添加该 repo

定义了依赖项后,可以运行 helm dependency update 来更新依赖项,它将根据你的依赖项文件把你所有指定的 chart 包下载到 charts/ 目录中:

➜ helm dependency update foochart
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "local" chart repository
...Successfully got an update from the "stable" chart repository
...Successfully got an update from the "example" chart repository
...Successfully got an update from the "another" chart repository
Update Complete. Happy Helming!
Saving 2 charts
Downloading apache from repo https://example.com/charts
Downloading mysql from repo https://another.example.com/charts

当执行 helm dependency update 命令的时候会解析 chart 的依赖项,会将他们作为 chart 包文件下载存放到 charts/ 目录中,所以,对于上面的示例,我们可以在 charts 目录中看到如下的文件:

charts/
  apache-1.2.3.tgz
  mysql-3.2.1.tgz
  1. alias 字段

除了上面的几个字段之外,每个依赖项还可以包含一个可选的 alias 别名字段。为依赖 chart 添加别名将使用别名作为依赖的名称。在需要访问其他名称的 chart 情况下,就可以使用别名,如下所示:

# parentchart/Chart.yaml

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

在上面示例中,我们将获得 3 个依赖项:

subchart
new-subchart-1
new-subchart-2

当然其实我们也可以手动来实现,将同一个 chart 以不同的名称多次复制/粘贴到 charts/ 目录中也是可以的。

TEMPLATES 和 VALUES

Helm Chart 模板是用 Go template 语言 进行编写的,另外还额外增加了(【Sprig】](https://github.com/Masterminds/sprig)库中的50个左右的附加模板函数和一些其他专用函数

所有模板文件都存储在 chart 的 templates/ 目录下面,当 Helm 渲染 charts 的时候,它将通过模板引擎传递该目录中的每个文件。模板的 Values 可以通过两种方式提供:

  • Chart 开发人员可以在 chart 内部提供一个名为 values.yaml 的文件,该文件可以包含默认的 values 值内容。
  • Chart 用户可以提供包含 values 值的 YAML 文件,可以在命令行中通过 helm install 来指定该文件。

当用户提供自定义 values 值的时候,这些值将覆盖 chart 中 values.yaml 文件中的相应的值。

  1. 模板文件

模板文件遵循编写 Go 模板的标准约定(可以查看 text/template 包文档查看详细信息),下面是一个模板文件示例:

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

上面这个示例是 Kubernetes replication 控制器的一个模板,它可以使用以下 4 个模板值(通常在 values.yaml 文件中定义的):

  • imageRegistry:Docker 镜像仓库
  • dockerTag:Docker 镜像 tag
  • pullPolicy:镜像拉取策略
  • storage:存储后端,默认设置为 "minio"

这些所有的 values 值都是有模板作者来定义的,Helm 不会也不需要规定这些参数。可以可以查看 Kubernetes Charts 项目去了解更多的 charts 项目的详细内容。

  1. 预定义 Values

在模板中用 .Values 可以获取到 values.yaml 文件(或者 --set 参数)提供的 values 值,此外,还可以在模板中访问其他预定义的数据。下面是一些预定义的、可用于每个模板、并且不能被覆盖的 values 值,与所有 values 值一样,名称都是区分大小写的:

  • Release.Name:release 的名称(不是 chart)
  • Release.Namespace:release 被安装到的命名空间
  • Release.Service:渲染当前模板的服务,在 Helm 上,实际上该值始终为 Helm
  • Release.IsUpgrade:如果当前操作是升级或回滚,则该值为 true
  • Release.IsInstall:如果当前操作是安装,则该值为 true
  • ChartChart.yaml 文件的内容,可以通过 Chart.Version 来获得 Chart 的版本,通过 Chart.Maintainers 可以获取维护者信息
  • Files: 一个包含 chart 中所有非特殊文件的 map 对象,这不会给你访问模板的权限,但是会给你访问存在的其他文件的权限(除非使用 .helmignore 排除它们),可以使用 {{ index .Files "file.name" }} 或者 {{ .Files.Get name }} 或者 {{ .Files.GetString name }} 函数来访问文件,你还可以使用 {{ .Files.GetBytes }}[]byte 的形式获取访问文件的内容
  • Capabilities:也是一个类 map 的对象,其中包含有关 Kubernetes 版本({{ .Capabilities.KubeVersion }})和支持的 Kubernetes API 版本({{ .Capabilities.APIVersions.Has "batch/v1" }})信息。

任何未知的 Chart.yaml 字段都会被删除,在 Chart 对象内部无法访问他们,所以,Chart.yaml 不能用于将任意结构化的数据传递到模板中,但是可以使用 values 文件来传递。

  1. Values 文件

为模板提供一些必须的 values 值的 values.yaml 文件如下所示:

imageRegistry: "quay.io/deis"
dockerTag: "latest"
pullPolicy: "Always"
storage: "s3"

values 文件的格式是 YAML,一个 chart 包可能包含一个默认的 values.yaml 文件,helm install 命令允许用户通过提供其他的 YAML 值文件来覆盖默认的值:

helm install --values=myvals.yaml wordpress

用这种方式来传递 values 值的时候,它们将合并到默认值文件中,比如有一个 myvals.yaml 文件如下所示:

storage: "gcs"

将其与 chart 的 values.yaml 文件合并后,得到的结果为:

imageRegistry: "quay.io/deis"
dockerTag: "latest"
pullPolicy: "Always"
storage: "gcs"

我们可以看到只有最后一个字段被覆盖了。

chart 内包含的默认 values 文件必须命名为 values.yaml,但是在命令行上指定的文件可以任意命名。如果在 helm install 或者 helm upgrade 的时候使用 --set 参数,则这些值将在客户端转换为 YAML 格式。如果 values 文件存在任何必须的条目,则可以使用 required 函数在 chart 模板中将它们声明为必须选项。

然后我们就可以使用 .Values 对象在模板中访问任意一个 values 值,类似于下面的模板文件:

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 }}
  1. 作用范围、依赖和 Values

values 文件可以声明顶级的 chart 以及该 chart 的 charts/ 目录中包含的任何 chart 的值。或者,换句话说,values 文件可以为 chart 以及他的任何依赖项提供 values 值。例如,上面提到了 WordPress 这个 chart 同时依赖 mysql 和 apache 这两个依赖项,values 文件可以为所有这些组件提供 values 值:

title: "My WordPress Site" # 传递到 WordPress 模板

mysql:
  max_connections: 100 # 传递到 MySQL
  password: "secret"

apache:
  port: 8080 # 传递到 Apache

较高级别的 Charts 可以访问下面定义的所有变量,所以,WordPress 这个 chart 可以通过 .Values.mysql.password 来访问 MySQL 的密码,但是较低级别的 chart 是无法访问父 chart 中的内容的,所有 MySQL 无法获取到 title 属性,当然同样也不能访问 apache.port

Values 是有命名空间的,但是会对其进行调整,比如对于 WordPress 这个 chart 来说,它可以通过 .Values.mysql.password 来进行访问,但是对于 MySQL 这个 chart 本身来说,values 的范围缩小了,命名空间前缀会被删除,所以它只需要通过 .Values.password 就可以访问到。

  1. 全局 Values

2.0.0-Alpha.2 版本开始,Helm 开始支持特殊的 global 全局值,比如将上面的示例修改如下:

title: "My WordPress Site" # 传递到 WordPress 模板

global:
  app: MyWordPress

mysql:
  max_connections: 100 # 传递到 MySQL
  password: "secret"

apache:
  port: 8080 # 传递到 Apache

上面我们添加了一个全局范围的 value 值:app: MyWordPress,该值可以通过 .Values.global.app 提供给所有 chart 使用。

例如,mysql 模板可以以 {{ .Values.global.app }} 来访问 app,apache chart 也可以,实际上,上面的 values 文件会这样重新生成:

title: "My WordPress Site" # 传递到 WordPress 模板

global:
  app: MyWordPress

mysql:
  global:
    app: MyWordPress
  max_connections: 100 # 传递到 MySQL
  password: "secret"

apache:
  global:
    app: MyWordPress
  port: 8080 # 传递到 Apache

这种方式提供了一种与所有子 chart 共享一个顶级变量的方式,这对于设置 meta 数据这种属性是非常有用的。如果子 chart 声明了全局变量,则该全局变量将向下(传递到子 chart 的子 chart 中)传递,而不会向上传递到父 chart,子 chart 无法影响 父 chart 的值。同样,父 chart 的全局遍历优先与子 chart 中的全局变量。

  1. Schema 文件

有时候,chart 开发者可能希望在其 values 值上面定义一个结构,这种情况下可以通过在 values.schema.json 文件中定义一个 schema 来完成,这里的 schema 就是一个 JSON Schema 文件结构规范,如下所示:

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

该 schema 会对 values 值进行校验,调用以下任何命令时,都会进行验证:

  • helm install
  • helm upgrade
  • helm lint
  • helm template

比如下面的示例文件就可以满足上面的 schema 要求:

name: frontend
protocol: https
port: 443

需要注意的是该 schema 将应用于最终的 .Values 对象,而不仅仅是应用于 values.yaml 文件,所以下面的文件也是可以满足 schema 要求的:

name: frontend
protocol: https

因为在安装的时候我们通过 --set 选项传递了必须的 port 属性:

helm install --set port=443

此外,还会根据所有的子 chart schemas 来检查最终的 .Values 对象,这意味着父 chart 无法规避对子 chart 的限制。同样的,如果子 chart 要求未满足子 chart 的 values.yaml 文件,则父 chart 必须满足这些限制才能生效。

  1. 参考文档

在编写模板、values、和 schema 文件的时候,下面这些文档可以提供一些帮助:

CRDS

Kubernetes 提供了一种声明新类型的 Kubernetes 对象的机制,使用 CustomResourceDefinitions(CRDS)可以让 Kubernetes 开发人员声明自定义资源类型。

在 Helm 3 中,CRD 被视为一种特殊的对象,它们在 chart 部分之前被安装,并且会受到一些限制。CRD YAML 文件应该放置 chart 内的 crds/ 目录下面。多个 CRDs 可以放在同一个文件中,Helm 将尝试将 CRD 目录中的所有文件加载到 Kubernetes 中。

需要注意的是 CRD 文件不能模板化,它们必须是纯的 YAML 文件。

当 Helm 安装一个新的 chart 的时候,它将会安装 CRDs,然后会暂停直到 API Server 提供 CRD 为止,然后才开始启动模板引擎,渲染其余的 chart 模板,并将其安装到 Kubernetes 中。由于这个安装顺序,CRD 信息在 Helm 模板的 .Capabilities 对象中是可以获取到的,并且 Helm 模板可能会创建在 CRD 中声明的对象的新实例。

比如,如果你的呃 chart 在 crds 目录下面有一个 CronTab 的 CRD,则可以在 templates/ 目录下面创建 CronTab 类型的实例:

crontabs/
  Chart.yaml
  crds/
    crontab.yaml
  templates/
    mycrontab.yaml

crontab.yaml 文件必须包含不带模板指定的 CRD:

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

然后模板 mycrontab.yaml 可以创建一个新的 CronTab(和平时使用模板一样):

apiVersion: stable.example.com
kind: CronTab
metadata:
  name: { { .Values.name } }
spec:
  # ...

在继续安装 templates/ 之前,Helm 会确保已经安装上了 CronTab 类型,并且可以从 Kubernetes API server 上获得该类型。

  1. CRDs 的限制

于 Kubernetes 中的大多数对象不同,CRDs 是全局安装的,所以 Helm 在管理 CRD 的时候比较谨慎,会有一些限制:

  • CRDs 不会重新安装,如果 Helm 确定 crds/ 目录中的 CRD 已经存在(无论版本如何),Helm 都不会重新安装或升级。
  • CRDs 不会在升级或回滚的时候安装,只会在安装操作的时候创建 CRDs。
  • CRDs 不会被删除,删除 CRD 会自动删除集群中所有 namespace 中的 CRDs 内容,所以 Helm 不会删除 CRD。

Helm 希望想要升级或删除 CRDs 的操作人员可以手动来仔细地操作。

使用 Helm 管理 Charts

helm 工具有几个用于操作 charts 的命令,如下所示。

创建一个新的 chart 包:

➜ helm create mychart
Created mychart/

一旦你已经编辑了一个 chart 包,Helm 可以将其打包到一个独立文件中:

➜ helm package mychart
Archived mychart-0.1.-.tgz

你还可以使用 helm 帮助你查找 chart 包的格式要求方面或其他问题:

➜ helm lint mychart
No issues found

Chart 仓库

chart 仓库实际上就是一个 HTTP 服务器,其中包含一个或多个打包的 chart 包,虽然可以使用 helm 来管理本地 chart 目录,但是在共享 charts 的时候,最好的还是使用 chart 仓库。

可以提供 YAML 文件和 tar文件并可以相应 GET 请求的任何 HTTP 服务器都可以作为 chart 仓库服务器。仓库的主要特征是存在一个名为 index.yaml 的特殊文件,该文件具有仓库中提供的所有软件包的列表以及允许检索和验证这些软件包的元数据。

在客户端,可以使用 helm repo 命令来管理仓库,但是 Helm 不提供用于将 chart 上传到远程 chart 仓库的工具。

Helmfile

前面我们已经了解了 Helm 的使用,但在实际使用场景中的一些需求 Helm 并不能很好的满足,需要进行一些修改和适配,比如当我们需要同时部署多个 chart、不同部署环境的区分以及 chart 的版本控制的时候,这个时候我们可以使用一个 Helmfile 的工具来解决这些场景的问题。

Helmfile 是一个声明式 Helm Chart 管理工具,通过一个 helmfile.yaml 文件来帮助用户管理和维护众多的 Helm Chat,其最主要作用是:

  • 集成在 CI/CD 系统中,提高部署的可观测性和可重复性,区分环境,免去各种 --set 造成的困扰。
  • 方便对 helm chart 进行版本控制,如指定版本范围、锁定版本等。
  • 定期同步,避免环境中出现不符合预期的配置。

Helmfile 的主要特点有:

  • 声明式:编写、版本控制、应用所需的状态文件以实现可见性和可再现性。
  • 模块:将基础架构的通用模式模块化,通过 Git、S3 等进行分发,以便在整个公司复用。
  • 多功能性:管理由 charts、kustomizations 和 Kubernetes 资源目录组成的集群,将所有内容转换为 Helm releases。
  • Patch:JSON/Strategic-Merge 在 helm 安装之前 patch Kubernetes 资源,无需分叉上游 charts。

安装

helmfile 提供了多种安装方式,我们可以直接在 release 页面 https://github.com/helmfile/helmfile/ 选择合适的包下载,比如我们这里是 Mac m1 环境就选择 darwin_arm64 的包:

$ wget https://github.com/helmfile/helmfile/releases/download/v0.151.0/helmfile_0.151.0_darwin_arm64.tar.gz
$ tar -xvf helmfile_0.151.0_darwin_arm64.tar.gz
x LICENSE
x README-zh_CN.md
x README.md
x helmfile
$ chmod +x helmfile && sudo mv helmfile /usr/local/bin
$ helmfile -v
helmfile version 0.151.0

这样就安装成功了,此外 helmfile 还支持通过容器方式运行,这样可以方便我们集成到 CI/CD 系统中去:

$ docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.config/helm:/root/.config/helm" -v "${PWD}:/wd" --workdir /wd ghcr.io/helmfile/helmfile:helm3-v0.145.2 helmfile sync

不过要注意安装后需要运行一次 helmfile init 命令,用来检查依赖插件(包括 helm-diff、helm-secrets 等)是否正确安装。

$ helmfile init
The helm plugin diff is not installed, do you need to install it [y/n]: y
Install helm plugin diff
Installed plugin: diff

The helm plugin secrets is not installed, do you need to install it [y/n]: y
Install helm plugin secrets
Installed plugin: secrets

The helm plugin s3 is not installed, do you need to install it [y/n]: y
Install helm plugin s3
Downloading and installing helm-s3 v0.14.0 ...
Checksum is valid.
Installed plugin: s3

The helm plugin helm-git is not installed, do you need to install it [y/n]: y
Install helm plugin helm-git
Installed plugin: helm-git

helmfile initialization completed!

helm-diff 是一个 Helm 插件,可让你预览 helm upgrade 会发生什么变化。helm-secrets 是一个帮助使用 Git 工作流管理机密并将其存储在任何地方的 helm 插件。

如果没有执行 init 命令则需要手动安装 helm-diff 插件,该插件是必须的,其他插件可以根据需要选择安装。

$ helm plugin install https://github.com/databus23/helm-diff

使用

接下来我们来了解下 Helmfile 的具体使用,首先我们从一个简单的示例开始,假设 helmfile.yaml 表示你的 Helm release 的期望状态如下所示:

# helmfile.yaml
repositories:
  - name: prometheus-community
    url: https://prometheus-community.github.io/helm-charts

releases:
  - name: prom-norbac-ubuntu
    namespace: prometheus
    chart: prometheus-community/prometheus
    set:
      - name: rbac.create
        value: false

上面的 helmfile.yaml 文件中首先通过 repositories 声明了 repo 配置,重点是后面的 releases 声明,这是一个对象数组,所以我们可以配置多个 Release 对象,在这里可以指定 Release 的名称、安装的命名空间、chat 包以及要覆盖的 Values 值。

然后我们可以执行 helmfile apply 命令来同步状态,这是 Helmfile 中最常用命令,与 kubectl apply 类似,根据 helmfile.yaml 中声明的配置可以一键执行相应的动作,如:添加 repo、安装或更新 release 等。

$ helmfile apply
# ......
+     release: prom-norbac-ubuntu
+   sessionAffinity: None
+   type: "ClusterIP"
prometheus, prom-norbac-ubuntu-prometheus-server, ServiceAccount (v1) has been added:
-
+ # Source: prometheus/templates/serviceaccount.yaml
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+   labels:
+     component: "server"
+     app: prometheus
+     release: prom-norbac-ubuntu
+     chart: prometheus-19.7.2
+     heritage: Helm
+   name: prom-norbac-ubuntu-prometheus-server
+   namespace: prometheus
+   annotations:
+     {}

Upgrading release=prom-norbac-ubuntu, chart=prometheus-community/prometheus
Release "prom-norbac-ubuntu" does not exist. Installing it now.
NAME: prom-norbac-ubuntu
LAST DEPLOYED: Thu Mar  9 16:57:54 2023
NAMESPACE: prometheus
STATUS: deployed
REVISION: 1
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prom-norbac-ubuntu-prometheus-server.prometheus.svc.cluster.local


Get the Prometheus server URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9090


The Prometheus alertmanager can be accessed via port  on the following DNS name from within your cluster:
prom-norbac-ubuntu-prometheus-%!s(<nil>).prometheus.svc.cluster.local


Get the Alertmanager URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9093
#################################################################################
######   WARNING: Pod Security Policy has been disabled by default since    #####
######            it deprecated after k8s 1.25+. use                        #####
######            (index .Values "prometheus-node-exporter" "rbac"          #####
###### .          "pspEnabled") with (index .Values                         #####
######            "prometheus-node-exporter" "rbac" "pspAnnotations")       #####
######            in case you still need it.                                #####
#################################################################################


The Prometheus PushGateway can be accessed via port 9091 on the following DNS name from within your cluster:
prom-norbac-ubuntu-prometheus-pushgateway.prometheus.svc.cluster.local


Get the PushGateway URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus-pushgateway,component=pushgateway" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9091

For more information on running Prometheus, visit:
https://prometheus.io/

Listing releases matching ^prom-norbac-ubuntu$
prom-norbac-ubuntu      prometheus      1               2023-03-09 16:57:54.571803 +0800 CST    deployed        prometheus-19.7.2v2.41.0


UPDATED RELEASES:
NAME                 CHART                             VERSION
prom-norbac-ubuntu   prometheus-community/prometheus    19.7.2

执行 helmfile apply 命令后,Helmfile 会进行如下操作:

  • 添加 repositories 中声明的 repo
  • 运行 helm diff 进行对比
  • 根据 release 中声明的配置,安装或更新 chart

这样我们就成功安装了一个 Prometheus 的应用了。

此外 helmfile 和 helm templete 一样也可以使用 Go templates,同时还有一个特殊的功能 requiredEnv,该函数允许声明模板渲染所需的特定环境变量,如果环境变量未设置或为空,则渲染失败返回错误信息。也就是可以在 helmfile 中直接使用环境变量,使用方式如下:

repositories:
- name: your-private-git-repo-hosted-charts https://{{ requiredEnv "GITHUB_TOKEN"}}@raw.githubusercontent.com/cnych/helm-repo-in-github/master/
releases:
  - name: {{ requiredEnv "NAME" }}-vault
    namespace: {{ requiredEnv "NAME" }}
    chart: xxxx/vault-secret-manager
    values:
      - db:
          username: {{ requiredEnv "DB_USERNAME" }}
          password: {{ requiredEnv "DB_PASSWORD" }}
    set:
      - name: proxy.domain
        value: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com
      - name: proxy.scheme
        value: {{ env "SCHEME" | default "https" }}

另外 helmfile 支持 Semver 2.0 的版本号,可以锁定主版本,防止误升级导致的错误。

releases:
  - name: vault
    namespace: vault
    version: ~1.24.1 # 限制版本 >=1.24.1 && < 1.25.0

同时还能通过 helmfile deps 命令生成 lock 文件,在 CD 时,除非修改 lock 文件,否无法发布新版本。helmfile.lock 内容如下:

version: v0.135.0
dependencies:
  - name: prometheus
    repository: https://charts.helm.sh/stable
    version: 11.12.1
digest: sha256:a5158f1361f2bbc4e73a80a22dd92b44538bdebeb2419658c36e31aa603b05fd
generated: "2020-12-23T16:26:57.42503+08:00"

当需要更新时,再次执行 helmfile deps 即可。

同样 helmfile 原生支持多环境支持,当执行 helmfile 命令的时候可以通过 --environment NAME 参数来指定环境,如果没有指定则默认使用 default。比如我们这里有三个文件 helmfile.yamlproduction.yamldefault.yaml

# helmfile.yaml
environments:
  default:
    values:
    - default.yaml
  production:
    values:
    - production.yaml

releases:
- name: myapp-{{ .Values.releaseName }} # 根据环境名,可能是 `dev` 或 `prod`
  values:
  - url: { .Values.domain }} # 根据环境名,可能是`dev.example.com`或`prod.example.com`
{{ if eq .Environment.Name "production" }} # 使用 Go template 的,如果是 prod 环境,指定下面的 values 文件
      - values/production-specified-values.yaml
{{ end }}
---
# production.yaml
domain: prod.example.com
releaseName: prod
---
# default.yaml
domain: dev.example.com
releaseName: dev

在执行 helmfile 时,只需使用 --environment 指定需要安装的环境即可:

$ helmfile --environment production apply

同样的 Helmfile 也支持 hook 功能,hook 是一个每次发布的扩展点,它由以下部分组成:

  • events
  • command
  • args
  • showlogs

helmfile 在运行时,会触发各种事件,一旦事件触发,相关的 hook 就会被执行,目前支持的如下事件:

  • prepare
  • presync
  • preuninstall
  • postuninstall
  • postsync
  • cleanup

下面这个示例,会打印事件触发时的的上下文信息。

environments:
  default:
  prod:

releases:
  - name: myapp
    chart: mychart
    # *snip*
    hooks:
      - events: ["prepare", "cleanup"]
        showlogs: true
        command: "echo"
        args: [
            "{{`{{.Environment.Name}}`}}",
            "{{`{{.Release.Name}}`}}",
            "{{`{{.HelmfileCommand}}`}}\
            ",
          ]

执行命令,可以看到 command 执行成功:

$ helmfile -e prod sync
helmfile.yaml: basePath=.

hook[prepare] logs | prod myapp sync

这也是个十分好用的功能,可以为不同的事件配置不同的 hook,这样在 CD 出现问题时,通过 hook 可以第一时间收到通知,并快速定位问题。

下面的配置是 helmfile.yaml 文件的完整的可配置示例:

# 声明 repo 配置
repositories:
- name: <repo-name>
  # url: repo url
  # 可以设置基础配置 或 tls 认证
  # certFile: certificate 文件
  # keyFile: key 文件
  # username: 用户名
  # password: 密码

# helm 二进制文件的路径
helmBinary: path/to/helm3

# helm 的一些默认设置,这些配置与 `helm SUBCOMMAND` 相同,可以通过这个配置声明一些,默认的配置
helmDefaults:
  tillerNamespace: tiller-namespace  #dedicated default key for tiller-namespace
  tillerless: false                  #dedicated default key for tillerless
  kubeContext: kube-context          #dedicated default key for kube-context (--kube-context)
  cleanupOnFail: false               #dedicated default key for helm flag --cleanup-on-fail
  # additional and global args passed to helm (default "")
  args:
    - "--set k=v"
  # verify the chart before upgrading (only works with packaged charts not directories) (default false)
  verify: true
  # wait for k8s resources via --wait. (default false)
  wait: true
  # time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
  timeout: 600
  # performs pods restart for the resource if applicable (default false)
  recreatePods: true
  # forces resource update through delete/recreate if needed (default false)
  force: false
  # when using helm 3.2+, automatically create release namespaces if they do not exist (default true)
  createNamespace: true
  ...

# 为 helmfile 中所有的 release 设置相同的 label,可用于为所有 release 标记相同的版本
commonLabels:
  hello: world

# 设置 release 配置(支持多 release)
releases:
  # 远程 chart 示例(chart 已经上传到 remote 仓库)
  - name: vault                            # name of this release
    namespace: vault                       # target namespace
    createNamespace: true                  # helm 3.2+ automatically create release namespace (default true)
    labels:                                # Arbitrary key value pairs for filtering releases
      foo: bar
    chart: roboll/vault-secret-manager     # the chart being installed to create this release, referenced by `repository/chart` syntax
    version: ~1.24.1                       # the semver of the chart. range constraint is supported
    condition: vault.enabled               # The values lookup key for filtering releases. Corresponds to the boolean value of `vault.enabled`, where `vault` is an arbitrary value
    missingFileHandler: Warn # set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues.
    # Values files used for rendering the chart
    values:
      # Value files passed via --values
      - vault.yaml
      # Inline values, passed via a temporary values file and --values, so that it doesn't suffer from type issues like --set
      - address: https://vault.example.com
      # Go template available in inline values and values files.
      - image:
          # The end result is more or less YAML. So do `quote` to prevent number-like strings from accidentally parsed into numbers!
          # See https://github.com/roboll/helmfile/issues/608
          tag: {{ requiredEnv "IMAGE_TAG" | quote }}
          # Otherwise:
          #   tag: "{{ requiredEnv "IMAGE_TAG" }}"
          #   tag: !!string {{ requiredEnv "IMAGE_TAG" }}
        db:
          username: {{ requiredEnv "DB_USERNAME" }}
          # value taken from environment variable. Quotes are necessary. Will throw an error if the environment variable is not set. $DB_PASSWORD needs to be set in the calling environment ex: export DB_PASSWORD='password1'
          password: {{ requiredEnv "DB_PASSWORD" }}
        proxy:
          # Interpolate environment variable with a fixed string
          domain: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com
          scheme: {{ env "SCHEME" | default "https" }}
    # Use `values` whenever possible!
    # `set` translates to helm's `--set key=val`, that is known to suffer from type issues like https://github.com/roboll/helmfile/issues/608
    set:
    # single value loaded from a local file, translates to --set-file foo.config=path/to/file
    - name: foo.config
      file: path/to/file
    # set a single array value in an array, translates to --set bar[0]={1,2}
    - name: bar[0]
      values:
      - 1
      - 2
    # set a templated value
    - name: namespace
      value: {{ .Namespace }}
    # will attempt to decrypt it using helm-secrets plugin

  # 本地 chart 示例(chart 保存在本地)
  - name: grafana                            # name of this release
    namespace: another                       # target namespace
    chart: ../my-charts/grafana              # the chart being installed to create this release, referenced by relative path to local helmfile
    values:
    - "../../my-values/grafana/values.yaml"             # Values file (relative path to manifest)
    - ./values/{{ requiredEnv "PLATFORM_ENV" }}/config.yaml # Values file taken from path with environment variable. $PLATFORM_ENV must be set in the calling environment.
    wait: true

# 可以嵌套其他的 helmfiles,支持从本地和远程拉取 helmfile
helmfiles:
- path: path/to/subhelmfile.yaml
  # label 选择器可以过滤需要覆盖的 release
  selectors:
  - name=prometheus
  # 覆盖 value
  values:
  # 使用文件覆盖
  - additional.values.yaml
  # 覆盖单独的 key
  - key1: val1
- # 远程拉取配置
  path: git::https://github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0
# 如果指向不存在路径,则打印告警错误
missingFileHandler: Error

# 多环境管理
environments:
  # 当没有设置 `--environment NAME` 时,使用 default
  default:
    values:
    # 内容可以是文件路径或者 key:value
    - environments/default/values.yaml
    - myChartVer: 1.0.0-dev
  # "production" 环境,当设置了 `helmfile --environment production sync` 时
  production:
    values:
    - environment/production/values.yaml
    - myChartVer: 1.0.0
    # disable vault release processing
    - vault:
        enabled: false
    ## `secrets.yaml` is decrypted by `helm-secrets` and available via `{{ .Environment.Values.KEY }}`
    secrets:
    - environment/production/secrets.yaml
    # 当找不到 `environments.NAME.values` 时,可以设置为 "Error", "Warn", "Info", "Debug",默认是 "Error"
    missingFileHandler: Error

# 分层管理,可以将所有文件合并,顺序为:environments.yaml < - defaults.yaml < - templates.yaml < - helmfile.yaml
bases:
- environments.yaml
- defaults.yaml
- templates.yaml

# API 功能
apiVersions:
- example/v1

Helmfile 很大程度上弥补了 Helm 的不足,提高部署的可观测性和可重复性,提高了效率,在我们做 CI/CD 流程中值得使用。