共计 10507 个字符,预计需要花费 27 分钟才能阅读完成。
前言
Helm是 K8S 集群下面的一个包管理器,通过其工程师可将应用打包成一个整体,而用户可使用 helm 安装打包后的应用,其功能类似于 apt-get
之于 ubuntu 系统、yum/dnf
之于 redhat
系统。本文作者将讲述如何通过 helm 打包应用,以及如何使用其部署应用,但读者须先了解 K8S 基础知识,如 Deployment、Satefulset、Service、Configmap、Secret、PV/PVC 等,否则可先移步“迈入 Docker、Kubernetes 容器世界的大门”这里。
常规部署应用
本节使用清单文件部署一测试用例,其含两个服务 hello、greeter,hello 依赖于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
为简单考虑,本文示例仅使用 deployment、ingress、service 这些技术部署且内容很简单,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
执行如下命令分别将 hello、greeter 打包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:latest,service 端口为 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 默认的配置,如设置 replicas 为2。
# hello dir:$ cat > values.yaml <<'EOF'
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
EOF
templates/service.yaml为 service 配置文件,对于两应用我们无需修改保持默认即可,其配置通过引用 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.fullname 为test-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
- 为求简单,我们不配置存活与就绪探针,删除 livenessProbe 与readinessProbe项。
# 删除如下项: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.yaml为 ingress 配置文件,在图表中可设置 ingress.enabled=true
启用 ingress,对于此文件保持默认无需修改,而若图表不想提供ingress 配置功能,可将此文件删除。
当通过 helm 部署图表时,其渲染 templates/NOTES.txt 文件以打印备注信息,一般我们无需修改此文件,但对于本例,我们将容器端口 80 调整为9080。
最后,将如下依赖关系添加 hello 图表的 requirements.yaml 或Chart.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
至此,我们已为 hello、greeter 分别部署了图表,而 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 部署了 2 个pod。
$ 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 单独部署仓库了。
下面使用 helm 将chartmuseum安装于独立的命令空间中,首先添加仓库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 技术来处理此复杂应用。
- repo: 读者可从此仓库上获取本文所有代码 ↩
- chart: 包在 helm 中表示为chart ↩
- crepo: helm 项目地址在 https://github.com/helm/chart… ↩