Helm 最近是越来越得到大家的认可了,其实一年之前我们团队就开始用了,而今天我是想把这个工具的使用经验总结下。

Kubernetes 上手一段时候,就会发现他的资源描述文件的管理是个问题,很容易有过多相似的文件,这时候可能就会有做成同一份模板加不同环境的输入参数的想法,可惜 kubectl 不支持。

而在这之前,我们也试过其它的模板系统,比如 ktmpl,这些小工具的特点就是上手挺快,只是如果要其它比较高级的功能的话(如模板渲染的流程控制,依赖管理等),只能自己去开发了。

在上手 Helm 之前,有一段时间觉得这个工具不怎么好用,因为需要学挺多东西,还得以 Chart 的方式管理各个项目,不过一旦上手后,就会发现用它管理 k8s 上的部署还是非常方便的,功能挺强大,而且放在 Git 中管理起来也非常方便。

好,下面开始讲重点,需要你有点基础,最好先去看一遍 官方文档,因为我不会说的很仔细,会挑选点比较重要的说说,毕竟再仔细也不能比得过官方的文档。

基础概念

  • Helm:客户端,主要负责管理本地的 Charts、repositories 以及与服务端通信,;
  • Tiller:安装在 k8s 集群中的服务端,是实际用来管理安装在 k8s 中应用的,就是将模板与 values 合并,当然实际开发过程中,也可以安装在 k8s 集群之外
  • Chart:是用来管理模板与默认 values 的项目,也可以认为是一个 package,可以发布到专门的 repository;

安装 & 初始化

1
helm init

这个命令会在本地初始化配置,同时在远程 k8s 上的 kube-system 中安装服务端应用,也就是 Tiller 。

这里有三个参数需要注意下:

  • –client-only:也就是不安装服务端应用,这在 CI&CD 中可能需要,因为通常你已经在 k8s 集群中安装好应用了,这时只需初始化 helm 客户端即可;
  • –history-max:最大历史,当你用 helm 安装应用的时候,helm 会在所在的 namespace 中创建一份安装记录,随着更新次数增加,这份记录会越来越多;
  • –tiller-namespace:默认是 kube-system,你也可以设置为其它 namespace;

Charts

可以将它简单与 npm 对比下

1
2
3
4
npm          <====> helm
node modules <====> charts
registry <====> repository
package.json <====> requirements.yaml

这样,是不是能快一点理解?那么,接下来就是如何开发 Chart 了,最重要的肯定是模板了。

模板

由于是用 Golang 开发的,使用的也是 Go 的模板语法,举个简单的例子:

1
2
3
4
5
6
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: {{ .Values.config }}

这个模板,用到了 Release 以及 Values 这两个内置变量,因此当用 Helm 安装到 k8s 中的时候,Tiller 会将模板中的变量渲染成 k8s 可用的 yaml 文本。

其它内置变量看 这里

另外,就是 values 文件了,它最大的特点就在于可以 覆盖 ,举例来说,假如你运行的命令是:

1
helm install -n test ./mychart -f values1.yaml -f values2.yaml

那么,values2 中的值就会覆盖 value1 中同名 key 的值,利用这一点,就可以用不同的 values 文件区分出不同的环境。

假如你分别在云服务商的两个可用区内,有两个类似的 k8s 集群(比如双活架构),那么,同一个应用在两边部署的时候,就可以采取如下类似的文件目录了:

1
2
3
4
5
6
7
8
9
common.yaml # zone-a 与 zone-b 共享
zone-a/
front-app.yaml
backend-api.yaml
worker.yaml
zone-b/
front-app.yaml
backend-api.yaml
worker.yaml

然后,在不同的环境中,用不同的 values 文件即可,而其中 common.yaml 便可以在不同环境中共享一些 values。

1
helm install -n test ./mychart -f common.yaml -f zone-a/font-app.yaml

另外,如果需要将文件配置放到 k8s 的 Secrets 中,可以这么做:

1
2
3
4
5
6
7
8
9
10
11
12
{{- $fullName := include "chart.fullname" . -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ $fullName }}
labels:
app: {{ template "chart.name" . }}
chart: {{ template "chart.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
data:
{{ (.Files.Glob "bar/*").AsSecrets | indent 2 }}

几个小技巧

Image pull credentials

有些时候,Docker 镜像可能需要用户名与密码去 registry 拉取,那么,你就需要专门为此创建一个模板了。

比如 value 是:

1
2
3
4
imageCredentials:
registry: quay.io
username: someone
password: sillyness

而模板就是:

1
2
3
{{- define "imagePullSecret" }}
{{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .Values.imageCredentials.registry (printf "%s:%s" .Values.imageCredentials.username .Values.imageCredentials.password | b64enc) | b64enc }}
{{- end }}

当然,需要注意多个 Deployment 共享一个 Chart 的情况,这时候可能会出现 secrets 冲突的情况,可考虑单为此单独创建一个 Config Chart,然后作为 App Chart 的依赖。

配置更新后 Pod 自动重启

利用 k8s 的 Deployment 更改后的自动更新 [1],我们可以用来更新应用配置,简单说就是更新 Secrets 或 ConfigMaps 后,计算它的最新 hash 值,然后将这个 hash 值 patch 到相应的 Deployment 中。

1
2
3
4
5
6
7
kind: Deployment
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
[...]

这样,假如这个配置有问题,比如造成应用崩溃了,k8s 也会认为新的 ReplicaSet 失败了,不会将流量导过去,从而不继续滚动更新,避免了了由配置更新导致的应用崩溃问题。

P.S.

这篇文章可以看看:draft-vs-gitkube-vs-helm-vs-ksonnet-vs-metaparticle-vs-skaffold

主要讲的是 helm 与其它工具的对比,比如 draft 可以帮你快速初始化一个对应开发语言的 helm Chart,而 ksonnet 与 Helm 非常类似,可重点看看;

另外,我接下来还尝试把 Helm 写成一个系列,会细讲我们是如何实践这个工具的,比如应用配置管理,与 CI&CD 集成等,敬请期待。

Ref

  1. https://stackoverflow.com/questions/37317003/restart-pods-when-configmap-updates-in-kubernetes

首发于 Github issues: https://github.com/xizhibei/blog/issues/89 ,欢迎 Star 以及 Watch

本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可
作者:习之北 (@xizhibei)
原链接:https://blog.xizhibei.me/2018/10/20/introduction-to-helm-the-basic/