Helm工程实践:如何维护一个Helm工程

Helm简介

  • 包管理
  • 模板引擎:Go template

helm 3 渲染资源的顺序

假设有个chart “A” 创建了下面的Kubernetes对象:

  • namespace “A-Namespace”
  • statefulset “A-StatefulSet”
  • service “A-Service”

另外,A是依赖于chart B创建的对象:

  • namespace “B-Namespace”
  • replicaset “B-ReplicaSet”
  • service “B-Service”

安装/升级chart A后,会创建/修改一个单独的Helm版本。这个版本会按顺序创建/升级以下所有的Kubernetes对象:

  • A-Namespace
  • B-Namespace
  • A-Service
  • B-Service
  • B-ReplicaSet
  • A-StatefulSet

这是因为当Helm安卓/升级chart时,chart中所有的Kubernetes对象以及依赖会

  • 聚合成一个单一的集合;然后
  • 按照类型和名称排序;
  • 然后按这个顺序创建/升级。
    至此会为chart及其依赖创建一个包含所有对象的release版本。 Kubernetes类型的安装顺序会按照kind_sorter.go(查看Helm源文件)中给出的枚举顺序进行。

CRD的限制

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

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

chart 示例

开发流程

获取开发环境

使用Chart Starter 包

对于相似度很高的项目,可以使用chart starter ,可以节约修改 chart name的时间;clone 仓库到本地后,加入到HELM_DATA_HOME目录下的starters目录中。之后再根据每个业务组件自身需要进行修改

使用helm create

适合从零开始开发的chart

1
2
helm create mychart
rm -rf mychart/templates/*

在容器里开发

1
2
3
# Kubernetesk8s config copy~/.kube/config
$ docker image pull registry.xxx.com/xxx/helm-playground:xxxx
$ docker run -it --rm -v ~/.kube:/root/.kube xxxx /bin/bash

修改templates 和 values

修改需要部署的资源文件和values,保证一个应用的最小可部署资源集合;

开发建议

  • 命名规范:template文件用小写字母;values中的变量使用驼峰;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    $ tree ./ ./
    Chart.yaml
    README.md
    README.md.gotmpl
    templates
    NOTES.txt
    _helpers.tpl
    bootstrap-configmap.yaml
    clusterrole.yaml
    clusterrolebinding.yaml
    deployment.yaml
    init-job.yaml
    persistentvolume.yaml
    persistentvolumeclaim.yaml
    service-monitor.yaml
    service.yaml
    serviceaccount.yaml
    storageclass.yaml
    values.schema.json
    values.yaml
    1 directory, 18 files
    不要hardcode namespace,而是通过 – namespace flag 动态传入,后者灵活性更强。
1
2
3
4
5
# suggest
helm install business-defender ./ -n whale-component --debug --atomic
# not suggest
metadata:
namespace: {{ .Release.Namespace }}

https://github.com/helm/helm/issues/5465

  • _helpers.tpl 可类比成 Utils 类:比较通用,复用比较多 的逻辑 比较复杂的逻辑

在_helpers.tpl 定义Named Templates 时,统一以chart.name 开头。相当于把chart.name 当作name templates 的命名空间,避免引用冲突;

1
2
3
4
5
6
7
8
9
10
# good
{{/* Create a default fully qualified app name. */}}
{{- define "business-defender.fullname" -}}
...
{{- end }}
# bad
{{/* Create a default fully qualified app name. */}}
{{- define "fullname" -}}
...
{{- end }}
  • 给values写注释,并用helm-docs生成文档,用helm schema-gen 生成schema.json文件
    1
    2
    3
    4
    5
    6
    7
    global:
    # -- machine architecture
    hostArch: "amd64"
    # -- docker registry
    dockerRegistry: "registry.xxxx.com"
    # -- prometheus namespace
    monitorNamespace: "monitoring"

验证

语法验证

1
2
3
4
$ helm lint
==> Linting .
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed

渲染验证

1
2
helm template ./ --debug -n whale-component
helm install business-defender ./ -n whale-component --debug --dry-run

template 和 install –dry-run 的区别在于是否会和 Kubernetes Api Server 通行。使用 install –dry-run ,相当于helm 渲染资源后使用kubectl 的–dry-run 验证一样;因为可以和k8s通信,可以获取一些k8s的元数据,此时,.Capabilities 内置对象才有意义;

部署验证
验证时,需要在 defender 环境下验证,因为chart中可能包含一些预定的资源依赖,比如 password-secret;

1
2
3
4
5
6
7
8
9
10
# atomic flag
helm install business-defender ./ -n whale-component --debug --atomic

# wait delete
helm uninstall business-defender -n whale-component --wait --debug

$ helm list -n whale-component

# Release
$ helm get all business-defender -n whale-component

pre-commit:生成readme.md 和 values.schema.json

1
2
3
4
# helm plugin schema-genvaluesschema.jsonhelm installvalues.yaml.
helm schema-gen values.yaml > values.schema.json
# valuesreadme.md
helm-docs

发布

手动发布
需要手动安装 cm-push :https://github.com/chartmuseum/helm-push

1
2
3
4
$ helm plugin install https://github.com/chartmuseum/helm-push
$ helm repo add --username <username> --password <password> business https://registry.xxx.com/chartrepo
/business
$ helm cm-push --username=<username> --password=<password> . business --version 2.2.1l

另外也可以通过页面上传

CI发布

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
image:
name: registry.xxx.com/viper-ce/library/helm-tool:3.6.3
entrypoint: ["/bin/sh", "-c"]
variables:
HELM_CI_REPO_ADDR: https://registry.xxx.com/chartrepo/business
HELM_CI_REPO_NAME: business
stages:
- lint
- render
- release
lint-chart:
stage: lint
script:
- helm lint .
render-chart:
stage: render
script:
- helm template .
release-chart:
stage: release
script:
- export CHART_VERSION=$(awk '/^version/{print $NF}' Chart.yaml)
- helm repo add --username ${HELM_REPO_USERNAME} --password ${HELM_REPO_PASSWD} ${HELM_CI_REPO_NAME}
${HELM_CI_REPO_ADDR}
- helm push . ${HELM_CI_REPO_NAME} --version "${CHART_VERSION}-${CI_COMMIT_SHA:0:8}"
- helm push . ${HELM_CI_REPO_NAME} --version "${CHART_VERSION}" --force
only:
- master

合入master时才发布一个版本;
每次release 会push两次:

  • 带 commit id 的版本
  • 不带 commid id 的stable版本,强制覆盖仓库中的stable版本

repo管理

仓库选择:

是否需要单独创建一个project存储chartrepo,业务chart 和 中间件chart 是否需要区分?

版本管理

版本约定
helm chart 的版本管理主要是通过 Chart.yaml里面的appVersion和version去管理

  • appVersion:主要对应 应用 本身的版本
    比如 business-defender 的版本是1.0.2-SNAPSHOT
  • version:主要对应 helm chart 版本,helm package 通过读取 Chart.yaml 里的 version进行 chart 打包,然后推送到仓库

如 当前业务系统的版本为 2.2.0 ,helm chart的commit_id 6336fbe,此时version:2.2.0-6336fbe

1
2
version: 2.2.0
appVersion: "1.0.2-SNAPSHOT"

仓库中存在:

  • 以version/chart version/ 业务系统版本 的stable版本
  • 带有commit id 的开发版本