本篇已加入《.NET Core on K8S學習實踐系列文章索引》,可以點擊查看更多容器化技術相關系列文章。
一、關於Helm
1.1 為何需要Helm?
雖然K8S能夠很好地組織和編排容器,但是缺少一個更高層次的應用打包工具,而Helm就是專門干這個事的。
通過Helm能夠幫助開發者定義、安裝和升級Kubernetes中的容器雲應用。同時,也可以通過Helm進行容器雲應用的分享。
1.2 Helm的架構
Helm的整體架構如下圖(圖片來源-Kubernetes中文社區)所示:

Helm架構由Helm客戶端、Tiller服務器端和Chart倉庫所組成;
兩個重要概念:
(1)Chart是創建一個應用的信息集合,包括各種K8S對象的配置模板、參數定義等,可以理解為是apt、yum中的軟件安裝包;
(2)Release是Chart的運行實例,代表了一個正在運行的應用。
Tiller部署在Kubernetes中,Helm客戶端從Chart倉庫中獲取Chart安裝包,並通過與Tiller服務器的交互將其安裝部署到Kubernetes集群中。
簡單說來,Helm客戶端負責管理Chart,而 Tiller服務器則負責管理Release。
二、Helm的安裝和使用
2.1 Helm客戶端的安裝
執行以下命令將Helm客戶端安裝在能夠執行kubectl命令的節點上,這里假設我們安裝在k8s-master節點上進行示例演示:
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash
也可以通過下面的方式安裝:
wget https://storage.googleapis.com/kubernetes-helm/helm-v2.11.0-linux-amd64.tar.gz tar -zxvf helm-v2.11.0-linux-amd64.tar.gz cd linux-amd64/ cp helm /usr/local/bin/
驗證:查看helm版本
helm version

補充:為了提高使用命令行的效率,建議安裝helm命令補全腳本,命令如下:
cd ~ && helm completion bash > .helmrc echo "source .helmrc" >> .bashrc
重新登錄后,就可以方便地通過tab鍵來補全helm子命令和參數了,如下圖所示,當我們輸入helm install --之后按下Tab鍵,就會給我們參數提示了:

2.2 Tiller服務器的安裝
Tiller服務器本身也是作為容器化的一個應用運行在K8S集群中,這里我們簡單執行下面的命令即可安裝Tiller服務:
helm init
執行以上命令,會如下圖所示:

看到上圖中的提示信息,代表Helm服務端已經安裝成功。
這時,我們可以看看Tiller的Service、Deployment和Pod有沒有啟動起來:
(1)Service & Deployment

(2)Pod

如果看到其Status不是Running,那么很有可能是鏡像沒有拉取下來,可以曲線救國:即下載可訪問的鏡像然后修改Tag!
docker pull fishead/gcr.io.kubernetes-helm.tiller:v2.11.0 docker tag fishead/gcr.io.kubernetes-helm.tiller:v2.11.0 gcr.io/kubernetes-helm/tiller:v2.11.0
這時再次通過helm version命令驗證一下:

可以看到,我們已經可以成功看到客戶端和服務端的版本信息了,證明客戶端和服務端(Pod)都已經安裝成功了!
2.3 Helm的使用准備
Helm安裝好后,我們可以通過以下helm search來查看當前可安裝的Chart:

Note:Helm安裝時會為我們配置好兩個倉庫,一個是stable官方倉庫,另一個是local本地倉庫,上圖中顯示的都是stable官方倉庫中的Chart。
為了能夠執行install安裝,我們還需要事先為Tiller服務器添加集群權限,防止因Tiller服務器的權限不足而無法install。
# 創建serviceaccount資源tiller,屬於kube-system命名空間 kubectl create serviceaccount -n kube-system tiller # 創建 clusterrolebinding資源tiller-cluster-rule,集群角色為cluster-admin,用戶為kube-system:tiller kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller # 修改deployment tiller-deploy的配置,增加字段spec.template.spec.serviceAccount kubectl patch deploy -n kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

至此,使用Helm的准備工作就到此結束,后面我們就可以開始實踐安裝Chart了!
三、MySQL Chart實踐
3.1 初步安裝MySQL Chart
這里我們通過以下命令來通過官方倉庫安裝mysql:
helm install stable/mysql -n=edc-mysql --namespace=edc-charts
其中,-n 代表 release的名字,--namespace 指定了其所在namespace。
執行成功之后,會顯示一屏幕的提示信息,其中Notes部分包含了release的使用方法,可以重點關注一下。
這里我們通過以下命令來看看已經部署的release:
helm list

可以看到,該release的狀態已經是DEPLOYED,也可以看到其版本號是5.7.27。
下面再看看service、deployment、pod以及pvc的情況:


從上圖可以看到,由於還沒有位mysql准備PV(PersistentVolume,不了解此概念的童鞋可以參考這一篇《K8S數據管理》),導致當前release不可用,處於Pending狀態。接下來我們就要先解決PV的問題,讓release能夠正常運行起來!在此之前,為了后續方便演示,這里現將此chart刪除:
helm delete edc-mysql
3.2 為MySQL Chart准備PV
首先,按照約定准備一個edc-mysql-pv.yml,如下所示:
apiVersion: v1 kind: PersistentVolume metadata: name: edc-mysql-pv spec: capacity: storage: 8Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain #storageClassName: nfs nfs: path: /edc/k8s/nfsdata/edc-mysql-pv server: k8s-master
這里申請了一個8G的PV,用於適配mysql chart的默認配置要求,當然我們也可以通過修改自定義values.yaml來修改。
3.3 定制化安裝MySQL Chart
Helm有兩種方式傳遞配置參數實現定制化安裝,一種是指定自定義的values文件,另一種是通過--set直接傳入參數值。這里我們演示通過第二種,這里我們重新安裝mysql chart:
helm install stable/mysql -namespace=edc-charts --set mysqlRootPassword=edc123456 -n edison
驗證結果如下圖所示:

3.4 升級和回滾Release
這里假設我安裝的版本是5.7.14,這里我將其先升級為5.7.26來演示:
helm upgrade --set imageTag=5.7.26 edison stable/mysql
通過查看可以看到image已經換為了5.7.26:

通過helm history可以查看release的所有歷史版本:

Note:這里Revision 1是5.7.14版本,Revision 2是5.7.26版本,Revision 3是5.7.27版本。
這里我們通過helm rollback回退到Revision 1版本(即5.7.14版本),可以看到已經成功回退到了5.7.14版本:

四、自定義Chart實踐
4.1 創建Chart
首先,通過以下命令創建一個chart命名為mychart:
helm create mychart
Helm會幫我們創建目錄mychart,並生成各種chart文件。

這里我們需要關注的是values.yaml,修改其中的內容為我們之前演示的ASP.NET Core WebAPI應用鏡像:
# Default values for mychart. # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 image: repository: edisonsaonian/k8s-demo tag: latest pullPolicy: IfNotPresent service: type: NodePort port: 80 nodePort: 31000 ingress: enabled: false resources: limits: cpu: 1 memory: 228Mi requests: cpu: 100m memory: 128Mi
這里我們選擇NodePort的方式讓外部可以通過31000端口訪問到API,也設置了資源限制。
此外,我們再修改一下Templates目錄下的deployment和service兩個模板文件:
(1)deployment模板:重點關注兩個探針的配置
apiVersion: apps/v1beta2 kind: Deployment metadata: name: {{ include "mychart.fullname" . }} labels: app.kubernetes.io/name: {{ include "mychart.name" . }} helm.sh/chart: {{ include "mychart.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app.kubernetes.io/name: {{ include "mychart.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: labels: app.kubernetes.io/name: {{ include "mychart.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP # 探針 檢測項目是否存活 livenessProbe: httpGet: path: /api/values port: http # 探針 檢測項目是否啟動成功 readinessProbe: httpGet: path: /api/values port: http initialDelaySeconds: 30 periodSeconds: 60 resources: {{ toYaml .Values.resources | indent 12 }} {{- with .Values.nodeSelector }} nodeSelector: {{ toYaml . | indent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{ toYaml . | indent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{ toYaml . | indent 8 }} {{- end }}
(2)service模板:重點關注NodePort的配置
apiVersion: v1 kind: Service metadata: name: {{ include "mychart.fullname" . }} labels: app.kubernetes.io/name: {{ include "mychart.name" . }} helm.sh/chart: {{ include "mychart.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http # 添加nodePort nodePort: {{ .Values.service.nodePort }} protocol: TCP name: http selector: app.kubernetes.io/name: {{ include "mychart.name" . }} app.kubernetes.io/instance: {{ .Release.Name }}
編寫完成后,通過 helm lint 可以幫助我們快速驗證是否有語法錯誤:

4.2 安裝Chart
沒有語法錯誤檢測之后,便可以開始安裝Chart了,正式安裝之前我們可以通過以下命令來模擬安裝,它會輸出每個模板生成的yaml內容,幫助你檢查生成的yaml內容是否是你想要的或者正確的。
helm install --dry-run --debug
然后,這里我們選擇本地安裝Chart:
helm install mychart -n edc-api-release --namespace=aspnetcore
只需要簡單的一句話,就可以將chart部署到K8S集群中了,下面我們通過在外部訪問NodePort 31000端口來驗證一下是否部署成功:
(1)Node 1

(2)Node 2

兩個Node節點都可以訪問到,證明部署成功!
4.3 添加Chart到倉庫
通過測試之后,我們的Chart就可以發布到倉庫中供團隊成員使用了,像阿里雲、騰訊雲等雲服務商都已經提供了完善的Helm遠程倉庫,我們也可以自己搭建一個倉庫,任何的Web Server其實都可以作為一個chart倉庫。
下面我們在k8s-master上啟動給一個httpd容器,讓它來作為我們的本地chart倉庫。
docker run -d -p 8080:80 -v /var/www:/usr/local/apache2/htdocs/ httpd
然后,我們將mychart進行打包,helm會將其打包為一個tgz包:
helm package mychart
然后,我們為mychart包生成倉庫的index文件,並將其推送到本地chart倉庫中:
mkdir myrepo mv mychart-0.1.0.tgz myrepo/ helm repo index myrepo/ --url http://192.168.2.100:8080/charts
這里我們將httpd容器中的charts目錄作為chart倉庫,因此需要提前創建charts目錄,並將打好的包和index.yaml文件也上傳到該目錄中:

最后,我們將新倉庫添加到helm:
helm repo add edc-repo http://192.168.2.100:8080/charts helm repo list

可以看到,edc-repo已經添加到了helm中,代表可以從新的本地倉庫中下載和安裝mychart了!
4.4 使用自定義Chart
現在我們來從本地的新倉庫中下載和安裝mychart:
helm install edc-repo/mychart -n mychart-release --namespace=aspnetcore
安裝完成后再次驗證:
(1)Node 1

(2)Node 2

如果以后倉庫添加了新的chart,需要使用以下命令來更新本地的index文件:
helm repo update
五、小結
本文介紹了K8S的包管理器Helm的基本概念與安裝和使用,Helm能夠幫助我們像使用apt或yum那樣管理安裝、部署、升級和刪除容器化應用,最后演示了如何為我們的ASP.NET Core API應用開發自己的chart,並在團隊中共享chart。當然,關於Helm,筆者也是初學,還有很多地方沒有研究,希望此文能給初學者一點幫助,謝謝!
參考資料
(1)CloudMan,《每天5分鍾玩轉Kubernetes》
(2)李振良,《一天入門Kubernets教程》
(3)馬哥(馬永亮),《Kubernetes快速入門》
(4)潘猛,《Kubernetes筆記之Helm》
(5)雪雁(心萊科技),《利用Helm簡化Kubernetes應用部署(二)》
(6)周國通,《Kubernetes實戰篇之Helm填坑與基本命令》
