如何使用Helm进行本地开发

Helm是kubernetes的官方包管理工具。根据官网上的描述Helm is the best way to find, share, and use software built for Kubernetes.可以看出helm在kubernetes社区中的定位。

这篇文章并不是helm的入门文章,而是着重于如何在本地开发chart。希望进行helm入门的同学可以参考官方文档。

概述

本文会分为两个部分来探讨如何在本地开发chart,分别是:

  • Chart的规范
  • Helm提供的本地开发功能

Chart的规范

根据定义,一个Chart是一些有相关性的Kubernetes资源的集合。一个chart可以是一个简单的应用,比如memcached,或者是一个复杂的集合,比如一个full-stack的web的应用,含有server,ui,database,cache等等。

Chart从本质上只不过是一些文件,不过这些文件需要满足一定的规范,比如目录的规范和文件名的规范。

Chart的目录结构

根据规定,符合如下目录结构的目录就是一个Chart,目录名即为Chart名(不包含版本信息):

1
2
3
4
5
6
7
8
9
10
wordpress/
Chart.yaml # A YAML file containing information about the chart
LICENSE # OPTIONAL: A plain text file containing the license for the chart
README.md # OPTIONAL: A human-readable README file
requirements.yaml # OPTIONAL: A YAML file listing dependencies for the chart
values.yaml # The default configuration values for this chart
charts/ # OPTIONAL: A directory containing any charts upon which this chart depends.
templates/ # OPTIONAL: A directory of templates that, when combined with values,
# will generate valid Kubernetes manifest files.
templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes

虽然这里看到charts和templates文件夹都是optional的,但是至少需要有一个存在,chart才是合法的。

Chart.yaml文件

每个Chart都必须有一个Chart.yaml文件,这个文件的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
name: The name of the chart (required)
version: A SemVer 2 version (required)
description: A single-sentence description of this project (optional)
keywords:
- A list of keywords about this project (optional)
home: The URL of this project's home page (optional)
sources:
- A list of URLs to source code for this project (optional)
maintainers: # (optional)
- name: The maintainer's name (required for each maintainer)
email: The maintainer's email (optional for each maintainer)
url: A URL for the maintainer (optional for each maintainer)
engine: gotpl # The name of the template engine (optional, defaults to gotpl)
icon: A URL to an SVG or PNG image to be used as an icon (optional).
appVersion: The version of the app that this contains (optional). This needn't be SemVer.
deprecated: Whether or not this chart is deprecated (optional, boolean)
tillerVersion: The version of Tiller that this chart requires. This should be expressed as a SemVer range: ">2.0.0" (optional)

Chart的版本

每个Chart都必须有一个版本号,版本号必须遵守语义化版本规范V2。每个package(Chart打包后的东西)同时由name和version来唯一确定。

比如,一个叫做nginx的版本为1.2.3的Chart,打包后就是nginx-1.2.3.tgz

更复杂的语义化版本号是被支持的,比如version: 1.2.3-alpha.1+ef365但是非语义化的版本是不被允许的。

Helm和Tiller都会使用Chart的名称+版本来唯一标识一个package,所以Chart.yaml里面的版本一定要对应package的文件名。

appVersion

appVersion其实并没啥用,只是指定了Chart包含的应用的版本,对helm和tiller来说并不会有啥影响,也不需要和Chart的version一致。自己随便写都可以……

Deprecating Chart

可以通过在Chart.yaml里面把deprecated设为true来标识一个Chart已经是deprecated状态。

License,ReadME和Notes

一个Chart还可以有License来标识License信息,README.md来包含一些介绍信息,以及一个templates/NOTES.txt文件来指导如何去安装或者使用。

templates/NOTES.txt文件会被当做普通的template来对待(意味着其中可以有变量),并且会在每次helm status之后和helm install之后被打印到STDOUT。

比如stable/mysql的NOTES.txt如下:

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
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
{{ template "mysql.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local

To get your root password run:

MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "mysql.fullname" . }} -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 {{ template "mysql.fullname" . }} -p

To connect to your database directly from outside the K8s cluster:
{{- if contains "NodePort" .Values.service.type }}
MYSQL_HOST=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath='{.items[0].status.addresses[0].address}')
MYSQL_PORT=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "mysql.fullname" . }} -o jsonpath='{.spec.ports[0].nodePort}')

{{- else if contains "ClusterIP" .Values.service.type }}
MYSQL_HOST=127.0.0.1
MYSQL_PORT={{ default "3306" .Values.service.port }}

# Execute the following commands to route the connection:
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "mysql.fullname" . }}" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward $POD_NAME {{ default "3306" .Values.service.port }}:{{ default "3306" .Values.service.port }}

{{- end }}

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

可以看出来,NOTES.txt是用来给用户作使用上的指导的。

Chart的依赖

我们都知道,软件开发过程中,复用是一个很重要的概念,同样的,Chart也可以依赖于其它的Chart,可以复用其它的Chart的内容。

使用requirements.yaml

Helm提供了两种对Chart复用的方法,第一种是在requirements.yaml中指定依赖的Chart,如下:

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

如果说需要对一个chart复用多次,可以这么干:

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

除此之外,Helm还可以选择性的去使用依赖的chart,具体可以参考tags and condition

直接使用charts来手动管理

第二种是直接把需要用的Chart放到charts文件夹下。一般情况下推荐使用第一种,第二种是在需要对依赖的chart做魔改的情况下用到的。

Helm还提供了helm dep这个命令来方便对依赖的管理,之后会介绍到。

依赖的一些实现细节

helm installhelm upgrade的时候,helm会把依赖和当前chart打包成一个集合一起送给tiller,然后(目前是)按照类型+字母顺序来apply,并不是先去install依赖再去install当前的chart。

例如,我们有一个chart,会有以下三个东西:

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

这个chart依赖于另一个chart,有如下三个东西:

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

那么在安装或者升级的过程中,顺序如下:

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

Helm客户端提供的和本地开发相关的功能

Helm的客户端提供了一些和本地开发相关的命令,这里简单介绍一下。

helm completion

顾名思义,提供了命令补全,使用方式也比较简单:

1
$ source <(helm completion zsh)

helm create

可以通过这个命令直接创建出一个符合Chart规范的目录出来,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ helm create myweb
$ tree myweb
myweb
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── ingress.yaml
│   └── service.yaml
└── values.yaml

2 directories, 7 files

helm dependency

顾名思义,是用来进行依赖管理的,可以被简写为helm dep,具体使用如下:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
$ helm dep
Manage the dependencies of a chart.

Helm charts store their dependencies in 'charts/'. For chart developers, it is
often easier to manage a single dependency file ('requirements.yaml')
which declares all dependencies.

The dependency commands operate on that file, making it easy to synchronize
between the desired dependencies and the actual dependencies stored in the
'charts/' directory.

A 'requirements.yaml' file is a YAML file in which developers can declare chart
dependencies, along with the location of the chart and the desired version.
For example, this requirements file declares two dependencies:

# requirements.yaml
dependencies:
- name: nginx
version: "1.2.3"
repository: "https://example.com/charts"
- name: memcached
version: "3.2.1"
repository: "https://another.example.com/charts"

The 'name' should be the name of a chart, where that name must match the name
in that chart's 'Chart.yaml' file.

The 'version' field should contain a semantic version or version range.

The 'repository' URL should point to a Chart Repository. Helm expects that by
appending '/index.yaml' to the URL, it should be able to retrieve the chart
repository's index. Note: 'repository' can be an alias. The alias must start
with 'alias:' or '@'.

Starting from 2.2.0, repository can be defined as the path to the directory of
the dependency charts stored locally. The path should start with a prefix of
"file://". For example,

# requirements.yaml
dependencies:
- name: nginx
version: "1.2.3"
repository: "file://../dependency_chart/nginx"

If the dependency chart is retrieved locally, it is not required to have the
repository added to helm by "helm add repo". Version matching is also supported
for this case.

Usage:
helm dependency [command]

Aliases:
dependency, dep, dependencies


Available Commands:
build rebuild the charts/ directory based on the requirements.lock file
list list the dependencies for the given chart
update update charts/ based on the contents of requirements.yaml

Flags:
-h, --help help for dependency

Use "helm dependency [command] --help" for more information about a command.

helm fetch

一看这就是个下载别的chart的命令,为啥我要说和本地开发有关系呢?

因为我认为,helm的官方repo里面的chart最大的作用就是作为一个best practice来展示给使用者一个示例。

所以,当不知道该怎么写的时候,去抄吧😁。

helm lint

顾名思义,用来检查一个Chart是否存在问题。

如果说有错误,会报出error,并返回非零值。

我们就用刚才的myweb来试手:

1
2
3
4
5
$ helm lint myweb
==> Linting myweb
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, no failures

helm package

这个命令是当一个chart写完后用来把一个chart打包成chartName-version.tgz的。一般只有在发布的时候使用,提供了比较多的功能,比如sign之类的,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ helm package --help
This command packages a chart into a versioned chart archive file. If a path
is given, this will look at that path for a chart (which must contain a
Chart.yaml file) and then package that directory.

If no path is given, this will look in the present working directory for a
Chart.yaml file, and (if found) build the current directory into a chart.

Versioned chart archives are used by Helm package repositories.

Usage:
helm package [flags] [CHART_PATH] [...]

Flags:
-u, --dependency-update update dependencies from "requirements.yaml" to dir "charts/" before packaging
-d, --destination string location to write the chart. (default ".")
--key string name of the key to use when signing. Used if --sign is true
--keyring string location of a public keyring (default "/Users/daniel/.gnupg/pubring.gpg")
--save save packaged chart to local chart repository (default true)
--sign use a PGP private key to sign this package
--version string set the version on the chart to this semver version

我们还是用刚才的myweb作为例子:

1
2
$ helm package myweb
Successfully packaged chart and saved it to: /Users/daniel/Works/k8s/helm/myweb-0.1.0.tgz

helm serve

这个命令是用来在本地开启一个repo server的,可以用来本地测试使用。

helm template

这个命令可以在本地渲染出template来检查是否正确,具体使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ helm template --help
Render chart templates locally and display the output.

This does not require Tiller. However, any values that would normally be
looked up or retrieved in-cluster will be faked locally. Additionally, none
of the server-side testing of chart validity (e.g. whether an API is supported)
is done.

To render just one template in a chart, use '-x':

$ helm template mychart -x templates/deployment.yaml

Usage:
helm template [flags] CHART

Flags:
-x, --execute stringArray only execute the given templates
--kube-version string override the Kubernetes version used as Capabilities.KubeVersion.Major/Minor (e.g. 1.7)
-n, --name string release name (default "RELEASE-NAME")
--name-template string specify template used to name the release
--namespace string namespace to install the release into
--notes show the computed NOTES.txt file as well
--set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
-f, --values valueFiles specify values in a YAML file (can specify multiple) (default [])

我们仍然以myweb作为例子:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
$ helm template myweb
---
# Source: myweb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: RELEASE-NAME-myweb
labels:
app: myweb
chart: myweb-0.1.0
release: RELEASE-NAME
heritage: Tiller
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: myweb
release: RELEASE-NAME

---
# Source: myweb/templates/deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: RELEASE-NAME-myweb
labels:
app: myweb
chart: myweb-0.1.0
release: RELEASE-NAME
heritage: Tiller
spec:
replicas: 1
template:
metadata:
labels:
app: myweb
release: RELEASE-NAME
spec:
containers:
- name: myweb
image: "nginx:stable"
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
readinessProbe:
httpGet:
path: /
port: 80
resources:
{}


---
# Source: myweb/templates/ingress.yaml

helm verify

这个命令是用来验证一个给定的chart是否被sign。在对安全性要求高的环境下有用。

helm plugin

最后是这个helm plugin,看到这个我们就能感觉到,helm瞬间有了无数的扩展性,需要什么功能如果helm不提供咱们就自己干一个加上去。

helm目前现在已经有了一些比较好的plugin,比如有一个plugin支持用template render出来之后再进行验证查错之类的。

如果有一些别的定制化的需求也可以通过自己写个plugin来完成。