4

前言

HelmK8S集群下面的一个包管理器,通过其工程师可将应用打包成一个整体,而用户可使用helm安装打包后的应用,其功能类似于apt-get之于ubuntu系统、yum/dnf之于redhat系统。本文作者将讲述如何通过helm打包应用,以及如何使用其部署应用,但读者须先了解K8S基础知识,如DeploymentSatefulsetServiceConfigmapSecretPV/PVC等,否则可先移步“迈入Docker、Kubernetes容器世界的大门”这里。

常规部署应用

本节使用清单文件部署一测试用例,其含两个服务hellogreeterhello依赖于greeter,调用关系如下所示:

<http get> --------> (hello) --------> (greeter)

执行如下命令clone仓库1,而后将示例部署于demo命名空间中。

git clone https://github.com/zylpsrs/helm-example.git
cd helm-example
kubectl create namespace demo
kubectl -n demo apply -f k8s

为简单考虑,本文示例仅使用deploymentingressservice这些技术部署且内容很简单,k8s目录如下所示:

$ tree k8s/
k8s/
├── deploy-greeter.yaml             # 部署greeter
├── deploy-hello.yaml               # 部署hello
├── ing-hello.yaml                  # 为hello服务创建ingress,故集群需部署有ingress控制器
├── svc-greeter.yaml                # 为greeter创建服务
└── svc-hello.yaml                  # 为hello创建服务

如下所示,本节在demo命名空间中部署有如下对象:

$ kubectl -n demo get deploy,svc,ingress,pod
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/greeter   1/1     1            1           20h
deployment.apps/hello     1/1     1            1           20h

NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/greeter   ClusterIP   10.100.44.79    <none>        9080/TCP   20h
service/hello     ClusterIP   10.106.116.73   <none>        9080/TCP   20h

NAME                       CLASS    HOSTS              ADDRESS         PORTS   AGE
ingress.extensions/hello   <none>   hello.app.zyl.io   192.168.120.6   80      20h

NAME                           READY   STATUS    RESTARTS   AGE
pod/greeter-7cbd47b5bd-6vtxk   1/1     Running   1          20h
pod/hello-6f8f75f799-z8kgj     1/1     Running   1          20h

若集群安装有ingress controller,则可通过ingress地址访问hello服务,否则在计算节点上通过其service地址访问,如下所示,我们调用hello服务时传递helloTo=<value>参数,其返回Hello, <value>!字符串。

$ curl http://hello.app.zyl.io/?helloTo=Ingress
Hello, Ingress!

$ curl http://10.106.116.73:9080/?helloTo=Service
Hello, Service!

上面通过清单文件部署了测试用例,了解了此测试用例功能,那么,本文接着将使用helm打包两应用,并使用其部署到集群中。为了后续helm部署测试,执行如下命令删除部署的应用。

kubectl delete -f k8s

使用Helm打包

首先,执行如下命令安装helm,这里选择v3版本:

wget -O helm.tgz https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz
tar pxz helm.tgz -C /usr/local/bin/ --strip-components=1
chmod 755 /usr/local/bin/helm

执行如下命令分别将hellogreeter打包2,其将生成两个目录。

helm create hello
helm create greeter

注意helm将以模板填充目录,故可参照模板按需调整。我们在values.yaml文件中定义变量,于模板文件中通过{{ <var> }}引用变量,而_helpers.tpl文件可定义一些模板,其支持条件判断等语法。

$ tree hello
hello
├── charts                       # 此chart依赖于哪些chart,则至于此目录下
├── Chart.yaml                   # 此文件含Chart元数据信息,如名称、版本,若无特别需要可保持默认
├── templates                    # 模板,此目录含部署清单文件,按需增减清单文件
│   ├── deployment.yaml
│   ├── _helpers.tpl             # 此文件中可定义一些模板,可通过include或template引用
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml                  # 变量配置文件,此文件定义变量以.Values开始

可选。以greeter为例配置图表的Chart.yaml文件,于其中添加元数据信息,其可通过.Chart.<var>被引用。

# greeter dir:
$ cat > Chart.yaml <<'EOF'
# 这几个字段模板中默认包含
apiVersion: v2
name: greeter
description: Receive messages from http and return welcome message
type: application
version: 0.1.0
appVersion: 1.0.0
# 可添加一些维护者等信息
maintainers:
- email: zylpsrs@sina.cn
  name: Yanlin. Zhou
sources:
- https://github.com/zylpsrs/helm-example.git
keywords:
- helm
- deployment
#home: https://github.com/zylpsrs/helm-example
#icon:
EOF

配置values.yaml文件于其中添加自定义变量信息,对于模板生成的默认信息我们按需增减。注意:为尽量少调整模板配置,对于我们不使用的配置最好不要删除。

  • greeter配置如下,部署一个replicat,镜像名为greeter:latestservice端口为9080,其使用ClusterIP方式:
# greeter dir:
$ vi values.yaml
replicaCount: 1

image:
  repository: registry.cn-hangzhou.aliyuncs.com/zylpsrs/example/greeter
  pullPolicy: IfNotPresent
  tag: latest

service:
  type: ClusterIP
  port: 9080
EOF
  • hello配置与greeter类似,我们调整了默认ingress配置(虽然未启用),因其依赖于greeter,我们于其中添加greeter信息,这里我们设置了一些变量以覆盖greeter默认的配置,如设置replicas2
# hello dir:
$ vi values.yaml
replicaCount: 1

image:
  repository: registry.cn-hangzhou.aliyuncs.com/zylpsrs/example/hello
  pullPolicy: IfNotPresent
  tag: latest

service:
  type: ClusterIP
  port: 9080

ingress:
  enabled: false
  hosts:
    - host: hello.app.zyl.io
      paths: [/]
      
greeter:
  enabled: true
  replicaCount: 2
  service:
    port: 9080

templates/service.yamlservice配置文件,对于两应用我们无需修改保持默认即可,其配置通过引用values.yaml的变量与_helpers.tpl中的模板来完善,如下所示:

# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "greeter.fullname" . }}                # 引用自_helpers.tpl文件
  labels:
    {{- include "greeter.labels" . | nindent 4 }}         # 引用自_helpers.tpl文件
spec:
  type: {{ .Values.service.type }}         # 此处引用values.yaml中得service.type
  ports:
    - port: {{ .Values.service.port }}     # 此处引用values.yaml中得service.port
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "greeter.selectorLabels" . | nindent 4 }} # 引用自_helpers.tpl文件

service配置虽无需修改,但其名称引用自_helpers.tpl中的模板名则需知晓,对于默认生成的greeter.fullname模板其定义有些复杂,但其值一般等于.Release.Name-.Chart.Name,如若安装chart时指定其Release名为test,则greeter.fullnametest-greeter

# templates/_helpers.tpl
...
{{- define "greeter.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
...

参考k8s/deploy-hello.yaml部署清单文件,其通过环境变量GREETER=http://greeter:9080设置greeter地址,如若通过helm安装greeter,其服务名称引用模板greeter.fullname的值为.Release.Name-greeter,那么于hello图表中,我们为greeter服务在_helpers.tpl中定一个模板名称,其值等于.Release.Name-greeter

# templates/_helpers.tpl
{{- define "hello.greeter.fullname" -}}
{{- $name := default "greeter" .Values.greeter.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}

两应用采用deployment无状态部署,此处我们调整templates/deployment.yaml文件:

  • 两应用均监听在9080端口,故调整容器端口为9080
# 调整如下配置的容器端口:
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
# 容器端口调整为9080:
          ports:
            - name: http
              containerPort: 9080
              protocol: TCP
  • 为求简单,我们不配置存活与就绪探针,删除livenessProbereadinessProbe项。
# 删除如下项:
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
  • hello依赖于greeter,在hello部署清单中设置环境变量GREETER:若hello部署清单中启用了greeter服务,则服务名称引用至模板名hello.greeter.fullname,否则为greeter
# 添加环境变量信息:
          env:
            - name: GREETER
            {{- if .Values.greeter.enabled }}
              value: 'http://{{ template "hello.greeter.fullname" . }}:9080'
            {{- else }}
              value: 'http://greeter:9080'
            {{- end }}

templates/ingress.yamlingress配置文件,在图表中可设置ingress.enabled=true启用ingress,对于此文件保持默认无需修改,而若图表不想提供ingress配置功能,可将此文件删除。

当通过helm部署图表时,其渲染templates/NOTES.txt文件以打印备注信息,一般我们无需修改此文件,但对于本例,我们将容器端口80调整为9080

最后,将如下依赖关系添加hello图表的requirements.yamlChart.yaml(推荐)文件中,这样当通过chart安装hello时,其将自动安装greeter应用,同时,我们还需要将greeter目录拷贝到hello/charts目录。

# hello dir:
$ cat >> Chart.yaml <<EOF
# 依赖关系
dependencies:
- name: greeter
  # 版本,如下匹配0.开头的所有版本
  version: 0.x.x
  # helm仓库地址,如同image镜像,helm也可推送到仓库,此仓库我们后续再配置
  repository: http://chartmuseum.app.zyl.io
  # 通过此条件判断是否依赖于此应用
  condition: greerer.enabled
  # 可为此应用添加一些标签
  tags:
    - http
EOF

$ cp -a ../greeter charts/
$ helm dep list                                 # 检查依赖关系,显示正常
NAME    VERSION REPOSITORY                      STATUS  
greeter 0.x.x   http://chartmuseum.app.zyl.io   unpacked

至此,我们已为hellogreeter分别部署了图表,而hello依赖于greeter,故在Chart.yaml文件中为其配置了依赖关系,其中指定的仓库名称后续我们再配置。

使用Helm部署

我们使用helm部署hello图表,其将自动部署其依赖的greeter应用,在hello图表目录下,执行如下命令创建一个名为test的实例:

$ helm -n demo install test .
NAME: test
LAST DEPLOYED: Thu Jun 18 15:51:13 2020
NAMESPACE: demo
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace demo -l "app.kubernetes.io/name=hello,app.kubernetes.io/instance=test" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace demo port-forward $POD_NAME 8080:9080

我们可执行helm list查看当前命名空间中通过helm部署的实例:

$ helm list -n demo
NAME    NAMESPACE       ... STATUS          CHART           APP VERSION
test    demo            ... deployed        hello-0.1.0     1.0.0   

如下所示,安装hello图表时也安装了greeter应用,因为在hello图表的values.yaml文件中配置greeter.replicaCount=2,故此处greeter部署了2pod

$ kubectl -n demo get svc,ingress,deploy,pod
NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/test-greeter   ClusterIP   10.99.226.226    <none>        9080/TCP   11m
service/test-hello     ClusterIP   10.105.187.216   <none>        9080/TCP   11m

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/test-greeter   2/2     2            2           11m
deployment.apps/test-hello     1/1     1            1           11m

NAME                                READY   STATUS    RESTARTS   AGE
pod/test-greeter-695ffd859d-pwc7s   1/1     Running   0          11m
pod/test-greeter-695ffd859d-r2gcw   1/1     Running   0          11m
pod/test-hello-779565bb5d-rdwbh     1/1     Running   0          11m

$ curl http://10.105.187.216:9080/?helloTo=Helm
Hello, Helm!

如同image镜像,我们也可将helm图表推送到仓库中以利于分发与部署,下面我们则搭建一个私有helm仓库并将chart推送到仓库中,而后使用此仓库获取chart并部署应用,但在此前,执行如下命令将当前部署的实例删除。

$ helm -n demo uninstall test
release "test" uninstalled

搭建Helm仓库

参考Helm官方文档The Chart Repository Guide可知有很多方式来搭建helm仓库,本文安装chartmuseum作为helm仓库,但是,如若已部署有harbor镜像仓库,则其已支持helm仓库,故无需重新为helm单独部署仓库了。

下面使用helmchartmuseum安装于独立的命令空间中,首先添加仓库3

$ helm repo add stable https://kubernetes-charts.storage.googleapis.com

为保证推送到仓库的charts持久化,我们需为chartmuseum提供一个持久化存储卷,如若读者参考此文章”使用kubeadm搭建一单节点k8s测试集群“部署测试k8s集群,则我们集群上将有一个nfs类型的默认持久化存储。

$ oc get storageclass
NAME            PROVISIONER                            RECLAIMPOLICY   VOLUMEBINDINGMODE ...
nfs (default)   cluster.local/nfs-server-provisioner   Delete          Immediate         ...

注意:若容器若使用nfs卷,则集群节点需安装nfs客户端,否则将无法挂载nfs

$ yum -y install nfs-utils

执行如下命令将chartmuseum部署到单独的命名空间helm-repo中,为简单考虑,这里不为仓库配置任何验证:任何人均可获取与上传chart包,若集群配置有ingress controler,则可为仓库配置ingress,此处主机名称配置为chartmuseum.app.zyl.io

$ kubectl create namespace helm-repo
$ cat > /tmp/values.yaml <<'EOF'
env:
  open:
    STORAGE: local
    # 默认禁用API操作,此处必须关闭,否则我们没法通过API管理chart
    DISABLE_API: false
    # 允许上传相同版本的chart
    ALLOW_OVERWRITE: true

persistence:
  enabled: true
  accessMode: ReadWriteOnce
  size: 8Gi

ingress:
  enabled: true
  hosts:
    - name: chartmuseum.app.zyl.io
      path: /
      tls: false
EOF
$ helm -n helm-repo install -f values.yaml mychart stable/chartmuseum

pod启动成功后,参考文档我们以其API接口上传一个测试chart到仓库,如下所示:

$ helm create mychart
$ helm package mychart
Successfully packaged chart and saved it to: /tmp/mychart-0.1.0.tgz

# upload:
$ curl --data-binary "@mychart-0.1.0.tgz" http://chartmuseum.app.zyl.io/api/charts
{"saved":true}

# list all charts:
$ curl http://chartmuseum.app.zyl.io/api/charts
{"mychart":[{"name":"mychart","version":"0.1.0",...]}

使用curl工具显示通过API管理仓库不友好,我们可安装[helm-push]()插件,而后执行helm push将上文创建的图表上传到仓库,如下所示:

$ helm plugin install https://github.com/chartmuseum/helm-push.git
$ helm repo add mychart http://chartmuseum.app.zyl.io
$ cd hello && helm push . mychart
$ cd .. && helm push greeter mychart
# or
$ helm push greeter http://chartmuseum.app.zyl.io

# list all charts:
$ helm repo update              # 先同步仓库信息
$ helm search repo mychart
NAME            CHART VERSION   APP VERSION     DESCRIPTION 
mychart/mychart 0.1.0           1.16.0          A Helm chart for Kubernetes
mychart/greeter 0.1.0           1.0.0           Receive messages from http and return ...
mychart/hello   0.1.0           1.0.0           Receive messages from http and return ...

从远程仓库部署

现在我们部署了helm仓库,已将chart上传到仓库中,并且通过helm repo add将仓库添加到了本地helm仓库中,那么本节我们就用此仓库安装图表。如下所示,我们希望为hello应用部署ingress,故通过参数--set key=value-f file覆盖默认配置。

$ cat > /tmp/values.yaml <<EOF
ingress:
  enabled: true
  hosts:
    - host: hello.app.zyl.io
      paths: [/]
EOF
$ helm -n demo install -f /tmp/values.yaml test mychart/hello

如下所示,此时demo命名空间中部署有ingress对象,我们通过其访问应用:

$ oc get ing,pod
NAME                            CLASS    HOSTS              ADDRESS         PORTS   AGE
ingress.extensions/test-hello   <none>   hello.app.zyl.io   192.168.120.6   80      78s

NAME                                READY   STATUS    RESTARTS   AGE
pod/test-greeter-695ffd859d-5mkvk   1/1     Running   0          78s
pod/test-greeter-695ffd859d-hqqtc   1/1     Running   0          78s
pod/test-hello-779565bb5d-2z9jv     1/1     Running   0          78s

$  curl http://hello.app.zyl.io/?helloTo=helmRepo
Hello, helmRepo!

结束语

通过本文我们学习了如何通过helm打包复杂的应用,虽然示例并不复杂,但其具备此能力,然而helm适合无状态应用,其具备第一天安装应用的能力,但第二天应用维护操作(如升级/备份)比较无能为力,鉴于此,作者将在后面讲解operator技术来处理此复杂应用。


  1. repo: 读者可从此仓库上获取本文所有代码
  2. chart: 包在helm中表示为chart
  3. crepo: helm项目地址在https://github.com/helm/chart...

我是读书人
114 声望136 粉丝

随意记录,想着啥,写啥