1. 程式人生 > 其它 >包管理工具 HELM(7)

包管理工具 HELM(7)

包管理工具 HELM(7)

1. Helm安裝使用

Helm這個東西其實早有耳聞,但是一直沒有用在生產環境,而且現在對這貨的評價也是褒貶不一。正好最近需要再次部署一套測試環境,對於單體服務,部署一套測試環境我相信還是非常快的,但是對於微服務架構的應用,要部署一套新的環境,就有點折磨人了,微服務越多、你就會越絕望的。雖然我們線上和測試環境已經都遷移到了kubernetes環境,但是每個微服務也得維護一套yaml檔案,而且每個環境下的配置檔案也不太一樣,部署一套新的環境成本是真的很高。如果我們能使用類似於yum的工具來安裝我們的應用的話是不是就很爽歪歪了啊?Helm就相當於kubernetes環境下的yum包管理工具。

1.1 用途

做為 Kubernetes 的一個包管理工具,Helm具有如下功能:

  • 建立新的 chart
  • chart 打包成 tgz 格式
  • 上傳 chart 到 chart 倉庫或從倉庫中下載 chart
  • Kubernetes叢集中安裝或解除安裝 chart
  • 管理用Helm安裝的 chart 的釋出週期

1.2 重要概念

Helm 有三個重要概念:

  • chart:包含了建立Kubernetes的一個應用例項的必要資訊
  • config:包含了應用釋出配置資訊
  • release:是一個 chart 及其配置的一個執行例項

1.3 Helm元件

Helm 有以下兩個組成部分: Helm Structrue

Helm Client 是使用者命令列工具,其主要負責如下:

  • 本地 chart 開發
  • 倉庫管理
  • 與 Tiller sever 互動
  • 傳送預安裝的 chart
  • 查詢 release 資訊
  • 要求升級或解除安裝已存在的 release

Tiller Server是一個部署在Kubernetes叢集內部的 server,其與 Helm client、Kubernetes API server 進行互動。Tiller server 主要負責如下:

  • 監聽來自 Helm client 的請求
  • 通過 chart 及其配置構建一次釋出
  • 安裝 chart 到Kubernetes叢集,並跟蹤隨後的釋出
  • 通過與Kubernetes互動升級或解除安裝 chart
  • 簡單的說,client 管理 charts,而 server 管理髮布 release

1.4 安裝

我們可以在Helm Realese頁面下載二進位制檔案,這裡下載的v2.10.0版本,解壓後將可執行檔案helm拷貝到/usr/local/bin目錄下即可,這樣Helm客戶端就在這臺機器上安裝完成了。

現在我們可以使用Helm命令檢視版本了,會提示無法連線到服務端Tiller

[root@node01 ~]# wget https://get.helm.sh/helm-v2.10.0-linux-amd64.tar.gz
[root@node01 ~]# tar xf helm-v2.10.0-linux-amd64.tar.gz 
[root@node01 ~]# ls linux-amd64/
helm  LICENSE  README.md
[root@node01 ~]# cp -a ./linux-amd64/helm /usr/local/bin/

[root@node01 ~]# helm version
Client: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}
Error: could not find tiller

要安裝 Helm 的服務端程式,我們需要使用到kubectl工具,所以先確保kubectl工具能夠正常的訪問 kubernetes 叢集的apiserver哦。

然後我們在命令列中執行初始化操作:

[root@node01 ~]# helm init --upgrade --tiller-image cnych/tiller:v2.10.0 --stable-repo-url https://cnych.github.io/kube-charts-mirror/
Creating /root/.helm 
Creating /root/.helm/repository 
Creating /root/.helm/repository/cache 
Creating /root/.helm/repository/local 
Creating /root/.helm/plugins 
Creating /root/.helm/starters 
Creating /root/.helm/cache/archive 
Creating /root/.helm/repository/repositories.yaml 
Adding stable repo with URL: https://cnych.github.io/kube-charts-mirror/ 
Adding local repo with URL: http://127.0.0.1:8879/charts 
$HELM_HOME has been configured at /root/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.

Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!

這個命令會把預設的 google 的倉庫地址替換成我同步的一個映象地址。

如果在安裝過程中遇到了一些其他問題,比如初始化的時候出現瞭如下錯誤:

E0125 14:03:19.093131   56246 portforward.go:331] an error occurred forwarding 55943 -> 44134: error forwarding port 44134 to pod d01941068c9dfea1c9e46127578994d1cf8bc34c971ff109dc6faa4c05043a6e, uid : unable to do port forwarding: socat not found.
2018/01/25 14:03:19 (0xc420476210) (0xc4203ae1e0) Stream removed, broadcasting: 3
2018/01/25 14:03:19 (0xc4203ae1e0) (3) Writing data frame
2018/01/25 14:03:19 (0xc420476210) (0xc4200c3900) Create stream
2018/01/25 14:03:19 (0xc420476210) (0xc4200c3900) Stream added, broadcasting: 5
Error: cannot connect to Tiller

解決方案:在節點上安裝socat可以解決

$ sudo yum install -y socat

Helm 服務端正常安裝完成後,Tiller預設被部署在kubernetes叢集的kube-system名稱空間下:

[root@node01 ~]# kubectl get pod -n kube-system -l app=helm
NAME                             READY     STATUS    RESTARTS   AGE
tiller-deploy-86b844d8c6-d2kw8   1/1       Running   0          27s

此時,我們檢視 Helm 版本就都正常了:

[root@node01 ~]# helm version
Client: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}

另外一個值得注意的問題是RBAC,我們的 kubernetes 叢集是1.10.0版本的,預設開啟了RBAC訪問控制,所以我們需要為Tiller建立一個ServiceAccount,讓他擁有執行的許可權,詳細內容可以檢視 Helm 文件中的Role-based Access Control。 建立rbac.yaml檔案:

[root@node01 ~]# vim rbac-config.yaml
[root@node01 ~]# cat rbac-config.yaml 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system

然後使用kubectl建立:

[root@node01 ~]# kubectl create -f rbac-config.yaml
serviceaccount "tiller" created
clusterrolebinding.rbac.authorization.k8s.io "tiller" created

建立了tiller的 ServceAccount 後還沒完,因為我們的 Tiller 之前已經就部署成功了,而且是沒有指定 ServiceAccount 的,所以我們需要給 Tiller 打上一個 ServiceAccount 的補丁:

[root@node01 ~]# kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
deployment.extensions "tiller-deploy" patched

上面這一步非常重要,不然後面在使用 Helm 的過程中可能出現Error: no available release name found的錯誤資訊。

至此, Helm客戶端和服務端都配置完成了,接下來我們看看如何使用吧。

1.5 使用

我們現在了嘗試建立一個 Chart:

[root@node01 ~]# helm create hello-helm
Creating hello-helm

[root@node01 ~]# tree hello-helm
hello-helm
├── charts
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ ├── NOTES.txt
│ └── service.yaml
└── values.yaml

2 directories, 7 files

我們通過檢視templates目錄下面的deployment.yaml檔案可以看出預設建立的 Chart 是一個 nginx 服務,具體的每個檔案是幹什麼用的,我們可以前往 Helm 官方文件進行檢視,後面會和大家詳細講解的。比如這裡我們來安裝 1.7.9 這個版本的 nginx,則我們更改 value.yaml 檔案下面的 image tag 即可,將預設的 stable 更改為 1.7.9,為了測試方便,我們把 Service 的型別也改成 NodePort

[root@node01 ~]# vim hello-helm/values.yaml 
[root@node01 ~]# cat hello-helm/values.yaml 
...
image:
  repository: nginx
  tag: 1.7.9
  pullPolicy: IfNotPresent

nameOverride: ""
fullnameOverride: ""

service:
  type: NodePort
  port: 80
...

現在我們來嘗試安裝下這個 Chart :

[root@node01 ~]# helm install ./hello-helm
NAME:   foppish-billygoat
LAST DEPLOYED: Mon Sep 20 15:11:34 2021
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1beta2/Deployment
NAME                          DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
foppish-billygoat-hello-helm  1        0        0           0          0s

==> v1/Pod(related)
NAME                                           READY  STATUS   RESTARTS  AGE
foppish-billygoat-hello-helm-79f56fc76f-9cdm8  0/1    Pending  0         0s

==> v1/Service
NAME                          TYPE      CLUSTER-IP    EXTERNAL-IP  PORT(S)       AGE
foppish-billygoat-hello-helm  NodePort  10.109.79.65  <none>       80:31941/TCP  0s


NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services foppish-billygoat-hello-helm)
  export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
[root@node01 ~]# kubectl get pods -l app=hello-helm
NAME                                            READY     STATUS    RESTARTS   AGE
foppish-billygoat-hello-helm-79f56fc76f-9cdm8   1/1       Running   0          2m

[root@node01 ~]# kubectl get svc -l app=hello-helm
NAME                           TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
foppish-billygoat-hello-helm   NodePort   10.109.79.65   <none>        80:31941/TCP   3m

等到 Pod 建立完成後,我們可以根據建立的 Service 的 NodePort 來訪問該服務了,然後在瀏覽器中開啟http://k8s.haimaxy.com:31941就可以正常的訪問我們剛剛部署的 nginx 應用了。

檢視release

[root@node01 ~]# helm list
NAME             	REVISION	UPDATED                 	STATUS  	CHART           	APP VERSION	NAMESPACE
foppish-billygoat	1       	Mon Sep 20 15:11:34 2021	DEPLOYED	hello-helm-0.1.0	1.0        	default 

打包chart

[root@node01 ~]# helm package hello-helm
Successfully packaged chart and saved it to: /root/hello-helm-0.1.0.tgz

[root@node01 ~]# ll -d /root/hello-helm-0.1.0.tgz
-rw-r--r-- 1 root root 2598 9月  20 15:27 /root/hello-helm-0.1.0.tgz

然後我們就可以將打包的tgz檔案分發到任意的伺服器上,通過helm fetch就可以獲取到該 Chart 了。

刪除release

[root@node01 ~]# helm delete foppish-billygoat
release "foppish-billygoat" deleted

然後我們看到kubernetes叢集上的該 nginx 服務也已經被刪除了。

[root@node01 ~]# kubectl get pods -l app=hello-helm
No resources found.

還有更多關於Helm的使用命令,我們可以前往官方文件檢視。

2. Helm 的基本使用

上節課我們成功安裝了Helm的客戶端以及服務端Tiller Server,我們也自己嘗試建立了我們的第一個 Helm Chart 包,這節課就來和大家一起學習下 Helm 中的一些常用的操作方法。

2.1 倉庫

Helm 的 Repo 倉庫和 Docker Registry 比較類似,Chart 庫可以用來儲存和共享打包 Chart 的位置,我們在安裝了 Helm 後,預設的倉庫地址是 google 的一個地址,這對於我們不能上網的同學就比較苦惱了,沒辦法訪問到官方提供的 Chart 倉庫,可以用helm repo list來檢視當前的倉庫配置:

[root@node01 ~]# helm repo list
NAME  	URL                                        
stable	https://cnych.github.io/kube-charts-mirror/
local 	http://127.0.0.1:8879/charts  

我們可以看到除了一個預設的 stable 的倉庫配置外,還有一個 local 的本地倉庫,這是我們本地測試的一個倉庫地址。其實要建立一個 Chart 倉庫也是非常簡單的,Chart 倉庫其實就是一個帶有index.yaml索引檔案和任意個打包的 Chart 的 HTTP 伺服器而已,比如我們想要分享一個 Chart 包的時候,將我們本地的 Chart 包上傳到該伺服器上面,別人就可以使用了,所以其實我們自己託管一個 Chart 倉庫也是非常簡單的,比如阿里雲的 OSS、Github Pages,甚至自己建立的一個簡單伺服器都可以。

為了解決上網的問題,我這裡建了一個 Github Pages 倉庫,每天會自動和官方的倉庫進行同步,地址是:https://github.com/cnych/kube-charts-mirror,這樣我們就可以將我們的 Helm 預設倉庫地址更改成我們自己的倉庫地址了:

[root@node01 ~]# helm repo remove stable
"stable" has been removed from your repositories
[root@node01 ~]# helm repo add stable https://cnych.github.io/kube-charts-mirror/
"stable" has been added to your repositories
[root@node01 ~]# helm repo list
NAME  	URL                                        
local 	http://127.0.0.1:8879/charts               
stable	https://cnych.github.io/kube-charts-mirror/

[root@node01 ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈ 

倉庫新增完成後,可以使用 update 命令進行倉庫更新。當然如果要我們自己來建立一個 web 伺服器來服務 Helm Chart 的話,只需要實現下面幾個功能點就可以提供服務了:

  • 將索引和Chart置於伺服器目錄中
  • 確保索引檔案index.yaml可以在沒有認證要求的情況下訪問
  • 確保 yaml 檔案的正確內容型別(text/yaml 或 text/x-yaml)

如果你的 web 服務提供了上面幾個功能,那麼也就可以當做 Helm Chart 倉庫來使用了。

2.2 查詢 chart

Helm 將 Charts 包安裝到 Kubernetes 叢集中,一個安裝例項就是一個新的 Release,要找到新的 Chart,我們可以通過搜尋命令完成。

記住,如果不能上網,將預設的 stable 的倉庫地址更換成上面我們建立的地址

直接執行helm search命令可以檢視有哪些 Charts 是可用的:

[root@node01 ~]# helm search
NAME                                     CHART VERSION    APP VERSION                     DESCRIPTION
...
stable/minio                             1.6.3            RELEASE.2018-08-25T01-56-38Z    Minio is a high performance distributed object storage se...
stable/mission-control                   0.4.2            3.1.2                           A Helm chart for JFrog Mission Control
stable/mongodb                           4.2.2            4.0.2                           NoSQL document-oriented database that stores JSON-like do...
stable/mongodb-replicaset                3.5.6            3.6                             NoSQL document-oriented database that stores JSON-like do...
...
stable/zetcd                             0.1.9            0.0.3                           CoreOS zetcd Helm chart for Kubernetes
...

如果沒有使用過濾條件,helm search 顯示所有可用的 charts。可以通過使用過濾條件進行搜尋來縮小搜尋的結果範圍:

[root@node01 ~]# helm search mysql
NAME                            	CHART VERSION	APP VERSION	DESCRIPTION                                                 
stable/mysql                    	0.10.2       	5.7.14     	Fast, reliable, scalable, and easy to use open-source rel...
stable/mysqldump                	1.0.0        	5.7.21     	A Helm chart to help backup MySQL databases using mysqldump 
stable/prometheus-mysql-exporter	0.2.1        	v0.11.0    	A Helm chart for prometheus mysql exporter with cloudsqlp...
stable/percona                  	0.3.3        	5.7.17     	free, fully compatible, enhanced, open source drop-in rep...
stable/percona-xtradb-cluster   	0.5.0        	5.7.19     	free, fully compatible, enhanced, open source drop-in rep...
stable/phpmyadmin               	1.3.0        	4.8.3      	phpMyAdmin is an mysql administration frontend              
stable/gcloud-sqlproxy          	0.6.0        	1.11       	Google Cloud SQL Proxy                                      
stable/mariadb                  	5.2.3        	10.1.37    	Fast, reliable, scalable, and easy to use open-source rel...

可以看到明顯少了很多 charts 了,同樣的,我們可以使用 inspect 命令來檢視一個 chart 的詳細資訊:

[root@node01 ~]# helm inspect stable/mysql|head -27
appVersion: 5.7.14
description: Fast, reliable, scalable, and easy to use open-source relational database
  system.
engine: gotpl
home: https://www.mysql.com/
icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png
keywords:
- mysql
- database
- sql
maintainers:
- email: [email protected]
  name: olemarkus
- email: [email protected]
  name: viglesiasce
name: mysql
sources:
- https://github.com/kubernetes/charts
- https://github.com/docker-library/mysql
version: 0.10.2

---
## mysql image version
## ref: https://hub.docker.com/r/library/mysql/tags/
##
image: "mysql"
imageTag: "5.7.14"
...

使用 inspect 命令可以檢視到該 chart 裡面所有描述資訊,包括執行方式、配置資訊等等。

通過 helm search 命令可以找到我們想要的 chart 包,找到後就可以通過 helm install 命令來進行安裝了。

2.3 安裝 chart

要安裝新的軟體包,直接使用 helm install 命令即可。最簡單的情況下,它只需要一個 chart 的名稱引數:

[root@node01 ~]# helm install stable/mysql
NAME:   geared-moth
LAST DEPLOYED: Mon Sep 20 16:01:21 2021
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Secret
NAME               TYPE    DATA  AGE
geared-moth-mysql  Opaque  2     1s

==> v1/ConfigMap
NAME                    DATA  AGE
geared-moth-mysql-test  1     1s

==> v1/PersistentVolumeClaim
NAME               STATUS   VOLUME  CAPACITY  ACCESS MODES  STORAGECLASS  AGE
geared-moth-mysql  Pending  1s

==> v1/Service
NAME               TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)   AGE
geared-moth-mysql  ClusterIP  10.105.138.133  <none>       3306/TCP  1s

==> v1beta1/Deployment
NAME               DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
geared-moth-mysql  1        1        1           0          1s

==> v1/Pod(related)
NAME                                READY  STATUS   RESTARTS  AGE
geared-moth-mysql-58778f7d84-jrx57  0/1    Pending  0         0s


NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
geared-moth-mysql.default.svc.cluster.local

To get your root password run:

    MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default geared-moth-mysql -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 geared-moth-mysql -p

To connect to your database directly from outside the K8s cluster:
    MYSQL_HOST=127.0.0.1
    MYSQL_PORT=3306

    # Execute the following command to route the connection:
    kubectl port-forward svc/geared-moth-mysql 3306

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

現在 mysql chart 已經安裝上了,安裝 chart 會建立一個新 release 物件。上面的 release 被命名為 hmewing-squid。如果你想使用你自己的 release 名稱,只需使用--name引數指定即可,比如:

$ helm install stable/mysql --name mydb

在安裝過程中,helm 客戶端將列印有關建立哪些資源的有用資訊,release 的狀態以及其他有用的配置資訊,比如這裡的有訪問 mysql 服務的方法、獲取 root 使用者的密碼以及連線 mysql 的方法等資訊。

值得注意的是 Helm 並不會一直等到所有資源都執行才退出。因為很多 charts 需要的映象資源非常大,所以可能需要很長時間才能安裝到叢集中去。

要跟蹤 release 狀態或重新讀取配置資訊,可以使用 helm status 檢視:

[root@node01 ~]# helm status geared-moth|head
LAST DEPLOYED: Mon Sep 20 16:01:21 2021
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/PersistentVolumeClaim
NAME               STATUS   VOLUME  CAPACITY  ACCESS MODES  STORAGECLASS  AGE
geared-moth-mysql  Pending  4m

==> v1/Service
...

可以看到當前 release 的狀態是DEPLOYED,下面還有一些安裝的時候出現的資訊。

[root@node01 ~]# helm ls
NAME       	REVISION	UPDATED                 	STATUS  	CHART       	APP VERSION	NAMESPACE
geared-moth	1       	Mon Sep 20 16:01:21 2021	DEPLOYED	mysql-0.10.2	5.7.14  

2.4 自定義 chart

上面的安裝方式是使用 chart 的預設配置選項。但是在很多時候,我們都需要自定義 chart 以滿足自身的需求,要自定義 chart,我們就需要知道我們使用的 chart 支援的可配置選項才行。

要檢視 chart 上可配置的選項,使用helm inspect values命令即可,比如我們這裡檢視上面的 mysql 的配置選項:

[root@node01 ~]# helm inspect values stable/mysql
## mysql image version
## ref: https://hub.docker.com/r/library/mysql/tags/
##
image: "mysql"
imageTag: "5.7.14"

## Specify password for root user
##
## Default: random 10 character string
# mysqlRootPassword: testing

## Create a database user
##
# mysqlUser:
## Default: random 10 character string
# mysqlPassword:

## Allow unauthenticated access, uncomment to enable
##
# mysqlAllowEmptyPassword: true

## Create a database
##
# mysqlDatabase:

## Specify an imagePullPolicy (Required)
## It's recommended to change this to 'Always' if the image tag is 'latest'
## ref: http://kubernetes.io/docs/user-guide/images/#updating-images
##
imagePullPolicy: IfNotPresent

extraVolumes: |
  # - name: extras
  #   emptyDir: {}

extraVolumeMounts: |
  # - name: extras
  #   mountPath: /usr/share/extras
  #   readOnly: true

extraInitContainers: |
  # - name: do-something
  #   image: busybox
  #   command: ['do', 'something']

# Optionally specify an array of imagePullSecrets.
# Secrets must be manually created in the namespace.
# ref: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod
# imagePullSecrets:
  # - name: myRegistryKeySecretName

## Node selector
## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector: {}

livenessProbe:
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  successThreshold: 1
  failureThreshold: 3

readinessProbe:
  initialDelaySeconds: 5
  periodSeconds: 10
  timeoutSeconds: 1
  successThreshold: 1
  failureThreshold: 3

## Persist data to a persistent volume
persistence:
  enabled: true
  ## database data Persistent Volume Storage Class
  ## If defined, storageClassName: <storageClass>
  ## If set to "-", storageClassName: "", which disables dynamic provisioning
  ## If undefined (the default) or set to null, no storageClassName spec is
  ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
  ##   GKE, AWS & OpenStack)
  ##
  # storageClass: "-"
  accessMode: ReadWriteOnce
  size: 8Gi
  annotations: {}

## Configure resource requests and limits
## ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources:
  requests:
    memory: 256Mi
    cpu: 100m

# Custom mysql configuration files used to override default mysql settings
configurationFiles: {}
#  mysql.cnf: |-
#    [mysqld]
#    skip-name-resolve
#    ssl-ca=/ssl/ca.pem
#    ssl-cert=/ssl/server-cert.pem
#    ssl-key=/ssl/server-key.pem

# Custom mysql init SQL files used to initialize the database
initializationFiles: {}
#  first-db.sql: |-
#    CREATE DATABASE IF NOT EXISTS first DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
#  second-db.sql: |-
#    CREATE DATABASE IF NOT EXISTS second DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;

metrics:
  enabled: false
  image: prom/mysqld-exporter
  imageTag: v0.10.0
  imagePullPolicy: IfNotPresent
  resources: {}
  annotations: {}
    # prometheus.io/scrape: "true"
    # prometheus.io/port: "9104"
  livenessProbe:
    initialDelaySeconds: 15
    timeoutSeconds: 5
  readinessProbe:
    initialDelaySeconds: 5
    timeoutSeconds: 1

## Configure the service
## ref: http://kubernetes.io/docs/user-guide/services/
service:
  ## Specify a service type
  ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
  type: ClusterIP
  port: 3306
  # nodePort: 32000

ssl:
  enabled: false
  secret: mysql-ssl-certs
  certificates:
#  - name: mysql-ssl-certs
#    ca: |-
#      -----BEGIN CERTIFICATE-----
#      ...
#      -----END CERTIFICATE-----
#    cert: |-
#      -----BEGIN CERTIFICATE-----
#      ...
#      -----END CERTIFICATE-----
#    key: |-
#      -----BEGIN RSA PRIVATE KEY-----
#      ...
#      -----END RSA PRIVATE KEY-----

## Populates the 'TZ' system timezone environment variable
## ref: https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html
##
## Default: nil (mysql will use image's default timezone, normally UTC)
## Example: 'Australia/Sydney'
# timezone:

# To be added to the database server pod(s)
podAnnotations: {}

然後,我們可以直接在 YAML 格式的檔案中來覆蓋上面的任何配置,在安裝的時候直接使用該配置檔案即可:(config.yaml)

[root@node01 ~]# vim config.yaml
[root@node01 ~]# cat config.yaml 
mysqlUser: haimaxyUser
mysqlDatabase: haimaxyDB
service:
  type: NodePort

我們這裡通過 config.yaml 檔案定義了 mysqlUser 和 mysqlDatabase,並且把 service 的型別更改為了 NodePort,然後現在我們來安裝的時候直接指定該 yaml 檔案:

[root@node01 ~]# helm install -f config.yaml stable/mysql --name mydb
NAME:   mydb
LAST DEPLOYED: Mon Sep 20 16:19:48 2021
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Pod(related)
NAME                        READY  STATUS   RESTARTS  AGE
mydb-mysql-dfc999888-rpp4m  0/1    Pending  0         0s

==> v1/Secret
NAME        TYPE    DATA  AGE
mydb-mysql  Opaque  2     0s

==> v1/ConfigMap
NAME             DATA  AGE
mydb-mysql-test  1     0s

==> v1/PersistentVolumeClaim
NAME        STATUS   VOLUME  CAPACITY  ACCESS MODES  STORAGECLASS  AGE
mydb-mysql  Pending  0s

==> v1/Service
NAME        TYPE      CLUSTER-IP     EXTERNAL-IP  PORT(S)         AGE
mydb-mysql  NodePort  10.97.123.216  <none>       3306:31001/TCP  0s

==> v1beta1/Deployment
NAME        DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
mydb-mysql  1        1        1           0          0s


NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mydb-mysql.default.svc.cluster.local

To get your root password run:

    MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default mydb-mysql -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 mydb-mysql -p

To connect to your database directly from outside the K8s cluster:
    MYSQL_HOST=$(kubectl get nodes --namespace default -o jsonpath='{.items[0].status.addresses[0].address}')
    MYSQL_PORT=$(kubectl get svc --namespace default mydb-mysql -o jsonpath='{.spec.ports[0].nodePort}')

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

我們可以看到當前 release 的名字已經變成 mydb 了。然後可以檢視下 mydb 關聯的 Service 是否變成 NodePort 型別的了:

[root@node01 ~]# kubectl get svc
NAME                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
geared-moth-mysql   ClusterIP   10.105.138.133   <none>        3306/TCP         19m
kubernetes          ClusterIP   10.96.0.1        <none>        443/TCP          97d
mydb-mysql          NodePort    10.97.123.216    <none>        3306:31001/TCP   1m

看到服務 mydb-mysql 變成了 NodePort 型別的,二之前預設建立的 mewing-squid-mysql 是 ClusterIP 型別的,證明上面我們通過 YAML 檔案來覆蓋 values 是成功的。

接下來我們檢視下 Pod 的狀況:

[root@node01 ~]# kubectl get pods
NAME                                 READY     STATUS    RESTARTS   AGE
geared-moth-mysql-58778f7d84-jrx57   0/1       Pending   0          20m
mydb-mysql-dfc999888-rpp4m           0/1       Pending   0          2m

比較奇怪的是之前預設建立的和現在的 mydb 的 release 建立的 Pod 都是 Pending 狀態,直接使用 describe 命令檢視下:

[root@node01 ~]# kubectl describe pod mydb-mysql-dfc999888-rpp4m
Name:           mydb-mysql-dfc999888-rpp4m
Namespace:      default
Node:           <none>
Labels:         app=mydb-mysql
                pod-template-hash=897555444
Annotations:    <none>
Status:         Pending
...
Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  16s (x15 over 3m)  default-scheduler  pod has unbound PersistentVolumeClaims

我們可以發現兩個 Pod 處於 Pending 狀態的原因都是 PVC 沒有被繫結上,所以這裡我們可以通過 storageclass 或者手動建立一個合適的 PV 物件來解決這個問題。

另外為了說明 helm 更新的用法,我們這裡來直接禁用掉資料持久化,可以在上面的 config.yaml 檔案中設定:

persistence:
  enabled: false

另外一種方法就是在安裝過程中使用--set來覆蓋對應的 value 值,比如禁用資料持久化,我們這裡可以這樣來覆蓋:

$ helm install stable/mysql --set persistence.enabled=false --name mydb

2.5 升級

我們這裡將資料持久化禁用掉來對上面的 mydb 進行升級:

[root@node01 ~]# cat config.yaml 
mysqlUser: haimaxyUser
mysqlDatabase: haimaxyDB
service:
  type: NodePort
persistence:
  enabled: false

[root@node01 ~]# helm upgrade -f config.yaml mydb stable/mysql
Release "mydb" has been upgraded. Happy Helming!
LAST DEPLOYED: Mon Sep 20 17:30:24 2021
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
...

可以看到已經變成 DEPLOYED 狀態了,現在我們再去看看 Pod 的狀態呢:

[root@node01 ~]# kubectl get pods
NAME                                 READY     STATUS    RESTARTS   AGE
geared-moth-mysql-58778f7d84-jrx57   0/1       Pending   0          1h
mydb-mysql-6ffc84bbf6-jrkgx          1/1       Running   0          1m

我們看到 mydb 關聯的 Pod 已經變成了 PodInitializing 的狀態,已經不是 Pending 狀態了,同樣的,使用 describe 命令檢視:

[root@node01 ~]# kubectl describe pod mydb-mysql-6ffc84bbf6-jrkgx
Name:           mydb-mysql-6ffc84bbf6-jrkgx
Namespace:      default
Node:           node01/192.168.200.11
Start Time:     Mon, 20 Sep 2021 17:30:24 +0800
Labels:         app=mydb-mysql
                pod-template-hash=2997406692
Annotations:    <none>
Status:         Running
IP:             10.244.1.7
Controlled By:  ReplicaSet/mydb-mysql-6ffc84bbf6
...
Events:
  Type    Reason                 Age   From               Message
  ----    ------                 ----  ----               -------
  Normal  Scheduled              2m    default-scheduler  Successfully assigned mydb-mysql-6ffc84bbf6-jrkgx to node01
  Normal  SuccessfulMountVolume  2m    kubelet, node01    MountVolume.SetUp succeeded for volume "data"
  Normal  SuccessfulMountVolume  2m    kubelet, node01    MountVolume.SetUp succeeded for volume "default-token-ztfk2"
  Normal  Pulled                 2m    kubelet, node01    Container image "busybox:1.25.0" already present on machine
  Normal  Created                2m    kubelet, node01    Created container
  Normal  Started                2m    kubelet, node01    Started container
  Normal  Pulled                 2m    kubelet, node01    Container image "mysql:5.7.14" already present on machine
  Normal  Created                2m    kubelet, node01    Created container
  Normal  Started                2m    kubelet, node01    Started container

我們可以看到現在沒有任何關於 PVC 的錯誤資訊了,這是因為我們剛剛更新的版本中就是禁用掉了的資料持久化的,證明 helm upgrade 和 --values 是生效了的。現在我們使用 helm ls 命令檢視先當前的 release:

[root@node01 ~]# helm ls
NAME       	REVISION	UPDATED                 	STATUS  	CHART       	APP VERSION	NAMESPACE
geared-moth	1       	Mon Sep 20 16:01:21 2021	DEPLOYED	mysql-0.10.2	5.7.14     	default  
mydb       	2       	Mon Sep 20 17:30:24 2021	DEPLOYED	mysql-0.10.2	5.7.14     	default 

可以看到 mydb 這個 release 的REVISION已經變成2了,這是因為 release 的版本是遞增的,每次安裝、升級或者回滾,版本號都會加1,第一個版本號始終為1,同樣我們可以使用 helm history 命令檢視 release 的歷史版本:

[root@node01 ~]# helm history mydb
REVISION	UPDATED                 	STATUS    	CHART       	DESCRIPTION     
1       	Mon Sep 20 16:19:48 2021	SUPERSEDED	mysql-0.10.2	Install complete
2       	Mon Sep 20 17:30:24 2021	DEPLOYED  	mysql-0.10.2	Upgrade complete

當然如果我們要回滾到某一個版本的話,使用 helm rollback 命令即可,比如我們將 mydb 回滾到上一個版本:

[root@node01 ~]# #helm rollback mydb 1

2.6 刪除

上節課我們就學習了要刪除一個 release 直接使用 helm delete 命令就 OK:

[root@node01 ~]# helm delete geared-moth
release "geared-moth" deleted

這將從叢集中刪除該 release,但是這並不代表就完全刪除了,我們還可以通過--deleted引數來顯示被刪除掉 release:

[root@node01 ~]# helm list --deleted
NAME       	REVISION	UPDATED                 	STATUS 	CHART       	APP VERSION	NAMESPACE
geared-moth	1       	Mon Sep 20 16:01:21 2021	DELETED	mysql-0.10.2	5.7.14     	default  

[root@node01 ~]# helm list --all
NAME       	REVISION	UPDATED                 	STATUS  	CHART       	APP VERSION	NAMESPACE
geared-moth	1       	Mon Sep 20 16:01:21 2021	DELETED 	mysql-0.10.2	5.7.14     	default  
mydb       	2       	Mon Sep 20 17:30:24 2021	DEPLOYED	mysql-0.10.2	5.7.14     	default

helm list --all則會顯示所有的 release,包括已經被刪除的

由於 Helm 保留已刪除 release 的記錄,因此不能重新使用 release 名稱。(如果 確實 需要重新使用此 release 名稱,則可以使用此 --replace 引數,但它只會重用現有 release 並替換其資源。)這點是不是和 docker container 的管理比較類似

請注意,因為 release 以這種方式儲存,所以可以回滾已刪除的資源並重新啟用它。

如果要徹底刪除 release,則需要加上--purge引數:

[root@node01 ~]# helm delete geared-moth --purge
release "geared-moth" deleted
[root@node01 ~]# helm list --deleted
[root@node01 ~]#
[root@node01 ~]# helm list --all
NAME	REVISION	UPDATED                 	STATUS  	CHART       	APP VERSION	NAMESPACE
mydb	2       	Mon Sep 20 17:30:24 2021	DEPLOYED	mysql-0.10.2	5.7.14     	default 

3. Helm 模板之內建函式和Values

上節課和大家一起學習了Helm的一些常用操作方法,這節課來和大家一起定義一個chart包,瞭解 Helm 中模板的一些使用方法。

3.1 定義 chart

Helm 的 github 上面有一個比較完整的文件,建議大家好好閱讀下該文件,這裡我們來一起建立一個chart包。

一個 chart 包就是一個資料夾的集合,資料夾名稱就是 chart 包的名稱,比如建立一個 mychart 的 chart 包:

[root@node01 ~]# helm create mychart
Creating mychart
[root@node01 ~]# tree mychart
mychart
├── charts
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ ├── NOTES.txt
│ └── service.yaml
└── values.yaml

2 directories, 7 files

chart 包的目錄上節課我們就已經學習過了,這裡我們再來仔細看看 templates 目錄下面的檔案:

  • NOTES.txt:chart 的 “幫助文字”。這會在使用者執行 helm install 時顯示給使用者。
  • deployment.yaml:建立 Kubernetes deployment 的基本 manifest
  • service.yaml:為 deployment 建立 service 的基本 manifest
  • ingress.yaml: 建立 ingress 物件的資源清單檔案
  • _helpers.tpl:放置模板助手的地方,可以在整個 chart 中重複使用

這裡我們明白每一個檔案是幹嘛的就行,然後我們把 templates 目錄下面所有檔案全部刪除掉,這裡我們自己來建立模板檔案:

[root@node01 ~]# rm -rf mychart/templates/*.*

3.2 建立模板

這裡我們來建立一個非常簡單的模板 ConfigMap,在 templates 目錄下面新建一個configmap.yaml檔案:

[root@node01 ~]# vim mychart/templates/configmap.yaml
[root@node01 ~]# cat mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-configmap
data:
  myvalue: "Hello World"

實際上現在我們就有一個可安裝的 chart 包了,通過helm install命令來進行安裝:

[root@node01 ~]# helm install ./mychart/
NAME:   doltish-scorpion
LAST DEPLOYED: Mon Sep 20 23:18:29 2021
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME               DATA  AGE
mychart-configmap  1     0s

在上面的輸出中,我們可以看到我們的 ConfigMap 資源物件已經建立了。然後使用如下命令我們可以看到實際的模板被渲染過後的資原始檔:

[root@node01 ~]# helm get manifest doltish-scorpion

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-configmap
data:
  myvalue: "Hello World"

現在我們看到上面的 ConfigMap 檔案是不是正是我們前面在模板檔案中設計的,現在我們刪除當前的release:

[root@node01 ~]# helm delete doltish-scorpion
release "doltish-scorpion" deleted

3.3 新增一個簡單的模板

我們可以看到上面我們定義的 ConfigMap 的名字是固定的,但往往這並不是一種很好的做法,我們可以通過插入 release 的名稱來生成資源的名稱,比如這裡 ConfigMap 的名稱我們希望是:ringed-lynx-configmap,這就需要用到 Chart 的模板定義方法了。

Helm Chart 模板使用的是Go語言模板編寫而成,並添加了Sprig庫中的50多個附件模板函式以及一些其他特殊的函

需要注意的是kubernetes資源物件的 labels 和 name 定義被限制 63個字元,所以需要注意名稱的定義。

現在我們來重新定義下上面的 configmap.yaml 檔案:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"

我們將名稱替換成了{{ .Release.Name }}-configmap,其中包含在{{}}之中的就是模板指令,{{ .Release.Name }} 將 release 的名稱注入到模板中來,這樣最終生成的 ConfigMap 名稱就是以 release 的名稱開頭的了。這裡的 Release 模板物件屬於 Helm 內建的一種物件,還有其他很多內建的物件,稍後我們將接觸到。

現在我們來重新安裝我們的 Chart 包,注意觀察 ConfigMap 資源物件的名稱:

[root@node01 ~]# helm install ./mychart
NAME:   veering-toad
LAST DEPLOYED: Mon Sep 20 23:25:48 2021
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME                    DATA  AGE
veering-toad-configmap  1     0s

可以看到現在生成的名稱變成了quoting-zebra-configmap,證明已經生效了,當然我們也可以使用命令helm get manifest quoting-zebra檢視最終生成的清單檔案的樣子。

3.4 除錯

我們用模板來生成資原始檔的清單,但是如果我們想要除錯就非常不方便了,不可能我們每次都去部署一個release例項來校驗模板是否正確,所幸的時 Helm 為我們提供了--dry-run --debug這個可選引數,在執行helm install的時候帶上這兩個引數就可以把對應的 values 值和生成的最終的資源清單檔案打印出來,而不會真正的去部署一個release例項,比如我們來除錯上面建立的 chart 包:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '46499'

[debug] SERVER: "127.0.0.1:46499"

[debug] Original chart version: ""
[debug] CHART PATH: /root/mychart

NAME:   dunking-manta
REVISION: 1
RELEASED: Mon Sep 20 23:48:30 2021
CHART: mychart-0.1.0
USER-SUPPLIED VALUES:
{}
...
service:
  port: 80
  type: ClusterIP
tolerations: []

HOOKS:
MANIFEST:

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dunking-manta-configmap
data:
  myvalue: "Hello World"

現在我們使用--dry-run就可以很容易地測試程式碼了,不需要每次都去安裝一個 release 例項了,但是要注意的是這不能確保 Kubernetes 本身就一定會接受生成的模板,在除錯完成後,還是需要去安裝一個實際的 release 例項來進行驗證的。

3.5 內建物件

剛剛我們使用{{.Release.Name}}將 release 的名稱插入到模板中。這裡的 Release 就是 Helm 的內建物件,下面是一些常用的內建物件,在需要的時候直接使用就可以:

  • Release:這個物件描述了 release 本身。它裡面有幾個物件:
    • Release.Name:release 名稱
    • Release.Time:release 的時間
    • Release.Namespace:release 的 namespace(如果清單未覆蓋)
    • Release.Service:release 服務的名稱(始終是 Tiller)。
    • Release.Revision:此 release 的修訂版本號,從1開始累加。
    • Release.IsUpgrade:如果當前操作是升級或回滾,則將其設定為 true。
    • Release.IsInstall:如果當前操作是安裝,則設定為 true。
  • Values:從values.yaml檔案和使用者提供的檔案傳入模板的值。預設情況下,Values 是空的。
  • Chart:Chart.yaml檔案的內容。所有的 Chart 物件都將從該檔案中獲取。chart 指南中Charts Guide列出了可用欄位,可以前往檢視。
  • Files:這提供對 chart 中所有非特殊檔案的訪問。雖然無法使用它來訪問模板,但可以使用它來訪問 chart 中的其他檔案。請參閱 "訪問檔案" 部分。
    • Files.Get 是一個按名稱獲取檔案的函式(.Files.Get config.ini)
    • Files.GetBytes 是將檔案內容作為位元組陣列而不是字串獲取的函式。這對於像圖片這樣的東西很有用。
  • Capabilities:這提供了關於 Kubernetes 叢集支援的功能的資訊。
    • Capabilities.APIVersions 是一組版本資訊。
    • Capabilities.APIVersions.Has $version 指示是否在群集上啟用版本(batch/v1)。
    • Capabilities.KubeVersion 提供了查詢 Kubernetes 版本的方法。它具有以下值:Major,Minor,GitVersion,GitCommit,GitTreeState,BuildDate,GoVersion,Compiler,和 Platform。
    • Capabilities.TillerVersion 提供了查詢 Tiller 版本的方法。它具有以下值:SemVer,GitCommit,和 GitTreeState。
  • Template:包含有關正在執行的當前模板的資訊
  • Name:到當前模板的檔案路徑(例如 mychart/templates/mytemplate.yaml)
  • BasePath:當前 chart 模板目錄的路徑(例如 mychart/templates)。

上面這些值可用於任何頂級模板,要注意內建值始終以大寫字母開頭。這也符合Go的命名約定。當你建立自己的名字時,你可以自由地使用適合你的團隊的慣例。

3.6 values 檔案

上面的內建物件中有一個物件就是 Values,該物件提供對傳入 chart 的值的訪問,Values 物件的值有4個來源:

  • chart 包中的 values.yaml 檔案
  • 父 chart 包的 values.yaml 檔案
  • 通過 helm install 或者 helm upgrade 的-f或者--values引數傳入的自定義的 yaml 檔案(上節課我們已經學習過)
  • 通過--set引數傳入的值

chart 的 values.yaml 提供的值可以被使用者提供的 values 檔案覆蓋,而該檔案同樣可以被--set提供的引數所覆蓋。

這裡我們來重新編輯 mychart/values.yaml 檔案,將預設的值全部清空,新增一個新的資料:(values.yaml)

[root@node01 ~]# vim mychart/values.yaml 
[root@node01 ~]# cat mychart/values.yaml 
course: k8s

然後我們在上面的 templates/configmap.yaml 模板檔案中就可以使用這個值了:(configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  course: {{ .Values.course }}

可以看到最後一行我們是通過{{ .Values.course }}來獲取 course 的值的。現在我們用 debug 模式來檢視下我們的模板會被如何渲染:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '37197'

[debug] SERVER: "127.0.0.1:37197"

[debug] Original chart version: ""
[debug] CHART PATH: /root/mychart

NAME:   garish-waterbuffalo
REVISION: 1
RELEASED: Mon Sep 20 23:52:57 2021
CHART: mychart-0.1.0
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
course: k8s

HOOKS:
MANIFEST:

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: garish-waterbuffalo-configmap
data:
  myvalue: "Hello World"
  course: k8s

我們可以看到 ConfigMap 中 course 的值被渲染成了 k8s,這是因為在預設的 values.yaml 檔案中該引數值為 k8s,同樣的我們可以通過--set引數來輕鬆的覆蓋 course 的值:

[root@node01 ~]# helm install --dry-run --debug --set course=python ./mychart
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: flippant-hound-configmap
data:
  myvalue: "Hello World"
  course: python

由於--set 比預設 values.yaml 檔案具有更高的優先順序,所以我們的模板生成為 course: python。

values 檔案也可以包含更多結構化內容,例如,我們在 values.yaml 檔案中可以建立 course 部分,然後在其中新增幾個鍵:

[root@node01 ~]# vim mychart/values.yaml 
[root@node01 ~]# cat mychart/values.yaml 
course:
  k8s: devops
  python: django

現在我們稍微修改模板:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  k8s: {{ .Values.course.k8s }}
  python: {{ .Values.course.python }}

同樣可以使用 debug 模式檢視渲染結果:

[root@node01 ~]# helm install --dry-run --debug ./mychart
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: truculent-gorilla-configmap
data:
  myvalue: "Hello World"
  k8s: devops
  python: django

可以看到模板中的引數已經被 values.yaml 檔案中的值給替換掉了。雖然以這種方式構建資料是可以的,但我們還是建議保持 value 樹淺一些,平一些,這樣維護起來要簡單一點。

到這裡,我們已經看到了幾個內建物件的使用方法,並用它們將資訊注入到了模板之中。

4. Helm 模板之模板函式與管道

上節課我們學習瞭如何將資訊渲染到模板之中,但是這些資訊都是直接傳入模板引擎中進行渲染的,有的時候我們想要轉換一下這些資料才進行渲染,這就需要使用到 Go 模板語言中的一些其他用法。

4.1 模板函式

比如我們需要從.Values中讀取的值變成字串的時候就可以通過呼叫quote模板函式來實現:(templates/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  k8s: {{ quote .Values.course.k8s }}
  python: {{ .Values.course.python }}

模板函式遵循呼叫的語法為:functionName arg1 arg2...。在上面的模板檔案中,quote .Values.course.k8s呼叫quote函式並將後面的值作為一個引數傳遞給它。最終被渲染為:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '41661'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: vested-iguana-configmap
data:
  myvalue: "Hello World"
  k8s: "devops"
  python: django

我們可以看到.Values.course.k8s被渲染成了字串devops。上節課我們也提到過 Helm 是一種 Go 模板語言,擁有超過60多種可用的內建函式,一部分是由Go 模板語言本身定義的,其他大部分都是Sprig模板庫提供的一部分,我們可以前往這兩個文件中檢視這些函式的用法。

比如我們這裡使用的quote函式就是Sprig 模板庫提供的一種字串函式,用途就是用雙引號將字串括起來,如果需要雙引號",則需要新增\來進行轉義,而squote函式的用途則是用雙引號將字串括起來,而不會對內容進行轉義。

所以在我們遇到一些需求的時候,首先要想到的是去檢視下上面的兩個模板文件中是否提供了對應的模板函式,這些模板函式可以很好的解決我們的需求。

4.2 管道

模板語言除了提供了豐富的內建函式之外,其另一個強大的功能就是管道的概念。和UNIX中一樣,管道我們通常稱為Pipeline,是一個鏈在一起的一系列模板命令的工具,以緊湊地表達一系列轉換。簡單來說,管道是可以按順序完成一系列事情的一種方法。比如我們用管道來重寫上面的 ConfigMap 模板:(templates/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  k8s: {{ .Values.course.k8s | quote }}
  python: {{ .Values.course.python }}

這裡我們直接呼叫quote函式,而是調換了一個順序,使用一個管道符|將前面的引數傳送給後面的模板函式{{ .Values.course.k8s | quote }},使用管道我們可以將幾個功能順序的連線在一起,比如我們希望上面的 ConfigMap 模板中的 k8s 的 value 值被渲染後是大寫的字串,則我們就可以使用管道來修改:(templates/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  k8s: {{ .Values.course.k8s | upper | quote }}
  python: {{ .Values.course.python }}

這裡我們在管道中增加了一個upper函式,該函式同樣是Sprig 模板庫提供的,表示將字串每一個字母都變成大寫。然後我們用debug模式來檢視下上面的模板最終會被渲染成什麼樣子:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '44004'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: jaundiced-giraffe-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: django

我們可以看到之前我們的devops已經被渲染成了"DEVOPS"了,要注意的是使用管道操作的時候,前面的操作結果會作為引數傳遞給後面的模板函式,比如我們這裡希望將上面模板中的 python 的值渲染為重複出現3次的字串,則我們就可以使用到Sprig 模板庫提供的repeat函式,不過該函式需要傳入一個引數repeat COUNT STRING表示重複的次數:(templates/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  k8s: {{ .Values.course.k8s | upper | quote }}
  python: {{ .Values.course.python | quote | repeat 3 }}

repeat函式會將給定的字串重複3次返回,所以我們將得到這個輸出:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '35730'

[debug] SERVER: "127.0.0.1:35730"

[debug] Original chart version: ""
[debug] CHART PATH: /root/mychart

Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 7: did not find expected key

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dining-goose-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "django""django""django"

我們可以看到上面的輸出中 python 對應的值變成了3個相同的字串,這顯然是不符合我們預期的,我們的預期是形成一個字串,而現在是3個字串了,而且上面還有錯誤資訊,根據管道處理的順序,我們將quote函式放到repeat函式後面去是不是就可以解決這個問題了:(templates/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  k8s: {{ .Values.course.k8s | upper | quote }}
  python: {{ .Values.course.python | repeat 3 | quote }}

現在是不是就是先重複3次.Values.course.python的值,然後呼叫quote函式:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '34845'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: precise-lionfish-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"

現在是不是就正常了,也得到了我們的預期結果,所以我們在使用管道操作的時候一定要注意是按照從前到後一步一步順序處理的。

4.3 default 函式

另外一個我們會經常使用的一個函式是default 函式default DEFAULT_VALUE GIVEN_VALUE。該函式允許我們在模板內部指定預設值,以防止該值被忽略掉了。比如我們來修改上面的 ConfigMap 的模板:(templates/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: {{ .Values.hello | default  "Hello World" | quote }}
  k8s: {{ .Values.course.k8s | upper | quote }}
  python: {{ .Values.course.python | repeat 5 | quote }}

由於我們的values.yaml檔案中只定義了 course 結構的資訊,並沒有定義 hello 的值,所以如果沒有設定預設值的話是得不到{{ .Values.hello }}的值的,這裡我們為該值定義了一個預設值:Hello World,所以現在如果在values.yaml檔案中沒有定義這個值,則我們也可以得到預設值:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '36302'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: exiled-ferret-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjangodjangodjango"

我們可以看到myvalue值被渲染成了Hello World,證明我們的預設值生效了。

5. Helm 模板之控制流程

模板函式和管道是通過轉換資訊並將其插入到YAML文件中的強大方法。但有時候需要新增一些比插入字串更復雜一些的模板邏輯。這就需要使用到模板語言中提供的控制結構了。

控制流程為我們提供了控制模板生成流程的一種能力,Helm 的模板語言提供了以下幾種流程控制:

  • if/else 條件塊
  • with 指定範圍
  • range 迴圈塊

除此之外,它還提供了一些宣告和使用命名模板段的操作:

  • define在模板中宣告一個新的命名模板
  • template匯入一個命名模板
  • block聲明瞭一種特殊的可填寫的模板區域

關於命名模板的相關知識點,我們會在後面的課程中和大家接觸到,這裡我們暫時和大家介紹if/elsewithrange這3中控制流程的用法。

5.1 if/else 條件

if/else塊是用於在模板中有條件地包含文字塊的方法,條件塊的基本結構如下:

{{ if PIPELINE }}
  # Do something
{{ else if OTHER PIPELINE }}
  # Do something else
{{ else }}
  # Default case
{{ end }}

當然要使用條件塊就得判斷條件是否為真,如果值為下面的幾種情況,則管道的結果為 false:

  • 一個布林型別的
  • 一個數字
  • 一個的字串
  • 一個nil(空或null
  • 一個空的集合(mapslicetupledictarray

除了上面的這些情況外,其他所有條件都為

同樣還是以上面的 ConfigMap 模板檔案為例,新增一個簡單的條件判斷,如果 python 被設定為 django,則新增一個web: true:(tempaltes/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: {{ .Values.hello | default  "Hello World" | quote }}
  k8s: {{ .Values.course.k8s | upper | quote }}
  python: {{ .Values.course.python | repeat 3 | quote }}
  {{ if eq .Values.course.python "django" }}web: true{{ end }}

在上面的模板檔案中我們增加了一個條件語句判斷{{ if eq .Values.course.python "django" }}web: true{{ end }},其中運算子eq是判斷是否相等的操作,除此之外,還有neltgtandor等運算子都是 Helm 模板已經實現了的,直接使用即可。這裡我們{{ .Values.course.python }}的值在values.yaml檔案中預設被設定為了django,所以正常來說下面的條件語句判斷為,所以模板檔案最終被渲染後會有web: true這樣的的一個條目:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '36977'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dandy-crab-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  web: true

可以看到上面模板被渲染後出現了web: true的條目,如果我們在安裝的時候覆蓋下 python 的值呢,比如我們改成 ai:

[root@node01 ~]# helm install --dry-run --debug --set course.python=ai ./mychart
[debug] Created tunnel using local port: '45502'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: banking-fish-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "aiaiai"

根據我們模板檔案中的定義,如果{{ .Values.course.python }}的值為django的話就會新增web: true這樣的一個條目,但是現在我們是不是通過引數--set將值設定為了 ai,所以這裡條件判斷為,正常來說就不應該出現這個條目了,上面我們通過 debug 模式檢視最終被渲染的值也沒有出現這個條目,證明條件判斷是正確的。

5.2 空格控制

上面我們的條件判斷語句是在一整行中的,如果平時經常寫程式碼的同學可能非常不習慣了,我們一般會將其格式化為更容易閱讀的形式,比如:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: {{ .Values.hello | default  "Hello World" | quote }}
  k8s: {{ .Values.course.k8s | upper | quote }}
  python: {{ .Values.course.python | repeat 3 | quote }}
  {{ if eq .Values.course.python "django" }}
  web: true
  {{ end }}

這樣的話看上去比之前要清晰很多了,但是我們通過模板引擎來渲染一下,會得到如下結果:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '43335'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: pioneering-zebra-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  
  web: true

我們可以看到渲染出來會有多餘的空行,這是因為當模板引擎執行時,它將一些值渲染過後,之前的指令被刪除,但它之前所佔的位置完全按原樣保留剩餘的空白了,所以就出現了多餘的空行。YAML檔案中的空格是非常嚴格的,所以對於空格的管理非常重要,一不小心就會導致你的YAML檔案格式錯誤。

我們可以通過使用在模板標識{{後面新增破折號和空格{{-來表示將空白左移,而在}}前面新增一個空格和破折號-}}表示應該刪除右邊的空格,另外需要注意的是換行符也是空格

使用這個語法,我們來修改我們上面的模板檔案去掉多餘的空格:(templates/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: {{ .Values.hello | default  "Hello World" | quote }}
  k8s: {{ .Values.course.k8s | upper | quote }}
  python: {{ .Values.course.python | repeat 3 | quote }}
  {{- if eq .Values.course.python "django" }}
  web: true
  {{- end }}

現在我們來檢視上面模板渲染過後的樣子:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '38602'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: quieting-deer-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  web: true

現在是不是沒有多餘的空格了,另外我們需要謹慎使用-}},比如上面模板檔案中:

python: {{ .Values.course.python | repeat 3 | quote }}
{{- if eq .Values.course.python "django" -}}
web: true
{{- end }}

如果我們在if條件後面增加-}},這會渲染成:

python: "djangodjangodjango"web: true

因為-}}它刪除了雙方的換行符,顯然這是不正確的。

有關模板中空格控制的詳細資訊,請參閱官方 Go 模板文件Official Go template documentation

5.3 使用 with 修改範圍

接下來我們來看下with關鍵詞的使用,它用來控制變數作用域。還記得之前我們的{{ .Release.xxx }}或者{{ .Values.xxx }}嗎?其中的.就是表示對當前範圍的引用,.Values就是告訴模板在當前範圍中查詢Values物件的值。而with語句就可以來控制變數的作用域範圍,其語法和一個簡單的if語句比較類似:

{{ with PIPELINE }}
  #  restricted scope
{{ end }}

with語句可以允許將當前範圍.設定為特定的物件,比如我們前面一直使用的.Values.course,我們可以使用with來將.範圍指向.Values.course:(templates/configmap.yaml)

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: {{ .Values.hello | default  "Hello World" | quote }}
  {{- with .Values.course }}
  k8s: {{ .k8s | upper | quote }}
  python: {{ .python | repeat 3 | quote }}
  {{- if eq .python "django" }}
  web: true
  {{- end }}
  {{- end }}

可以看到上面我們增加了一個{{- with .Values.course }}xxx{{- end }}的一個塊,這樣的話我們就可以在當前的塊裡面直接引用.python.k8s了,而不需要進行限定了,這是因為該with宣告將.指向了.Values.course,在{{- end }}後.就會復原其之前的作用範圍了,我們可以使用模板引擎來渲染上面的模板檢視是否符合預期結果。

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '39377'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: intentional-goose-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  web: true

不過需要注意的是在with宣告的範圍內,此時將無法從父範圍訪問到其他物件了,比如下面的模板渲染的時候將會報錯,因為顯然.Release根本就不在當前的.範圍內,當然如果我們最後兩行交換下位置就正常了,因為{{- end }}之後範圍就被重置了:

{{- with .Values.course }}
k8s: {{ .k8s | upper | quote }}
python: {{ .python | repeat 3 | quote }}
release: {{ .Release.Name }}
{{- end }}

5.4 range 迴圈

如果大家對程式語言熟悉的話,幾乎所有的程式語言都支援類似於forforeach或者類似功能的迴圈機制,在 Helm 模板語言中,是使用range關鍵字來進行迴圈操作。

我們在values.yaml檔案中新增上一個課程列表:

[root@node01 ~]# vim mychart/values.yaml 
[root@node01 ~]# cat mychart/values.yaml 
course:
  k8s: devops
  python: django
courselist:
- k8s
- python
- search
- golang

現在我們有一個課程列表,修改 ConfigMap 模板檔案來迴圈打印出該列表:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: {{ .Values.hello | default  "Hello World" | quote }}
  {{- with .Values.course }}
  k8s: {{ .k8s | upper | quote }}
  python: {{ .python | repeat 3 | quote }}
  {{- if eq .python "django" }}
  web: true
  {{- end }}
  {{- end }}
  courselist:
  {{- range .Values.courselist }}
  - {{ . | title | quote }}
  {{- end }}

可以看到最下面我們使用了一個range函式,該函式將會遍歷{{ .Values.courselist }}列表,迴圈內部我們使用的是一個.,這是因為當前的作用域就在當前迴圈內,這個.從列表的第一個元素一直遍歷到最後一個元素,然後在遍歷過程中使用了titlequote這兩個函式,前面這個函式是將字串首字母變成大寫,後面就是加上雙引號變成字串,所以按照上面這個模板被渲染過後的結果為:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '39867'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: banking-cow-configmap
data:
  myvalue: "Hello World"
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  web: true
  courselist:
  - "K8s"
  - "Python"
  - "Search"
  - "Golang"
  - "Golang"

我們可以看到courselist按照我們的要求迴圈出來了。除了 list 或者 tuple,range 還可以用於遍歷具有鍵和值的集合(如map 或 dict),這個就需要用到變數的概念了。

5.5 變數

前面我們已經學習了函式、管理以及控制流程的使用方法,我們知道程式語言中還有一個很重要的概念叫:變數,在 Helm 模板中,使用變數的場合不是特別多,但是在合適的時候使用變數可以很好的解決我們的問題。如下面的模板:

{{- with .Values.course }}
k8s: {{ .k8s | upper | quote }}
python: {{ .python | repeat 3 | quote }}
release: {{ .Release.Name }}
{{- end }}

我們在with語句塊內添加了一個.Release.Name物件,但這個模板是錯誤的,編譯的時候會失敗,這是因為.Release.Name不在該with語句塊限制的作用範圍之內,我們可以將該物件賦值給一個變數可以來解決這個問題:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  {{- $releaseName := .Release.Name -}}
  {{- with .Values.course }}
  k8s: {{ .k8s | upper | quote }}
  python: {{ .python | repeat 3 | quote }}
  release: {{ $releaseName }}
  {{- end }}

我們可以看到我們在with語句上面增加了一句{{- $releaseName := .Release.Name -}},其中$releaseName就是後面的物件的一個引用變數,它的形式就是$name,賦值操作使用:=,這樣with語句塊內部的$releaseName變數仍然指向的是.Release.Name,同樣,我們 DEBUG 下檢視結果:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '45820'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: musty-cheetah-configmap
data:
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  release: musty-cheetah

可以看到已經正常了,另外變數在range迴圈中也非常有用,我們可以在迴圈中用變數來同時捕獲索引的值:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
courselist:
{{- range $index, $course := .Values.courselist }}
- {{ $index }}: {{ $course | title | quote }}
{{- end }}

例如上面的這個列表,我們在range迴圈中使用$index$course兩個變數來接收後面列表迴圈的索引和對應的值,最終可以得到如下結果:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '46112'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: hasty-pug-configmap
data:
  k8s: "DEVOPS"
  python: "djangodjangodjango"
  release: hasty-pug
courselist:
- 0: "K8s"
- 1: "Python"
- 2: "Search"
- 3: "Golang"

我們可以看到 courselist 下面將索引和對應的值都打印出來了,實際上具有鍵和值的資料結構我們都可以使用range來迴圈獲得二者的值,比如我們可以對.Values.course這個字典來進行迴圈:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  {{- range $key, $value := .Values.course }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}
[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '34481'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: gilded-seagull-configmap
data:
  k8s: "devops"
  python: "django"

直接使用range迴圈,用變數$key$value來接收欄位.Values.course的鍵和值。這就是變數在 Helm 模板中的使用方法。

6. Helm模板之命名模板

前面我們學習了一些 Helm 模板中的一些常用使用方法,但是我們都是操作的一個模板檔案,在實際的應用中,很多都是相對比較複雜的,往往會超過一個模板,如果有多個應用模板,我們應該如何進行處理呢?這就需要用到新的概念:命名模板。

命名模板我們也可以稱為子模板,是限定在一個檔案內部的模板,然後給一個名稱。在使用命名模板的時候有一個需要特別注意的是:模板名稱是全域性的,如果我們聲明瞭兩個相同名稱的模板,最後載入的一個模板會覆蓋掉另外的模板,由於子 chart 中的模板也是和頂層的模板一起編譯的,所以在命名的時候一定要注意,不要重名了。

為了避免重名,有個通用的約定就是為每個定義的模板新增上 chart 名稱:{{define "mychart.labels"}}define關鍵字就是用來宣告命名模板的,加上 chart 名稱就可以避免不同 chart 間的模板出現衝突的情況。

6.1 宣告和使用命名模板

使用define關鍵字就可以允許我們在模板檔案內部建立一個命名模板,它的語法格式如下:

{{ define "ChartName.TplName" }}
# 模板內容區域
{{ end }}

比如,現在我們可以定義一個模板來封裝一個 label 標籤:

{{- define "mychart.labels" }}
  labels:
    from: helm
    date: {{ now | htmlDate }}
{{- end }}

然後我們可以將該模板嵌入到現有的 ConfigMap 中,然後使用template關鍵字在需要的地方包含進來即可:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
{{- define "mychart.labels" }}
  labels:
    from: helm
    date: {{ now | htmlDate }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  {{- template "mychart.labels" }}
data:
  {{- range $key, $value := .Values.course }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}

我們這個模板檔案被渲染過後的結果如下所示:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '40254'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: singed-cardinal-configmap
  labels:
    from: helm
    date: 2021-09-21
data:
  k8s: "devops"
  python: "django"

我們可以看到define區域定義的命名模板被嵌入到了template所在的區域,但是如果我們將命名模板全都寫入到一個模板檔案中的話無疑也會增大模板的複雜性。

還記得我們在建立 chart 包的時候,templates 目錄下面預設會生成一個_helpers.tpl檔案嗎?我們前面也提到過 templates 目錄下面除了NOTES.txt檔案和以下劃線_開頭命令的檔案之外,都會被當做 kubernetes 的資源清單檔案,而這個下劃線開頭的檔案不會被當做資源清單外,還可以被其他 chart 模板中呼叫,這個就是 Helm 中的partials檔案,所以其實我們完全就可以將命名模板定義在這些partials檔案中,預設就是_helpers.tpl檔案了。

現在我們將上面定義的命名模板移動到 templates/_helpers.tpl 檔案中去:

{{/* 生成基本的 labels 標籤 */}}
{{- define "mychart.labels" }}
  labels:
    from: helm
    date: {{ now | htmlDate }}
{{- end }}

一般情況下面,我們都會在命名模板頭部加一個簡單的文件塊,用/**/包裹起來,用來描述我們這個命名模板的用途的。

現在我們講命名模板從模板檔案 templates/configmap.yaml 中移除,當然還是需要保留 template 來嵌入命名模板內容,名稱還是之前的 mychart.lables,這是因為模板名稱是全域性的,所以我們可以能夠直接獲取到。我們再用 DEBUG 模式來除錯下是否符合預期?

6.2 模板範圍

上面我們定義的命名模板中,沒有使用任何物件,只是使用了一個簡單的函式,如果我們在裡面來使用 chart 物件相關資訊呢:

[root@node01 ~]# vim mychart/templates/_helpers.tpl
[root@node01 ~]# cat mychart/templates/_helpers.tpl
{{/* 生成基本的 labels 標籤 */}}
{{- define "mychart.labels" }}
  labels:
    from: helm
    date: {{ now | htmlDate }}
    chart: {{ .Chart.Name }}
    version: {{ .Chart.Version }}
{{- end }}

當命名模板被渲染時,它會接收由 template 呼叫時傳入的作用域,我們還要在 template 後面加上作用域範圍即可:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  {{- template "mychart.labels" . }}
data:
  {{- range $key, $value := .Values.course }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}

我們在 template 末尾傳遞了.,表示當前的最頂層的作用範圍,如果我們想要在命名模板中使用.Values範圍內的資料,當然也是可以的,現在我們再來渲染下我們的模板:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '34355'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: crazy-lionfish-configmap
  labels:
    from: helm
    date: 2021-09-21
    chart: mychart
    version: 0.1.0
data:
  k8s: "devops"
  python: "django"

我們可以看到 chart 的名稱和版本號都已經被正常渲染出來了。

6.3 include 函式

假如現在我們將上面的定義的 labels 單獨提取出來放置到 _helpers.tpl 檔案中:

[root@node01 ~]# vim mychart/templates/_helpers.tpl
[root@node01 ~]# cat mychart/templates/_helpers.tpl
{{/* 生成基本的 labels 標籤 */}}
{{- define "mychart.labels" }}
from: helm
date: {{ now | htmlDate }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}

現在我們將該命名模板插入到 configmap 模板檔案的 labels 部分和 data 部分:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
    {{- template "mychart.labels" . }}
data:
  {{- range $key, $value := .Values.course }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}
  {{- template "mychart.labels" . }}

然後同樣的檢視下渲染的結果:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '44078'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: incendiary-lionfish-configmap
  labels:
from: helm
date: 2021-09-21
chart: mychart
version: 0.1.0
data:
  k8s: "devops"
  python: "django"
from: helm
date: 2021-09-21
chart: mychart
version: 0.1.0

我們可以看到渲染結果是有問題的,不是一個正常的 YAML 檔案格式,這是因為template只是表示一個嵌入動作而已,不是一個函式,所以原本命名模板中是怎樣的格式就是怎樣的格式被嵌入進來了,比如我們可以在命名模板中給內容區域都空了兩個空格,再來檢視下渲染的結構:

---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mortal-cricket-configmap
  labels:
  from: helm
  date: 2021-09-21
  chart: mychart
  version: 0.1.0
data:
  k8s: "devops"
  python: "django"
  from: helm
  date: 2021-09-21
  chart: mychart
  version: 0.1.0

我們可以看到 data 區域裡面的內容是渲染正確的,但是上面 labels 區域是不正常的,因為命名模板裡面的內容是屬於 labels 標籤的,是不符合我們的預期的,但是我們又不可能再去把命名模板裡面的內容再增加兩個空格,因為這樣的話 data 裡面的格式又不符合預期了。

為了解決這個問題,Helm 提供了另外一個方案來代替template,那就是使用include函式,在需要控制空格的地方使用indent管道函式來自己控制,比如上面的例子我們替換成include函式:

[root@node01 ~]# vim mychart/templates/configmap.yaml 
[root@node01 ~]# cat mychart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
{{- include "mychart.labels" . | indent 4 }}
data:
  {{- range $key, $value := .Values.course }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}
{{- include "mychart.labels" . | indent 2 }}

在 labels 區域我們需要4個空格,所以在管道函式indent中,傳入引數4就可以,而在 data 區域我們只需要2個空格,所以我們傳入引數2即可以,現在我們來渲染下我們這個模板看看是否符合預期呢:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '39001'
...
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ugly-mouse-configmap
  labels:    
    from: helm
    date: 2021-09-21
    chart: mychart
    version: 0.1.0
data:
  k8s: "devops"
  python: "django"  
  from: helm
  date: 2021-09-21
  chart: mychart
  version: 0.1.0

可以看到是符合我們的預期,所以在 Helm 模板中我們使用 include 函式要比 template 更好,可以更好地處理 YAML 檔案輸出格式。

7. Helm模板之其他注意事項

上節課我們學習了命名模板的使用,命名模板是 Helm 模板中非常重要的一個功能,在我們實際開發 Helm Chart 包的時候非常有用,到這裡我們基本上就把 Helm 模板中經常使用到的一些知識點和大家介紹完了。但是仍然還是有一些在開發中值得我們注意的一些知識點,比如 NOTES.txt 檔案的使用、子 Chart 的使用、全域性值的使用,這節課我們就來和大家一起了解下這些知識點。

7.1 NOTES.txt 檔案

我們前面在使用 helm install 命令的時候,Helm 都會為我們打印出一大堆介紹資訊,這樣當別的使用者在使用我們的 chart 包的時候就可以根據這些註釋資訊快速瞭解我們的 chart 包的使用方法,這些資訊就是編寫在 NOTES.txt 檔案之中的,這個檔案是純文字的,但是它和其他模板一樣,具有所有可用的普通模板函式和物件。

現在我們在前面的示例中 templates 目錄下面建立一個 NOTES.txt 檔案:

[root@node01 ~]# vim mychart/templates/NOTES.txt
[root@node01 ~]# cat mychart/templates/NOTES.txt
Thank you for installing {{ .Chart.Name }}.

Your release is named {{ .Release.Name }}.

To learn more about the release, try:

  $ helm status {{ .Release.Name }}
  $ helm get {{ .Release.Name }}

我們可以看到我們在 NOTES.txt 檔案中也使用 Chart 和 Release 物件,現在我們在 mychart 包根目錄下面執行安裝命令檢視是否能夠得到上面的註釋資訊:

[root@node01 ~]# helm install ./mychart
NAME:   eloping-fly
LAST DEPLOYED: Tue Sep 21 23:07:31 2021
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME                   DATA  AGE
eloping-fly-configmap  6     0s


NOTES:
Thank you for installing mychart.

Your release is named eloping-fly.

To learn more about the release, try:

  $ helm status eloping-fly
  $ helm get eloping-fly

現在已經安裝成功了,而且下面的註釋部分也被渲染出來了,我們可以看到 NOTES.txt 裡面使用到的模板物件都被正確渲染了。

為我們建立的 chart 包提供一個清晰的 NOTES.txt 檔案是非常有必要的,可以為使用者提供有關如何使用新安裝 chart 的詳細資訊,這是一種非常友好的方式方法。

7.2 子 chart 包

我們到目前為止都只用了一個 chart,但是 chart 也可以有 子 chart 的依賴關係,它們也有自己的值和模板,在學習字 chart 之前,我們需要了解幾點關於子 chart 的說明:

  • 子 chart 是獨立的,所以子 chart 不能明確依賴於其父 chart
  • 子 chart 無法訪問其父 chart 的值
  • 父 chart 可以覆蓋子 chart 的值
  • Helm 中有全域性值的概念,可以被所有的 chart 訪問

7.3 建立子 chart

現在我們就來建立一個子 chart,還記得我們在建立 mychart 包的時候,在根目錄下面有一個空資料夾 charts 目錄嗎?這就是我們的子 chart 所在的目錄,在該目錄下面新增一個新的 chart:

[root@node01 ~]# cd mychart/charts/
[root@node01 charts]# helm create mysubchart
Creating mysubchart
[root@node01 charts]# rm -rf mysubchart/templates/*.*

[root@node01 charts]# tree ..
..
├── charts
│ └── mysubchart
│     ├── charts
│     ├── Chart.yaml
│     ├── templates
│     └── values.yaml
├── Chart.yaml
├── templates
│ ├── configmap.yaml
│ ├── config.yaml
│ ├── _helpers.tpl
│ └── NOTES.txt
└── values.yaml

5 directories, 8 files

同樣的,我們將子 chart 模板中的檔案全部刪除了,接下來,我們為子 chart 建立一個簡單的模板和 values 檔案了。

[root@node01 charts]# cat > mysubchart/values.yaml <<EOF
> in: mysub
> EOF

[root@node01 charts]# cat mysubchart/values.yaml 
in: mysub
[root@node01 charts]# cat > mysubchart/templates/configmap.yaml <<EOF
> apiVersion: v1
> kind: ConfigMap
> metadata:
>   name: {{ .Release.Name }}-configmap2
> data:
>   in: {{ .Values.in }}
> EOF

[root@node01 charts]# cat mysubchart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap2
data:
  in: {{ .Values.in }}

我們上面已經提到過每個子 chart 都是獨立的 chart,所以我們可以單獨給 mysubchart 進行測試:

[root@node01 charts]# helm install --dry-run --debug ./mysubchart
[debug] Created tunnel using local port: '42049'
...
---
# Source: mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: cold-peahen-configmap2
data:
  in: mysub

我們可以看到正常渲染出了結果。

7.4 值覆蓋

現在 mysubchart 這個子 chart 就屬於 mychart 這個父 chart 了,由於 mychart 是父級,所以我們可以在 mychart 的 values.yaml 檔案中直接配置子 chart 中的值,比如我們可以在 mychart/values.yaml 檔案中新增上子 chart 的值:

[root@node01 ~]# vim mychart/values.yaml 
[root@node01 ~]# cat mychart/values.yaml 
course:
  k8s: devops
  python: django
courselist:
- k8s
- python
- search
- golang

mysubchart:
  in: parent

注意最後兩行,mysubchart 部分內的任何指令都會傳遞到 mysubchart 這個子 chart 中去的,現在我們在 mychart 根目錄中執行除錯命令,可以檢視到子 chart 也被一起渲染了:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '44314'
...
---
# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: hazy-albatross-configmap2
data:
  in: parent
---
# Source: mychart/templates/configmap.yaml
...

我們可以看到子 chart 中的值已經被頂層的值給覆蓋了。但是在某些場景下面我們還是希望某些值在所有模板中都可以使用,這就需要用到全域性 chart 值了。

7.5 全域性值

全域性值可以從任何 chart 或者子 chart中進行訪問使用,values 物件中有一個保留的屬性是Values.global,就可以被用來設定全域性值,比如我們在父 chart 的 values.yaml 檔案中新增一個全域性值:

[root@node01 ~]# vim mychart/values.yaml 
[root@node01 ~]# cat mychart/values.yaml 
course:
  k8s: devops
  python: django
courselist:
- k8s
- python
- search
- golang

mysubchart:
  in: parent

global:
  allin: helm

我們在 values.yaml 檔案中添加了一個 global 的屬性,這樣的話無論在父 chart 中還是在子 chart 中我們都可以通過{{ .Values.global.allin }}來訪問這個全域性值了。比如我們在 mychart/templates/configmap.yaml 和 mychart/charts/mysubchart/templates/configmap.yaml 檔案的 data 區域下面都新增上如下內容:

...
data:
  allin: {{ .Values.global.allin }}
...

現在我們在 mychart 根目錄下面執行 debug 除錯模式:

[root@node01 ~]# helm install --dry-run --debug ./mychart
[debug] Created tunnel using local port: '32908'
...
---
# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: joyous-lambkin-configmap
data:
  allin: helm
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: joyous-lambkin-configmap
data:
  allin: helm

我們可以看到兩個模板中都輸出了allin: helm這樣的值,全域性變數對於傳遞這樣的資訊非常有用,不過也要注意我們不能濫用全域性值。

另外值得注意的是我們在學習命名模板的時候就提到過父 chart 和子 chart 可以共享模板。任何 chart 中的任何定義塊都可用於其他 chart,所以我們在給命名模板定義名稱的時候添加了 chart 名稱這樣的字首,避免衝突。

8. Helm Hooks

和 Kubernetes 裡面的容器一樣,Helm 也提供了 Hook 的機制,允許 chart 開發人員在 release 的生命週期中的某些節點來進行干預,比如我們可以利用 Hooks 來做下面的這些事情:

  • 在載入任何其他 chart 之前,在安裝過程中載入 ConfigMap 或 Secret
  • 在安裝新 chart 之前執行作業以備份資料庫,然後在升級後執行第二個作業以恢復資料
  • 在刪除 release 之前執行作業,以便在刪除 release 之前優雅地停止服務

值得注意的是 Hooks 和普通模板一樣工作,但是它們具有特殊的註釋,可以使 Helm 以不同的方式使用它們。

Hook 在資源清單中的 metadata 部分用 annotations 的方式進行宣告:

apiVersion: ...
kind: ....
metadata:
  annotations:
    "helm.sh/hook": "pre-install"
# ...

接下來我們就來和大家介紹下 Helm Hooks 的一些基本使用方法。

8.1 Hooks

在 Helm 中定義瞭如下一些可供我們使用的 Hooks:

  • 預安裝pre-install:在模板渲染後,kubernetes 建立任何資源之前執行
  • 安裝後post-install:在所有 kubernetes 資源安裝到集群后執行
  • 預刪除pre-delete:在從 kubernetes 刪除任何資源之前執行刪除請求
  • 刪除後post-delete:刪除所有 release 的資源後執行
  • 升級前pre-upgrade:在模板渲染後,但在任何資源升級之前執行
  • 升級後post-upgrade:在所有資源升級後執行
  • 預回滾pre-rollback:在模板渲染後,在任何資源回滾之前執行
  • 回滾後post-rollback:在修改所有資源後執行回滾請求
  • crd-install:在執行其他檢查之前新增 CRD 資源,只能用於 chart 中其他的資源清單定義的 CRD 資源。

8.2 生命週期

Hooks 允許開發人員在 release 的生命週期中的一些關鍵節點執行一些鉤子函式,我們正常安裝一個 chart 包的時候的生命週期如下所示:

  • 使用者執行helm install foo
  • chart 被載入到服務端 Tiller Server 中
  • 經過一些驗證,Tiller Server 渲染 foo 模板
  • Tiller 將產生的資源載入到 kubernetes 中去
  • Tiller 將 release 名稱和其他資料返回給 Helm 客戶端
  • Helm 客戶端退出

如果開發人員在 install 的生命週期中定義了兩個 hook:pre-installpost-install,那麼我們安裝一個 chart 包的生命週期就會多一些步驟了:

  • 使用者執行helm install foo
  • chart 被載入到服務端 Tiller Server 中
  • 經過一些驗證,Tiller Server 渲染 foo 模板
  • Tiller 將 hook 資源載入到 kubernetes 中,準備執行pre-install hook
  • Tiller 會根據權重對 hook 進行排序(預設分配權重0,權重相同的 hook 按升序排序)
  • Tiller 然後載入最低權重的 hook
  • Tiller 等待,直到 hook 準備就緒
  • Tiller 將產生的資源載入到 kubernetes 中
  • Tiller 執行post-install hook
  • Tiller 等待,直到 hook 準備就緒
  • Tiller 將 release 名稱和其他資料返回給客戶端
  • Helm 客戶端退出

等待 hook 準備就緒,這是一個阻塞的操作,如果 hook 中宣告的是一個 Job 資源,那麼 Tiller 將等待 Job 成功完成,如果失敗,則釋出失敗,在這個期間,Helm 客戶端是處於暫停狀態的。

對於所有其他型別,只要 kubernetes 將資源標記為載入(新增或更新),資源就被視為就緒狀態,當一個 hook 聲明瞭很多資源是,這些資源是被序列執行的。

另外需要注意的是 hook 建立的資源不會作為 release 的一部分進行跟蹤和管理,一旦 Tiller Server 驗證了 hook 已經達到了就緒狀態,它就不會去管它了。

所以,如果我們在 hook 中建立了資源,那麼不能依賴helm delete去刪除資源,因為 hook 建立的資源已經不受控制了,要銷燬這些資源,需要在pre-delete或者post-delete這兩個 hook 函式中去執行相關操作,或者將helm.sh/hook-delete-policy這個 annotation 新增到 hook 模板檔案中。

8.3 寫一個 hook

上面我們也說了 hook 和普通模板一樣,也可以使用普通的模板函式和常用的一些物件,比如ValuesChartRelease等等,唯一和普通模板不太一樣的地方就是在資源清單檔案中的 metadata 部分會有一些特殊的註釋 annotation。

例如,現在我們來建立一個 hook,在前面的示例 templates 目錄中新增一個 post-install-job.yaml 的檔案,表示安裝後執行的一個 hook:

[root@node01 ~]# vim mychart/templates/post-install-job.yaml
[root@node01 ~]# cat mychart/templates/post-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-post-install-job
  lables:
    release: {{ .Release.Name }}
    chart: {{ .Chart.Name }}
    version: {{ .Chart.Version }}
  annotations:
    # 注意,如果沒有下面的這個註釋的話,當前的這個Job就會被當成release的一部分
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    metadata:
      name: {{ .Release.Name }}
      labels:
        release: {{ .Release.Name }}
        chart: {{ .Chart.Name }}
        version: {{ .Chart.Version }}
    spec:
      restartPolicy: Never
      containers:
      - name: post-install-job
        image: alpine
        command: ["/bin/sleep", "{{ default "10" .Values.sleepTime }}"]

上面的 Job 資源中我們新增一個 annotations,要注意的是,如果我們沒有新增下面這行註釋的話,這個資源就會被當成是 release 的一部分資源:

annotations:
  "helm.sh/hook": post-install

當然一個資源中我們也可以同時部署多個 hook,比如我們還可以新增一個post-upgrade的鉤子:

annotations:
  "helm.sh/hook": post-install,post-upgrade

另外值得注意的是我們為 hook 定義了一個權重,這有助於建立一個確定性的執行順序,權重可以是正數也可以是負數,但是必須是字串才行。

annotations:
  "helm.sh/hook-weight": "-5"

最後還添加了一個刪除 hook 資源的策略:

annotations:
  "helm.sh/hook-delete-policy": hook-succeeded

刪除資源的策略可供選擇的註釋值:

  • hook-succeeded:表示 Tiller 在 hook 成功執行後刪除 hook 資源
  • hook-failed:表示如果 hook 在執行期間失敗了,Tiller 應該刪除 hook 資源
  • before-hook-creation:表示在刪除新的 hook 之前應該刪除以前的 hook

當 helm 的 release 更新時,有可能 hook 資源已經存在於群集中。預設情況下,helm 會嘗試建立資源,並丟擲錯誤"... already exists"

我們可以選擇 "helm.sh/hook-delete-policy": "before-hook-creation",取代 "helm.sh/hook-delete-policy": "hook-succeeded,hook-failed" 因為:

例如為了手動除錯,將錯誤的 hook 作業資源儲存在 kubernetes 中是很方便的。 出於某種原因,可能有必要將成功的 hook 資源保留在 kubernetes 中。同時,在 helm release 升級之前進行手動資源刪除是不可取的。 "helm.sh/hook-delete-policy": "before-hook-creation" 在 hook 中的註釋,如果在新的 hook 啟動前有一個 hook 的話,會使 Tiller 將以前的release 中的 hook 刪除,而這個 hook 同時它可能正在被其他一個策略使用。

作者:退役小學生 出處:https://www.cnblogs.com/ywb123 本文版權歸作者和部落格園共有,轉載請註明出處,素質很重要!!!