如何使用 Helm 进行本地开发 | Pure White

1,294 阅读8分钟

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 是一些有相关性的 Kubernetes 资源的集合。一个 chart 可以是一个简单的应用,比如 memcached,或者是一个复杂的集合,比如一个 full-stack 的 web 的应用,含有 server,ui,database,cache 等等。

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

Chart 的目录结构

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

wordpress/
  Chart.yaml          
  LICENSE             
  README.md           
  requirements.yaml   
  values.yaml         
  charts/             
  templates/          
                      
  templates/NOTES.txt

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

Chart.yaml 文件

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

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: 
  - 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 
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 如下:

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,如下:

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 复用多次,可以这么干:

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 completion

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

$ source <(helm completion zsh)

helm create

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

$ 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,具体使用如下:

$ 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 来试手:

$ 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 之类的,如下:

$ 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 作为例子:

$ 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 来检查是否正确,具体使用如下:

$ 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 作为例子:

$ 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 来完成。