Helm
Kubernetes 包管理工具
Helm 可以幫助我們管理 Kubernetes 應(yīng)用程序 - Helm Charts 可以定義、安裝和升級(jí)復(fù)雜的 Kubernetes 應(yīng)用程序,Charts 包很容易創(chuàng)建、版本管理、分享和分布。Helm 對(duì)于 Kubernetes 來(lái)說(shuō)就相當(dāng)于 yum 對(duì)于 Centos 來(lái)說(shuō),如果沒(méi)有 yum 的話(huà),我們?cè)?Centos 下面要安裝一些應(yīng)用程序是極度麻煩的,同樣的,對(duì)于越來(lái)越復(fù)雜的 Kubernetes 應(yīng)用程序來(lái)說(shuō),如果單純依靠我們?nèi)ナ謩?dòng)維護(hù)應(yīng)用程序的 YAML 資源清單文件來(lái)說(shuō),成本也是巨大的。接下來(lái)我們就來(lái)了解了 Helm 的使用方法。
安裝
首先當(dāng)然需要一個(gè)可用的 Kubernetes 集群,然后在我們使用 Helm 的節(jié)點(diǎn)上已經(jīng)配置好可以通過(guò) kubectl 訪(fǎng)問(wèn)集群,因?yàn)?Helm 其實(shí)就是讀取的 kubeconfig 文件來(lái)訪(fǎng)問(wèn)集群的。
由于 Helm V2 版本必須在 Kubernetes 集群中安裝一個(gè) Tiller 服務(wù)進(jìn)行通信,這樣大大降低了其安全性和可用性,所以在 V3 版本中移除了服務(wù)端,采用了通用的 Kubernetes CRD 資源來(lái)進(jìn)行管理,這樣就只需要連接上 Kubernetes 即可,而且 V3 版本已經(jīng)發(fā)布了穩(wěn)定版,所以我們這里來(lái)安裝最新的 v3.0.1 版本,軟件包下載地址為:https://github.com/helm/helm/releases,我們可以根據(jù)自己的節(jié)點(diǎn)選擇合適的包,比如我這里是Mac,就下載 MacOS amd64 的版本。
下載到本地解壓后,將 helm 二進(jìn)制包文件移動(dòng)到任意的 PATH 路徑下即可:
$ helm version version.BuildInfo{Version:"v3.0.1", GitCommit:"7c22ef9ce89e0ebeb7125ba2ebf7d421f3e82ffa", GitTreeState:"clean", GoVersion:"go1.13.4"}
看到上面的版本信息證明已經(jīng)成功了。
一旦 Helm 客戶(hù)端準(zhǔn)備成功后,我們就可以添加一個(gè) chart 倉(cāng)庫(kù),當(dāng)然最常用的就是官方的 Helm stable charts 倉(cāng)庫(kù),但是由于官方的 charts 倉(cāng)庫(kù)地址需要ke xue shang wang,我們可以使用微軟的 charts 倉(cāng)庫(kù)代替:
$ helm repo add stable http://mirror.azure.cn/kubernetes/charts/ $ helm repo list NAME URL stable http://mirror.azure.cn/kubernetes/charts/
安裝完成后可以用 search 命令來(lái)搜索可以安裝的 chart 包:
$ helm search repo stable NAME CHART VERSION APP VERSION DESCRIPTION stable/acs-engine-autoscaler 2.2.2 2.1.1 DEPRECATED Scales worker nodes within agent pools stable/aerospike 0.3.1 v4.5.0.5 A Helm chart for Aerospike in Kubernetes stable/airflow 5.2.1 1.10.4 Airflow is a platform to programmatically autho... stable/ambassador 5.1.0 0.85.0 A Helm chart for Datawire Ambassador stable/anchore-engine 1.3.7 0.5.2 Anchore container analysis and policy evaluatio... stable/apm-server 2.1.5 7.0.0 The server receives data from the Elastic APM a... ......
示例
為了安裝一個(gè) chart 包,我們可以使用 helm install 命令,Helm 有多種方法來(lái)找到和安裝 chart 包,但是最簡(jiǎn)單的方法當(dāng)然是使用官方的 stable 這個(gè)倉(cāng)庫(kù)直接安裝:
首先從倉(cāng)庫(kù)中將可用的 charts 信息同步到本地,可以確保我們獲取到最新的 charts 列表:
$ helm repo update Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "stable" chart repository Update Complete. ? Happy Helming!?
比如我們現(xiàn)在安裝一個(gè) mysql 應(yīng)用:
$ helm install stable/mysql --generate-name NAME: mysql-1575619811 LAST DEPLOYED: Fri Dec 6 1614 2019 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: MySQL can be accessed via port 3306 on the following DNS name from within your cluster: mysql-1575619811.default.svc.cluster.local ......
我們可以看到 stable/mysql 這個(gè) chart 已經(jīng)安裝成功了,我們將安裝成功的這個(gè)應(yīng)用叫做一個(gè) release,由于我們?cè)诎惭b的時(shí)候指定了--generate-name 參數(shù),所以生成的 release 名稱(chēng)是隨機(jī)生成的,名為 mysql-1575619811。我們可以用下面的命令來(lái)查看 release 安裝以后對(duì)應(yīng)的 Kubernetes 資源的狀態(tài):
$ kubectl get all -l release=mysql-1575619811 NAME READY STATUS RESTARTS AGE pod/mysql-1575619811-8479b5b796-dgggz 0/1 Pending 0 27m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/mysql-1575619811 ClusterIP 10.106.141.2283306/TCP 27m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/mysql-1575619811 0/1 1 0 27m NAME DESIRED CURRENT READY AGE replicaset.apps/mysql-1575619811-8479b5b796 1 1 0 27m
我們也可以 helm show chart 命令來(lái)了解 MySQL 這個(gè) chart 包的一些特性:
$ helm show chart stable/mysql ......
如果想要了解更多信息,可以用 helm show all 命令:
$ helm show all stable/mysql ......
需要注意的是無(wú)論什么時(shí)候安裝 chart,都會(huì)創(chuàng)建一個(gè)新的 release,所以一個(gè) chart 包是可以多次安裝到同一個(gè)集群中的,每個(gè)都可以獨(dú)立管理和升級(jí)。
同樣我們也可以用 Helm 很容易查看到已經(jīng)安裝的 release:
$ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION mysql-1575619811 default 1 2019-12-06 1614.682302 +0800 CST deployed mysql-1.5.0 5.7.27
如果需要?jiǎng)h除這個(gè) release,也很簡(jiǎn)單,只需要使用 helm uninstall 命令即可:
$ helm uninstall mysql-1575619811 release "mysql-1575619811" uninstalled $ kubectl get all -l release=mysql-1575619811 No resources found. $ helm status mysql-1575619811 Error: release: not found
uninstall 命令會(huì)從 Kubernetes 中刪除 release,也會(huì)刪除與 release 相關(guān)的所有 Kubernetes 資源以及 release 歷史記錄。也可以在刪除的時(shí)候使用--keep-history參數(shù),則會(huì)保留 release 的歷史記錄,可以獲取該 release 的狀態(tài)就是 UNINSTALLED,而不是找不到 release了:
$ helm uninstall mysql-1575619811 --keep-history release "mysql-1575619811" uninstalled $ helm status mysql-1575619811 helm status mysql-1575619811 NAME: mysql-1575619811 LAST DEPLOYED: Fri Dec 6 16:47:14 2019 NAMESPACE: default STATUS: uninstalled ... $ helm ls -a NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION mysql-1575619811 default 1 2019-12-06 16:47:14.415214 +0800 CST uninstalled mysql-1.5.0 5.7.27
因?yàn)?Helm 會(huì)在刪除 release 后跟蹤你的 release,所以你可以審查歷史甚至取消刪除 release(使用 helm rollback 命令)。
定制
上面我們都是直接使用的 helm install 命令安裝的 chart 包,這種情況下只會(huì)使用 chart 的默認(rèn)配置選項(xiàng),但是更多的時(shí)候,是各種各樣的需求,索引我們希望根據(jù)自己的需求來(lái)定制 chart 包的配置參數(shù)。
我們可以使用 helm show values 命令來(lái)查看一個(gè) chart 包的所有可配置的參數(shù)選項(xiàng):
$ helm show values stable/mysql ## mysql image version ## ref: https://hub.docker.com/r/library/mysql/tags/ ## image: "mysql" imageTag: "5.7.14" busybox: image: "busybox" tag: "1.29.3" testFramework: enabled: true image: "dduportal/bats" tag: "0.4.0" ## 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 ......
上面我們看到的所有參數(shù)都是可以用自己的數(shù)據(jù)來(lái)覆蓋的,可以在安裝的時(shí)候通過(guò) YAML 格式的文件來(lái)傳遞這些參數(shù):
$ cat config.yaml mysqlUser: user0 mysqlPassword: user0pwd mysqlDatabase: user0db persistence: enabled: false $ helm install -f config.yaml stable/mysql helm install -f config.yaml mysql stable/mysql NAME: mysql LAST DEPLOYED: Fri Dec 6 1756 2019 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: MySQL can be accessed via port 3306 on the following DNS name from within your cluster: mysql.default.svc.cluster.local ......
release 安裝成功后,可以查看對(duì)應(yīng)的 Pod 信息:
$ kubectl get pod -l release=mysql NAME READY STATUS RESTARTS AGE mysql-ddd798f48-gnrzd 0/1 PodInitializing 0 119s $ kubectl describe pod mysql-ddd798f48-gnrzd ...... Environment: MYSQL_ROOT_PASSWORD:Optional: false MYSQL_PASSWORD: Optional: false MYSQL_USER: user0 MYSQL_DATABASE: user0db ......
可以看到環(huán)境變量 MYSQL_USER=user0,MYSQL_DATABASE=user0db 的值和我們上面配置的值是一致的。在安裝過(guò)程中,有兩種方法可以傳遞配置數(shù)據(jù):
--values(或者 -f):指定一個(gè) YAML 文件來(lái)覆蓋 values 值,可以指定多個(gè)值,最后邊的文件優(yōu)先
--set:在命令行上指定覆蓋的配置
如果同時(shí)使用這兩個(gè)值,--set將被合并到具有更高優(yōu)先級(jí)的--values,使用--set指定的值將持久化在 ConfigMap 中,對(duì)于給定的 release,可以使用helm get values
--set選項(xiàng)接收零個(gè)或多個(gè) name/value 對(duì),最簡(jiǎn)單的用法就是--set name=value,相當(dāng)于 YAML 文件中的:
name: value
多個(gè)值之間用字符串“,”隔開(kāi),用法就是--set a=b,c=d,相當(dāng)于 YAML 文件中的:
a: b c: d
也支持更加復(fù)雜的表達(dá)式,例如--set outer.inner=value,對(duì)應(yīng) YAML:
outer: inner: value
對(duì)于列表數(shù)組可以用{}來(lái)包裹,比如-set name={a, b, c},對(duì)應(yīng) YAML:
name: - a - b - c
從 Helm 2.5.0 開(kāi)始,就可以使用數(shù)組索引語(yǔ)法來(lái)訪(fǎng)問(wèn)列表中某個(gè)項(xiàng),比如--set servers[0].port=80,對(duì)應(yīng)的 YAML 為:
servers: - port: 80
也可以這樣設(shè)置多個(gè)值,比如-set servers[0].port=80,servers[0].host=example,對(duì)應(yīng)的 YAML 為:
servers - port: 80 host: example
有時(shí)候你可能需要在--set選項(xiàng)中使用特殊的字符,這個(gè)時(shí)候可以使用反斜杠來(lái)轉(zhuǎn)義字符,比如--set name=value1,value2,對(duì)應(yīng)的 YAML 為:
name: "value1,value2"
類(lèi)似的,你還可以轉(zhuǎn)義.,當(dāng) chart 模板中使用 toYaml 函數(shù)來(lái)解析 annotations、labels 以及 node selectors 之類(lèi)的時(shí)候,這非常有用,比如--set nodeSelector."kubernetes.io/role"=master,對(duì)應(yīng)的 YAML 文件:
nodeSelector: kubernetes.io/role: master
深度嵌套的數(shù)據(jù)結(jié)構(gòu)可能很難使用--set來(lái)表示,所以一般推薦還是使用 YAML 文件來(lái)進(jìn)行覆蓋,當(dāng)然在設(shè)計(jì) chart 模板的時(shí)候也可以結(jié)合考慮到--set這種用法,盡可能的提供更好的支持。
更多安裝方式
helm install 命令可以從多個(gè)源進(jìn)行安裝:
chart 倉(cāng)庫(kù)(類(lèi)似于上面我們提到的)
本地 chart 壓縮包(helm install foo-0.1.1.tgz)
本地解壓縮的 chart 目錄(helm install foo path/to/foo)
在線(xiàn)的 URL(helm install foolhttps://example.com/charts/foo-1.2.3.tgz)
升級(jí)或回滾
當(dāng)新版本的 chart 包發(fā)布的時(shí)候,或者當(dāng)你要更改 release 的配置的時(shí)候,你可以使用 helm upgrade 命令來(lái)操作。升級(jí)需要一個(gè)現(xiàn)有的 release,并根據(jù)提供的信息對(duì)其進(jìn)行升級(jí)。因?yàn)?Kubernetes charts 可能很大而且很復(fù)雜,Helm 會(huì)嘗試以最小的侵入性進(jìn)行升級(jí),它只會(huì)更新自上一版本以來(lái)發(fā)生的變化:
$ helm upgrade -f panda.yaml mysql stable/mysql helm upgrade -f panda.yaml mysql stable/mysql Release "mysql" has been upgraded. Happy Helming! NAME: mysql LAST DEPLOYED: Fri Dec 6 2111 2019 NAMESPACE: default STATUS: deployed REVISION: 2 ...
我們這里 mysql 這個(gè) release 用相同的 chart 包進(jìn)行升級(jí),但是新增了一個(gè)配置項(xiàng):
mysqlRootPassword: passw0rd
我們可以使用helm get values來(lái)查看新設(shè)置是否生效:
$ helm get values mysql USER-SUPPLIED VALUES: mysqlDatabase: user0db mysqlPassword: user0pwd mysqlRootPassword: passw0rd mysqlUser: user0 persistence: enabled: false
helm get 命令是查看集群中 release 的非常有用的命令,正如我們?cè)谏厦婵吹降?,它顯示了 panda.yaml 中的新配置值被部署到了集群中,現(xiàn)在如果某個(gè)版本在發(fā)布期間沒(méi)有按計(jì)劃進(jìn)行,那么可以使用 helm rollback [RELEASE] [REVISION] 命令很容易回滾到之前的版本:
$ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION mysql default 2 2019-12-06 21:06:11.36358 +0800 CST deployed mysql-1.5.0 5.7.27 $ helm history mysql REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION 1 Fri Dec 6 17:53:03 2019 superseded mysql-1.5.0 5.7.27 Install complete 2 Fri Dec 6 21:06:11 2019 deployed mysql-1.5.0 5.7.27 Upgrade complete $ helm rollback mysql 1 Rollback was a success! Happy Helming! $ kubectl get pods -l release=mysql NAME READY STATUS RESTARTS AGE mysql-ddd798f48-gnrzd 1/1 Running 0 3h25m $ helm get values mysql USER-SUPPLIED VALUES: mysqlDatabase: user0db mysqlPassword: user0pwd mysqlUser: user0 persistence: enabled: false
可以看到 values 配置已經(jīng)回滾到之前的版本了。上面的命令回滾到了 release 的第一個(gè)版本,每次進(jìn)行安裝、升級(jí)或回滾時(shí),修訂號(hào)都會(huì)加 1,第一個(gè)修訂號(hào)始終為1,我們可以使用helm history [RELEASE]來(lái)查看某個(gè)版本的修訂號(hào)。
除此之外我們還可以指定一些有用的選項(xiàng)來(lái)定制install/upgrade/rollback的一些行為,要查看完整的參數(shù)標(biāo)志,我們可以運(yùn)行helm
--timeout: 等待 Kubernetes 命令完成的時(shí)間,默認(rèn)是 300(5分鐘)
--wait: 等待直到所有 Pods 都處于就緒狀態(tài)、PVCs 已經(jīng)綁定、Deployments 具有處于就緒狀態(tài)的最小 Pods 數(shù)量(期望值減去 maxUnavailable)以及 Service 有一個(gè) IP 地址,然后才標(biāo)記 release 為成功狀態(tài)。它將等待與 --timeout 值一樣長(zhǎng)的時(shí)間,如果達(dá)到超時(shí),則 release 將標(biāo)記為失敗。注意:在 Deployment 將副本設(shè)置為 1 并且作為滾動(dòng)更新策略的一部分,maxUnavailable 未設(shè)置為0的情況下,--wait 將返回就緒狀態(tài),因?yàn)樗褲M(mǎn)足就緒狀態(tài)下的最小 Pod 數(shù)量
--no-hooks: 將會(huì)跳過(guò)命令的運(yùn)行 hooks
--recreate-pods: 僅適用于 upgrade 和 rollback,這個(gè)標(biāo)志將導(dǎo)致重新創(chuàng)建所有的 Pods。(Helm3 中啟用了)
Charts
Helm 使用一種名為 charts 的包格式,一個(gè) chart 是描述一組相關(guān)的 Kubernetes 資源的文件集合,單個(gè) chart 可能用于部署簡(jiǎn)單的應(yīng)用,比如 memcached pod,或者復(fù)雜的應(yīng)用,比如一個(gè)帶有 HTTP 服務(wù)、數(shù)據(jù)庫(kù)、緩存等等功能的完整 web 應(yīng)用程序。
Charts 是創(chuàng)建在特定目錄下面的文件集合,然后可以將它們打包到一個(gè)版本化的存檔中來(lái)部署。接下來(lái)我們就來(lái)看看使用 Helm 構(gòu)建 charts 的一些基本方法。
文件結(jié)構(gòu)
chart 被組織為一個(gè)目錄中的文件集合,目錄名稱(chēng)就是 chart 的名稱(chēng)(不包含版本信息),下面是一個(gè) WordPress 的 chart,會(huì)被存儲(chǔ)在 wordpress/ 目錄下面,基本結(jié)構(gòu)如下所示:
wordpress/ Chart.yaml # 包含當(dāng)前 chart 信息的 YAML 文件 LICENSE # 可選:包含 chart 的 license 的文本文件 README.md # 可選:一個(gè)可讀性高的 README 文件 values.yaml # 當(dāng)前 chart 的默認(rèn)配置 values values.schema.json # 可選: 一個(gè)作用在 values.yaml 文件上的 JSON 模式 charts/ # 包含該 chart 依賴(lài)的所有 chart 的目錄 crds/ # Custom Resource Definitions templates/ # 模板目錄,與 values 結(jié)合使用時(shí),將渲染生成 Kubernetes 資源清單文件 templates/NOTES.txt # 可選: 包含簡(jiǎn)短使用使用的文本文件
另外 Helm 會(huì)保留 charts/、crds/ 以及 templates/ 目錄以及上面列出的文件名的使用。
Chart.yaml 文件
對(duì)于一個(gè) chart 包來(lái)說(shuō) Chart.yaml 文件是必須的,它包含下面的這些字段:
apiVersion: chart API 版本 (必須) name: chart 名 (必須) version: SemVer 2版本 (必須) kubeVersion: 兼容的 Kubernetes 版本 (可選) description: 一句話(huà)描述 (可選) type: chart 類(lèi)型 (可選) keywords: - 當(dāng)前項(xiàng)目關(guān)鍵字集合 (可選) home: 當(dāng)前項(xiàng)目的 URL (可選) sources: - 當(dāng)前項(xiàng)目源碼 URL (可選) dependencies: # chart 依賴(lài)列表 (可選) - name: chart 名稱(chēng) (nginx) version: chart 版本 ("1.2.3") repository: 倉(cāng)庫(kù)地址 ("https://example.com/charts") maintainers: # (可選) - name: 維護(hù)者名字 (對(duì)每個(gè) maintainer 是必須的) email: 維護(hù)者的 email (可選) url: 維護(hù)者 URL (可選) icon: chart 的 SVG 或者 PNG 圖標(biāo) URL (可選). appVersion: 包含的應(yīng)用程序版本 (可選). 不需要 SemVer 版本 deprecated: chart 是否已被棄用 (可選, boolean)
其他字段默認(rèn)會(huì)被忽略。
版本
每個(gè) chart 都必須有一個(gè)版本號(hào),版本必須遵循 SemVer2 標(biāo)準(zhǔn),和 Helm Classic 不同,Kubernetes Helm 使用版本號(hào)作為 release 的標(biāo)記,倉(cāng)庫(kù)中的軟件包通過(guò)名稱(chēng)加上版本號(hào)來(lái)標(biāo)識(shí)的。
例如,將一個(gè) nginx 的 chart 包 version 字段設(shè)置為:1.2.3,則 chart 最終名稱(chēng)為:
nginx-1.2.3.tgz
還支持更復(fù)雜的 SemVer2 名稱(chēng),例如版本:1.2.3-alpha.1+ef365,但是需要注意的是系統(tǒng)明確禁止使用非 SemVer 的名稱(chēng)。
Chart.yaml 中的 version 字段被很多 Helm 工具使用,包括 CLI 工具,生成包的時(shí)候,命令 helm package 將使用該字段作為包名稱(chēng)中的標(biāo)記,系統(tǒng)是默認(rèn) Chart 包中的版本號(hào)與 chart.yaml 中的版本號(hào)匹配的,所以如果不匹配的話(huà)就導(dǎo)致一系列錯(cuò)誤。
apiVersion 字段
對(duì)于 Helm 3 以上的版本 apiVersion 字段應(yīng)該是 v2,之前版本的 Chart 應(yīng)該設(shè)置為1,并且也可以有 Helm 3 進(jìn)行安裝。
appVersion 字段
要注意 appVersion 字段與 version 字段無(wú)關(guān),這是一種指定應(yīng)用程序版本的方法,比如 drupal 的 Chart 包可能有一個(gè) appVersion: 8.2.1 的字段,表示 Chart 中包含的 drupal 版本是 8.2.1,該字段僅供參考,對(duì) Chart 版本的計(jì)算不會(huì)產(chǎn)生影響。
棄用 Charts
當(dāng)在 Chart 倉(cāng)庫(kù)中管理 charts 的時(shí)候,有時(shí)候需要棄用一個(gè) chart,Chart.yaml 中的可選字段 deprecated 可以用來(lái)標(biāo)記一個(gè) chart 為棄用狀態(tài)。如果將倉(cāng)庫(kù)中最新版本的 chart 標(biāo)記為棄用,則整個(gè) chart 都會(huì)被當(dāng)做棄用狀態(tài)了。以后可以通過(guò)發(fā)布一個(gè)未被標(biāo)記為棄用狀態(tài)的新版本來(lái)重新使用該 chart。棄用 charts 的工作流程如下所示:
更新 chart 的 Chart.yaml 來(lái)標(biāo)記 chart 為棄用狀態(tài)
發(fā)布該新版本到 Chart 倉(cāng)庫(kù)
從源碼倉(cāng)庫(kù)(比如 git)中刪除 chart
Chart 類(lèi)型
type 字段定義 chart 的類(lèi)型,可以定義兩種類(lèi)型:應(yīng)用程序(application)和庫(kù)(library)。應(yīng)用程序是默認(rèn)的類(lèi)型,它是一個(gè)可以完整操作的標(biāo)準(zhǔn) chart,庫(kù)或者輔助類(lèi) chart 為 chart 提供了一些實(shí)用的功能,library 不同于應(yīng)用程序 chart,因?yàn)樗鼪](méi)有資源對(duì)象,所以無(wú)法安裝。
一個(gè)應(yīng)用 chart 也可以當(dāng)作庫(kù)進(jìn)行使用。通過(guò)將類(lèi)型設(shè)置為 library,然后該 chart 就會(huì)渲染成一個(gè)庫(kù),可以在其中使用所有的實(shí)用性功能,chart 的所有資源對(duì)象都不會(huì)被渲染。
LICENSE, README 和 NOTES
Chart 還可以包含用于描述 chart 的安裝、配置、用法和許可證書(shū)的文件。
LICENSE 是一個(gè)純文本文件,其中包含 chart 的許可證書(shū)。chart 可以包含一個(gè)許可證書(shū),因?yàn)樗赡茉谀0逯芯哂?a target="_blank">編程邏輯,所以不只是配置,如果需要,chart 還可以為應(yīng)用程序提供單獨(dú)的 license(s)。
Chart 的 README 文件應(yīng)該采用 Markdown(README.md)格式,并且通常應(yīng)該包含如下的一些信息:
chart 提供的應(yīng)用程序的描述信息
運(yùn)行 chart 的任何先決條件或者要求
values.yaml 和默認(rèn)值中的一些選項(xiàng)說(shuō)明
與 chart 的安裝或配置有關(guān)的任何其他信息
chart 還可以包含簡(jiǎn)短的純文本模板或者 NOTES.txt 文件,該文件將在安裝后以及查看 release 狀態(tài)的時(shí)候打印出來(lái)。該文件會(huì)被當(dāng)成模板文件,并且可以用于顯示使用說(shuō)明,后續(xù)步驟或與 release 有關(guān)的任何其他信息。例如,可以提供用于連接到數(shù)據(jù)或訪(fǎng)問(wèn) Web UI 的指令。由于在運(yùn)行 helm install 或者 helm status 的時(shí)候該文件會(huì)打印到 STDOUT 中,所以建議該文件內(nèi)容保持內(nèi)容簡(jiǎn)短然后可以指向 README 文件來(lái)獲取更多詳細(xì)信息。
依賴(lài)
在 Helm 中,一個(gè) chart 包可能會(huì)依賴(lài)許多其他的 chart。這些依賴(lài)關(guān)系可以使用 Chart.yaml 中的依賴(lài)關(guān)系字段動(dòng)態(tài)鏈接,也可以引入到 charts/ 目錄手動(dòng)進(jìn)行管理。
使用 dependencies 字段管理依賴(lài)
當(dāng)前 chart 所需的依賴(lài) chart 需要在 dependencies 字段中進(jìn)行定義,如下所示:
dependencies: - name: apache version: 1.2.3 repository: https://example.com/charts - name: mysql version: 3.2.1 repository: https://another.example.com/charts
name 字段是所依賴(lài)的 chart 的名稱(chēng)
version 字段是依賴(lài)的 chart 版本
repository 字段是 chart 倉(cāng)庫(kù)的完整 URL,不過(guò)需要注意,必須使用 helm repo add 在本地添加該 repo
定義了依賴(lài)項(xiàng)后,可以運(yùn)行 helm dependency update 來(lái)更新依賴(lài)項(xiàng),它將根據(jù)你的依賴(lài)項(xiàng)文件把你所有指定的 chart 包下載到 charts/ 目錄中:
$ helm dependency update foochart Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "local" chart repository ...Successfully got an update from the "stable" chart repository ...Successfully got an update from the "example" chart repository ...Successfully got an update from the "another" chart repository Update Complete. Happy Helming! Saving 2 charts Downloading apache from repo https://example.com/charts Downloading mysql from repo https://another.example.com/charts
當(dāng)執(zhí)行 helm dependency update 命令的時(shí)候會(huì)解析 chart 的依賴(lài)項(xiàng),會(huì)將他們作為 chart 包文件下載存放到 charts/ 目錄中,所以,對(duì)于上面的示例,我們可以在 charts 目錄中看到如下的文件:
charts/ apache-1.2.3.tgz mysql-3.2.1.tgz
alias 字段
除了上面的幾個(gè)字段之外,每個(gè)依賴(lài)項(xiàng)還可以包含一個(gè)可選的 alias 別名字段。為依賴(lài) chart 添加別名將使用別名作為依賴(lài)的名稱(chēng)。在需要訪(fǎng)問(wèn)其他名稱(chēng)的 chart 情況下,就可以使用別名,如下所示:
# parentchart/Chart.yaml dependencies: - name: subchart repository: http://localhost:10191 version: 0.1.0 alias: new-subchart-1 - name: subchart repository: http://localhost:10191 version: 0.1.0 alias: new-subchart-2 - name: subchart repository: http://localhost:10191 version: 0.1.0
在上面示例中,我們將獲得3個(gè)依賴(lài)項(xiàng):
subchart new-subchart-1 new-subchart-2
當(dāng)然其實(shí)我們也可以手動(dòng)來(lái)實(shí)現(xiàn),將同一個(gè) chart 以不同的名稱(chēng)多次復(fù)制/粘貼到 charts/ 目錄中也是可以的。
TEMPLATES 和 VALUES
Helm Chart 模板是用 Go template 語(yǔ)言 進(jìn)行編寫(xiě)的,另外還額外增加了(【Sprig】](https://github.com/Masterminds/sprig)庫(kù)中的50個(gè)左右的附加模板函數(shù)和一些其他專(zhuān)用函數(shù)。
所有模板文件都存儲(chǔ)在 chart 的 templates/ 目錄下面,當(dāng) Helm 渲染 charts 的時(shí)候,它將通過(guò)模板引擎?zhèn)鬟f該目錄中的每個(gè)文件。模板的 Values 可以通過(guò)兩種方式提供:
Chart 開(kāi)發(fā)人員可以在 chart 內(nèi)部提供一個(gè)名為 values.yaml 的文件,該文件可以包含默認(rèn)的 values 值內(nèi)容。
Chart 用戶(hù)可以提供包含 values 值的 YAML 文件,可以在命令行中通過(guò) helm install 來(lái)指定該文件。
當(dāng)用戶(hù)提供自定義 values 值的時(shí)候,這些值將覆蓋 chart 中 values.yaml 文件中的相應(yīng)的值。
模板文件
模板文件遵循編寫(xiě) Go 模板的標(biāo)準(zhǔn)約定(可以查看 text/template 包文檔查看詳細(xì)信息),下面是一個(gè)模板文件示例:
apiVersion: v1 kind: ReplicationController metadata: name: deis-database namespace: deis labels: app.kubernetes.io/managed-by: deis spec: replicas: 1 selector: app.kubernetes.io/name: deis-database template: metadata: labels: app.kubernetes.io/name: deis-database spec: serviceAccount: deis-database containers: - name: deis-database image: {{ .Values.imageRegistry }}/postgres:{{ .Values.dockerTag }} imagePullPolicy: {{ .Values.pullPolicy }} ports: - containerPort: 5432 env: - name: DATABASE_STORAGE value: {{ default "minio" .Values.storage }}
上面這個(gè)示例是 Kubernetes replication 控制器的一個(gè)模板,它可以使用以下4個(gè)模板值(通常在 values.yaml 文件中定義的):
imageRegistry:Docker 鏡像倉(cāng)庫(kù)
dockerTag:Docker 鏡像 tag
pullPolicy:鏡像拉取策略
storage:存儲(chǔ)后端,默認(rèn)設(shè)置為 "minio"
這些所有的 values 值都是有模板作者來(lái)定義的,Helm 不會(huì)也不需要規(guī)定這些參數(shù)??梢钥梢圆榭碖ubernetes Charts 項(xiàng)目去了解更多的 charts 項(xiàng)目的詳細(xì)內(nèi)容。
預(yù)定義 Values
在模板中用 .Values 可以獲取到 values.yaml 文件(或者 --set 參數(shù))提供的 values 值,此外,還可以在模板中訪(fǎng)問(wèn)其他預(yù)定義的數(shù)據(jù)。下面是一些預(yù)定義的、可用于每個(gè)模板、并且不能被覆蓋的 values 值,與所有 values 值一樣,名稱(chēng)都是區(qū)分大小寫(xiě)的:
Release.Name:release 的名稱(chēng)(不是 chart)
Release.Namespace:release 被安裝到的命名空間
Release.Service:渲染當(dāng)前模板的服務(wù),在 Helm 上,實(shí)際上該值始終為 Helm
Release.IsUpgrade:如果當(dāng)前操作是升級(jí)或回滾,則該值為 true
Release.IsInstall:如果當(dāng)前操作是安裝,則該值為 true
Chart:Chart.yaml 文件的內(nèi)容,可以通過(guò) Chart.Version 來(lái)獲得 Chart 的版本,通過(guò) Chart.Maintainers 可以獲取維護(hù)者信息
Files: 一個(gè)包含 chart 中所有非特殊文件的 map 對(duì)象,這不會(huì)給你訪(fǎng)問(wèn)模板的權(quán)限,但是會(huì)給你訪(fǎng)問(wèn)存在的其他文件的權(quán)限(除非使用 .helmignore 排除它們),可以使用 {{ index .Files "file.name" }} 或者 {{ .Files.Get name }} 或者 {{ .Files.GetString name }} 函數(shù)來(lái)訪(fǎng)問(wèn)文件,你還可以使用 {{ .Files.GetBytes }} 以 []byte 的形式獲取訪(fǎng)問(wèn)文件的內(nèi)容
Capabilities:也是一個(gè)類(lèi) map 的對(duì)象,其中包含有關(guān) Kubernetes 版本({{ .Capabilities.KubeVersion }})和支持的 Kubernetes API 版本({{ .Capabilities.APIVersions.Has "batch/v1" }})信息。
任何未知的 Chart.yaml 字段都會(huì)被刪除,在 Chart 對(duì)象內(nèi)部無(wú)法訪(fǎng)問(wèn)他們,所以,Chart.yaml 不能用于將任意結(jié)構(gòu)化的數(shù)據(jù)傳遞到模板中,但是可以使用 values 文件來(lái)傳遞。
Values 文件
為模板提供一些必須的 values 值的 values.yaml 文件如下所示:
imageRegistry: "quay.io/deis" dockerTag: "latest" pullPolicy: "Always" storage: "s3"
values 文件的格式是 YAML,一個(gè) chart 包可能包含一個(gè)默認(rèn)的 values.yaml 文件,helm install 命令允許用戶(hù)通過(guò)提供其他的 YAML 值文件來(lái)覆蓋默認(rèn)的值:
$ helm install --values=myvals.yaml wordpress
用這種方式來(lái)傳遞 values 值的時(shí)候,它們將合并到默認(rèn)值文件中,比如有一個(gè) myvals.yaml 文件如下所示:
storage: "gcs"
將其與 chart 的 values.yaml 文件合并后,得到的結(jié)果為:
imageRegistry: "quay.io/deis" dockerTag: "latest" pullPolicy: "Always" storage: "gcs"
我們可以看到只有最后一個(gè)字段被覆蓋了。
chart 內(nèi)包含的默認(rèn) values 文件必須命名為 values.yaml,但是在命令行上指定的文件可以任意命名。 如果在 helm install 或者 helm upgrade 的時(shí)候使用 --set 參數(shù),則這些值將在客戶(hù)端轉(zhuǎn)換為 YAML 格式。 如果 values 文件存在任何必須的條目,則可以使用 required 函數(shù)在 chart 模板中將它們聲明為必須選項(xiàng)。
然后我們就可以使用.Values對(duì)象在模板中訪(fǎng)問(wèn)任意一個(gè) values 值,類(lèi)似于下面的模板文件:
apiVersion: v1 kind: ReplicationController metadata: name: deis-database namespace: deis labels: app.kubernetes.io/managed-by: deis spec: replicas: 1 selector: app.kubernetes.io/name: deis-database template: metadata: labels: app.kubernetes.io/name: deis-database spec: serviceAccount: deis-database containers: - name: deis-database image: {{ .Values.imageRegistry }}/postgres:{{ .Values.dockerTag }} imagePullPolicy: {{ .Values.pullPolicy }} ports: - containerPort: 5432 env: - name: DATABASE_STORAGE value: {{ default "minio" .Values.storage }}
作用范圍、依賴(lài)和 Values
values 文件可以聲明頂級(jí)的 chart 以及該 chart 的 charts/ 目錄中包含的任何 chart 的值?;蛘?,換句話(huà)說(shuō),values 文件可以為 chart 以及他的任何依賴(lài)項(xiàng)提供 values 值。例如,上面提到了 WordPress 這個(gè) chart 同時(shí)依賴(lài) mysql 和 apache 這兩個(gè)依賴(lài)項(xiàng),values 文件可以為所有這些組件提供 values 值:
title: "My WordPress Site" # 傳遞到 WordPress 模板 mysql: max_connections: 100 # 傳遞到 MySQL password: "secret" apache: port: 8080 # 傳遞到 Apache
較高級(jí)別的 Charts 可以訪(fǎng)問(wèn)下面定義的所有變量,所以,WordPress 這個(gè) chart 可以通過(guò) .Values.mysql.password 來(lái)訪(fǎng)問(wèn) MySQL 的密碼,但是較低級(jí)別的 chart 是無(wú)法訪(fǎng)問(wèn)父 chart 中的內(nèi)容的,所有 MySQL 無(wú)法獲取到 title 屬性,當(dāng)然同樣也不能訪(fǎng)問(wèn) apache.port。
Values 是有命名空間的,但是會(huì)對(duì)其進(jìn)行調(diào)整,比如對(duì)于 WordPress 這個(gè) chart 來(lái)說(shuō),它可以通過(guò).Values.mysql.password來(lái)進(jìn)行訪(fǎng)問(wèn),但是對(duì)于 MySQL 這個(gè) chart 本身來(lái)說(shuō),values 的范圍縮小了,命名空間前綴會(huì)被刪除,所以它只需要通過(guò) .Values.password 就可以訪(fǎng)問(wèn)到。
全局 Values
從 2.0.0-Alpha.2 版本開(kāi)始,Helm 開(kāi)始支持特殊的 global 全局值,比如將上面的示例修改如下:
title: "My WordPress Site" # 傳遞到 WordPress 模板 global: app: MyWordPress mysql: max_connections: 100 # 傳遞到 MySQL password: "secret" apache: port: 8080 # 傳遞到 Apache
上面我們添加了一個(gè)全局范圍的 value 值:app: MyWordPress,該值可以通過(guò).Values.global.app提供給所有 chart 使用。
例如,mysql 模板可以以{{ .Values.global.app }}來(lái)訪(fǎng)問(wèn) app,apache chart 也可以,實(shí)際上,上面的 values 文件會(huì)這樣重新生成:
title: "My WordPress Site" # 傳遞到 WordPress 模板 global: app: MyWordPress mysql: global: app: MyWordPress max_connections: 100 # 傳遞到 MySQL password: "secret" apache: global: app: MyWordPress port: 8080 # 傳遞到 Apache
這種方式提供了一種與所有子 chart 共享一個(gè)頂級(jí)變量的方式,這對(duì)于設(shè)置 meta 數(shù)據(jù)這種屬性是非常有用的。如果子 chart 聲明了全局變量,則該全局變量將向下(傳遞到子 chart 的子 chart 中)傳遞,而不會(huì)向上傳遞到父 chart,子 chart 無(wú)法影響 父 chart的值。同樣,父 chart 的全局遍歷優(yōu)先與子 chart 中的全局變量。
Schema 文件
有時(shí)候,chart 開(kāi)發(fā)者可能希望在其 values 值上面定義一個(gè)結(jié)構(gòu),這種情況下可以通過(guò)在 values.schema.json 文件中定義一個(gè) schema 來(lái)完成,這里的 schema 就是一個(gè) JSON Schema 文件結(jié)構(gòu)規(guī)范,如下所示:
{ "$schema": "https://json-schema.org/draft-07/schema#", "properties": { "image": { "description": "Container Image", "properties": { "repo": { "type": "string" }, "tag": { "type": "string" } }, "type": "object" }, "name": { "description": "Service name", "type": "string" }, "port": { "description": "Port", "minimum": 0, "type": "integer" }, "protocol": { "type": "string" } }, "required": [ "protocol", "port" ], "title": "Values", "type": "object" }
該 schema 會(huì)對(duì) values 值進(jìn)行校驗(yàn),調(diào)用以下任何命令時(shí),都會(huì)進(jìn)行驗(yàn)證:
helm install
helm upgrade
helm lint
helm template
比如下面的示例文件就可以滿(mǎn)足上面的 schema 要求:
name: frontend protocol: https port: 443
需要注意的是該 schema 將應(yīng)用于最終的.Values對(duì)象,而不僅僅是應(yīng)用于 values.yaml 文件,所以下面的文件也是可以滿(mǎn)足 schema 要求的:
name: frontend protocol: https
因?yàn)樵诎惭b的時(shí)候我們通過(guò)--set選項(xiàng)傳遞了必須的 port 屬性:
$ helm install --set port=443
此外,還會(huì)根據(jù)所有的子 chart schemas 來(lái)檢查最終的.Values對(duì)象,這意味著父 chart 無(wú)法規(guī)避對(duì)子 chart 的限制。同樣的,如果子 chart 要求未滿(mǎn)足子 chart 的 values.yaml 文件,則父 chart 必須滿(mǎn)足這些限制才能生效。
參考文檔
在編寫(xiě)模板、values、和 schema 文件的時(shí)候,下面這些文檔可以提供一些幫助:
Go Template
額外的模板函數(shù)
YAML 文件
JSON Schema
CRDS
Kubernetes 提供了一種聲明新類(lèi)型的 Kubernetes 對(duì)象的機(jī)制,使用 CustomResourceDefinitions(CRDS)可以讓 Kubernetes 開(kāi)發(fā)人員聲明自定義資源類(lèi)型。
在 Helm 3 中,CRD 被視為一種特殊的對(duì)象,它們?cè)?chart 部分之前被安裝,并且會(huì)受到一些限制。CRD YAML 文件應(yīng)該放置 chart 內(nèi)的 crds/ 目錄下面。多個(gè) CRDs 可以放在同一個(gè)文件中,Helm 將嘗試將 CRD 目錄中的所有文件加載到 Kubernetes 中。
需要注意的是 CRD 文件不能模板化,它們必須是純的 YAML 文件。
當(dāng) Helm 安裝一個(gè)新的 chart 的時(shí)候,它將會(huì)安裝 CRDs,然后會(huì)暫停直到 API Server 提供 CRD 為止,然后才開(kāi)始啟動(dòng)模板引擎,渲染其余的 chart 模板,并將其安裝到 Kubernetes 中。由于這個(gè)安裝順序,CRD 信息在 Helm 模板的 .Capabilities 對(duì)象中是可以獲取到的,并且 Helm 模板可能會(huì)創(chuàng)建在 CRD 中聲明的對(duì)象的新實(shí)例。
比如,如果你的呃 chart 在 crds 目錄下面有一個(gè) CronTab 的 CRD,則可以在 templates/ 目錄下面創(chuàng)建 CronTab 類(lèi)型的實(shí)例:
crontabs/ Chart.yaml crds/ crontab.yaml templates/ mycrontab.yaml
crontab.yaml 文件必須包含不帶模板指定的 CRD:
kind: CustomResourceDefinition metadata: name: crontabs.stable.example.com spec: group: stable.example.com versions: - name: v1 served: true storage: true scope: Namespaced names: plural: crontabs singular: crontab kind: CronTab
然后模板 mycrontab.yaml 可以創(chuàng)建一個(gè)新的 CronTab(和平時(shí)使用模板一樣):
apiVersion: stable.example.com kind: CronTab metadata: name: {{ .Values.name }} spec: # ...
在繼續(xù)安裝 templates/ 之前,Helm 會(huì)確保已經(jīng)安裝上了 CronTab 類(lèi)型,并且可以從 Kubernetes API server 上獲得該類(lèi)型。
CRDs 的限制
與 Kubernetes 中的大多數(shù)對(duì)象不同,CRDs 是全局安裝的,所以 Helm 在管理 CRD 的時(shí)候比較謹(jǐn)慎,會(huì)有一些限制:
CRDs 不會(huì)重新安裝,如果 Helm 確定 crds/ 目錄中的 CRD 已經(jīng)存在(無(wú)論版本如何),Helm 都不會(huì)重新安裝或升級(jí)。
CRDs 不會(huì)在升級(jí)或回滾的時(shí)候安裝,只會(huì)在安裝操作的時(shí)候創(chuàng)建 CRDs。
CRDs 不會(huì)被刪除,刪除 CRD 會(huì)自動(dòng)刪除集群中所有 namespace 中的 CRDs 內(nèi)容,所以 Helm 不會(huì)刪除 CRD。
Helm 希望想要升級(jí)或刪除 CRDs 的操作人員可以手動(dòng)來(lái)仔細(xì)地操作。
使用 Helm 管理 Charts
helm 工具有幾個(gè)用于操作 charts 的命令,如下所示。
創(chuàng)建一個(gè)新的 chart 包:
$ helm create mychart Created mychart/
一旦你已經(jīng)編輯了一個(gè) chart 包,Helm 可以將其打包到一個(gè)獨(dú)立文件中:
$ helm package mychart Archived mychart-0.1.-.tgz
你還可以使用 helm 幫助你查找 chart 包的格式要求方面或其他問(wèn)題:
$ helm lint mychart No issues found
Chart 倉(cāng)庫(kù)
chart 倉(cāng)庫(kù)實(shí)際上就是一個(gè) HTTP 服務(wù)器,其中包含一個(gè)或多個(gè)打包的 chart 包,雖然可以使用 helm 來(lái)管理本地 chart 目錄,但是在共享 charts 的時(shí)候,最好的還是使用 chart 倉(cāng)庫(kù)。
可以提供 YAML 文件和 tar文件并可以相應(yīng) GET 請(qǐng)求的任何 HTTP 服務(wù)器都可以作為 chart 倉(cāng)庫(kù)服務(wù)器。倉(cāng)庫(kù)的主要特征是存在一個(gè)名為 index.yaml 的特殊文件,該文件具有倉(cāng)庫(kù)中提供的所有軟件包的列表以及允許檢索和驗(yàn)證這些軟件包的元數(shù)據(jù)。
在客戶(hù)端,可以使用 helm repo 命令來(lái)管理倉(cāng)庫(kù),但是 Helm 不提供用于將 chart 上傳到遠(yuǎn)程 chart 倉(cāng)庫(kù)的工具。
模板開(kāi)發(fā)
內(nèi)置對(duì)象
前面我們介紹了 Helm Chart 的一些基本概念和使用,接下來(lái)我們重點(diǎn)介紹下 Chart 模板的編寫(xiě)。模板會(huì)渲染成 Kubernetes 的資源清單文件,下面我們將來(lái)學(xué)習(xí)下模板的結(jié)構(gòu),如何使用它們,如何編寫(xiě) Go 模板以及如何調(diào)試。
對(duì)象從模板引擎?zhèn)鬟f到模板中,在代碼中可以傳遞對(duì)象,也有一種方法可以在模板宏創(chuàng)建新的對(duì)象,比如 tuple 函數(shù)。對(duì)象可以很簡(jiǎn)單,也可以包含其他對(duì)象或函數(shù),例如,Release 對(duì)象就包含幾個(gè)對(duì)象(比如 Release.Name),F(xiàn)iles 對(duì)象就包含幾個(gè)函數(shù)。
前面提到過(guò)我們可以在模板中使用 {{ .Release.Name }} 獲取 release 的名稱(chēng),Release 是我們可以在模板中訪(fǎng)問(wèn)的幾個(gè)頂級(jí)對(duì)象之一:
Release:該對(duì)象描述了 release 本身的相關(guān)信息,它內(nèi)部有幾個(gè)對(duì)象:
Release.Name:release 名稱(chēng)
Release.Namespace:release 安裝到的命名空間
Release.IsUpgrade:如果當(dāng)前操作是升級(jí)或回滾,則該值為 true
Release.IsInstall:如果當(dāng)前操作是安裝,則將其設(shè)置為 true
Release.Revision:release 的 revision 版本號(hào),在安裝的時(shí)候,值為1,每次升級(jí)或回滾都會(huì)增加
Reelase.Service:渲染當(dāng)前模板的服務(wù),在 Helm 上,實(shí)際上該值始終為 Helm
Values:從 values.yaml 文件和用戶(hù)提供的 values 文件傳遞到模板的 Values 值,默認(rèn)情況下,Values 是空的。
Chart:獲取 Chart.yaml 文件的內(nèi)容,該文件中的任何數(shù)據(jù)都可以訪(fǎng)問(wèn),例如 {{ .Chart.Name }}-{{ .Chart.Version}} 可以渲染成 mychart-0.1.0,該對(duì)象下面可用的字段前面我們已經(jīng)提到過(guò)了。
Files:可以訪(fǎng)問(wèn) chart 中的所有非特殊文件,雖然無(wú)法使用它來(lái)訪(fǎng)問(wèn)模板文件,但是可以來(lái)訪(fǎng)問(wèn) chart 中的其他文件。
Files.Get:用于根據(jù)名稱(chēng)獲取文件(比如 .Files.Get config.ini)
Files.GetBytes:用于以 bytes 數(shù)組而不是字符串的形式來(lái)獲取文件內(nèi)容的函數(shù),這對(duì)于類(lèi)似于圖片之類(lèi)的東西很有用
Files.Glob:用于返回名稱(chēng)于給定的 shell glob 模式匹配的文件列表
Files.Lines:可以逐行讀取文件的函數(shù),對(duì)于遍歷文件中的每行內(nèi)容很有用
Files.AsSecrets:將文件內(nèi)容以 Base64 編碼的字符串返回的函數(shù)
Files.AsConfig:將文件正文作為 YAML 字典返回的函數(shù)
Capabilities:提供了獲取有關(guān) Kubernetes 集群支持功能的信息的對(duì)象
Capabilities.APIVersions:支持的版本集合
Capabilities.APIVersions.Has $version:判斷一個(gè)版本(比如 batch/v1)或資源(比如 apps/v1/Deployment)是否可用
Capabilities.Kube.Version:Kubernetes 的版本
Capabilities.Kube:是 Kubernetes 版本的縮寫(xiě)
Capabilities.Kube.Major:Kubernetes 主版本
Capabilities.Kube.Minor:Kubernetes 的次版本
Template:包含當(dāng)前正在執(zhí)行的模板的相關(guān)信息
Name:當(dāng)前模板的命名空間文件路徑(比如 mychart/templates/mytemplate.yaml)
BaePath:當(dāng)前 chart 的模板目錄的命名空間路徑(比如 mychart/templates)
需要注意的是內(nèi)置的對(duì)象始終是以大寫(xiě)字母開(kāi)頭的,這也是符合 Go 的命名約定的,創(chuàng)建自己的名稱(chēng)的時(shí)候,可以自由使用適合你團(tuán)隊(duì)的約定,一些團(tuán)隊(duì),比如 Kubernetes Charts 團(tuán)隊(duì),選擇僅使用首字母小寫(xiě),以區(qū)分本地名稱(chēng)和內(nèi)置名稱(chēng),這里我們也會(huì)遵循該約定。
Values
前面我們介紹了 Helm 模板提供的內(nèi)置對(duì)象,其中就有一個(gè)內(nèi)置對(duì)象 Values,該對(duì)象提供對(duì)傳遞到 chart 忠的 values 值的訪(fǎng)問(wèn),其內(nèi)容主要有4個(gè)來(lái)源:
chart 文件中的 values.yaml 文件
如果這是子 chart,父 chart 的 values.yaml 文件
用 -f 參數(shù)傳遞給 helm install 或 helm upgrade 的 values 值文件(例如 helm install -f myvals.yaml ./mychart)
用--set傳遞的各個(gè)參數(shù)(例如 helm install --set foo=bar ./mychart)
values.yaml 文件是默認(rèn)值,可以被父 chart 的 values.yaml 文件覆蓋,而后者又可以由用戶(hù)提供的 values 值文件覆蓋,而該文件又可以被--set參數(shù)覆蓋。
values 值文件是純 YAML 文件,我們可以來(lái)編輯 mychart/values.yaml 文件然后編輯 ConfigMap 模板。刪除 values.yaml 中的默認(rèn)設(shè)置后,我們將只設(shè)置一個(gè)參數(shù):
favoriteDrink: coffee
現(xiàn)在我們可以在模板中直接使用它:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favoriteDrink }}
可以看到在最后一行我們將 favoriteDrink 作為 Values 的屬性進(jìn)行訪(fǎng)問(wèn):{{ .Values.favoriteDrink }}。我們可以來(lái)看看是如何渲染的:
$ helm install --generate-name --dry-run --debug ./mychart install.go [debug] Original chart version: "" install.go [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain/content/helm/manifests/mychart NAME: mychart-1575963545 LAST DEPLOYED: Tue Dec 10 1506 2019 NAMESPACE: default STATUS: pending-install REVISION: 1 TEST SUITE: None USER-SUPPLIED VALUES: {} COMPUTED VALUES: favoriteDrink: coffee HOOKS: MANIFEST: --- # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575963545-configmap data: myvalue: "Hello World" drink: coffee
由于在默認(rèn)的 values.yaml 文件中將 favoriteDrink 設(shè)置為了 coffee,所以這就是模板中顯示的值,我們可以通過(guò)在調(diào)用 helm install 的過(guò)程中添加--set參數(shù)來(lái)覆蓋它:
$ helm install --generate-name --dry-run --debug --set favoriteDrink=slurm ./mychart install.go [debug] Original chart version: "" install.go [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain/content/helm/manifests/mychart NAME: mychart-1575963760 LAST DEPLOYED: Tue Dec 10 1543 2019 NAMESPACE: default STATUS: pending-install REVISION: 1 TEST SUITE: None USER-SUPPLIED VALUES: favoriteDrink: slurm COMPUTED VALUES: favoriteDrink: slurm HOOKS: MANIFEST: --- # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575963760-configmap data: myvalue: "Hello World" drink: slurm
因?yàn)?-set的優(yōu)先級(jí)高于默認(rèn)的 values.yaml 文件,所以我們的模板會(huì)生成 drink: slurm。Values 值文件也可以包含更多結(jié)構(gòu)化的內(nèi)容,例如我們可以在 values.yaml 文件中創(chuàng)建一個(gè) favorite 的部分,然后在其中添加幾個(gè) keys:
favorite: drink: coffee food: pizza
現(xiàn)在我們?cè)偃バ薷南挛覀兊哪0澹?/p>
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink }} food: {{ .Values.favorite.food }}
雖然我們可以通過(guò)這種方式來(lái)構(gòu)造數(shù)據(jù),但是還是建議你將 values 樹(shù)保持更淺,這樣在使用的時(shí)候更加簡(jiǎn)單。當(dāng)我們考慮為子 chart 分配 values 值的時(shí)候,我們就可以看到如何使用樹(shù)形結(jié)構(gòu)來(lái)命名 values 值了。
刪除默認(rèn) KEY
如果你需要從默認(rèn)值中刪除 key,則可以將該 key 的值覆蓋為 null,在這種情況下,Helm 將從覆蓋的 values 中刪除該 key。例如,在 Drupal chart 中配置一個(gè) liveness 探針:
livenessProbe: httpGet: path: /user/login port: http initialDelaySeconds: 120
如果你想使用--set livenessProbe.exec.command=[cat, docroot/CHANGELOG.txt]將 livenessProbe 的處理程序覆蓋為 exec 而不是 httpGet,則 Helm 會(huì)將默認(rèn)鍵和覆蓋鍵合并在一起,如下所示:
livenessProbe: httpGet: path: /user/login port: http exec: command: - cat - docroot/CHANGELOG.txt initialDelaySeconds: 120
但是,這樣卻有一個(gè)問(wèn)題,因?yàn)槟悴荒苈暶鞫鄠€(gè) livenessProbe 處理程序,為了解決這個(gè)問(wèn)題,你可以讓 Helm 通過(guò)將 livenessProbe.httpGet 設(shè)置為 null 來(lái)刪除它:
$ helm install stable/drupal --set image=my-registry/drupal:0.1.0 --set livenessProbe.exec.command=[cat, docroot/CHANGELOG.txt] --set livenessProbe.httpGet=null
到這里我們已經(jīng)了解到了幾個(gè)內(nèi)置對(duì)象,并利用它們將信息注入到了模板中,現(xiàn)在我們來(lái)看看模板引擎的另外方面:函數(shù)和管道。
函數(shù)與管道
在我們已經(jīng)了解了如何將信息加入到模板中,但是這些信息都是直接原樣的放置過(guò)去的,有時(shí)候,我們希望以一種對(duì)我們更有用的方式來(lái)轉(zhuǎn)換提供的數(shù)據(jù)。
下面讓我們從一個(gè)最佳實(shí)踐開(kāi)始:將.Values對(duì)象中的字符串注入模板時(shí),我們應(yīng)該引用這些字符串,我們可以通過(guò)在 template 指令中調(diào)用 quote 函數(shù)來(lái)實(shí)現(xiàn),比如:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ quote .Values.favorite.drink }} food: {{ quote .Values.favorite.food }}
模板函數(shù)遵循的語(yǔ)法規(guī)則是 functionName arg1 arg2...,在上面的代碼片段中,quote .Values.favorite.drink 會(huì)調(diào)用 quote 函數(shù)并傳遞一個(gè)單個(gè)參數(shù)。
Helm 有60多種可用的函數(shù),其中一些是由 Go 模板語(yǔ)言本身定義的,其他大多數(shù)都是 Sprig 模板庫(kù)提供的,接下來(lái)我們會(huì)通過(guò)部分示例來(lái)逐步介紹其中的一些功能函數(shù)。
當(dāng)我們談?wù)?Helm 模板語(yǔ)言 的時(shí)候,就好像是特定于 Helm 一樣,但實(shí)際上它是 Go 模板語(yǔ)言加上一些額外的函數(shù)以及各種封裝程序的組合,以將某些對(duì)象暴露給模板。當(dāng)我們需要學(xué)習(xí)模板的時(shí)候,Go 模板上有許多資源會(huì)對(duì)我們有所幫助的。
管道
模板語(yǔ)言有一個(gè)強(qiáng)大的功能就是管道(Pipeline)概念,管道利用 UNIX 的概念,將一系列模板命令鏈接在一起,一起對(duì)外提供服務(wù),換句話(huà)說(shuō),管道是按順序完成多項(xiàng)工作的有效方式,我們來(lái)使用管道重寫(xiě)上面的示例模板:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | quote }} food: {{ .Values.favorite.food | quote }}
在這里我們沒(méi)有調(diào)用 quote ARGUMENT 函數(shù),而是顛倒了下順序,我們使用管道符(|)將參數(shù)發(fā)送給函數(shù):.Values.favorite.drink | quote,使用管道,我們可以將多個(gè)功能鏈接在一起:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | quote }} food: {{ .Values.favorite.food | upper | quote }}
管道順序: 反轉(zhuǎn)順序是模板中常見(jiàn)的做法,我們會(huì)看到 .val | quote 比 quote .val 用法更多,雖然兩種方法都是可以的。
最后,模板渲染后,會(huì)產(chǎn)生如下所示的結(jié)果:
$ helm install --generate-name --dry-run --debug ./mychart install.go [debug] Original chart version: "" install.go [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain/content/helm/manifests/mychart NAME: mychart-1575966483 LAST DEPLOYED: Tue Dec 10 1604 2019 NAMESPACE: default STATUS: pending-install REVISION: 1 TEST SUITE: None USER-SUPPLIED VALUES: {} COMPUTED VALUES: favorite: drink: coffee food: pizza favoriteDrink: coffee HOOKS: MANIFEST: --- # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575966483-configmap data: myvalue: "Hello World" drink: "coffee" food: "PIZZA"
我們可以看到 values 中的 pizza 值已經(jīng)被轉(zhuǎn)換成了 "PIZZA"。當(dāng)這樣傳遞參數(shù)的時(shí)候,第一個(gè)求值結(jié)果(.Values.favorite.drink)會(huì)作為一個(gè)參數(shù)發(fā)送給函數(shù),我們可以修改上面的 drink 示例,用一個(gè)帶有兩個(gè)參數(shù)的函數(shù)進(jìn)行說(shuō)明:repeat COUNT STRING。
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | repeat 5 | quote }} food: {{ .Values.favorite.food | upper | quote }}
repeat 函數(shù)將重復(fù)字符串給定的次數(shù),渲染后我們可以得到如下的輸出結(jié)果:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575966939-configmap data: myvalue: "Hello World" drink: "coffeecoffeecoffeecoffeecoffee" food: "PIZZA"
default 函數(shù)
在模板中經(jīng)常會(huì)使用到的一個(gè)函數(shù)是 default 函數(shù):default DEFAULT_VALUE GIVEN_VALUE,該函數(shù)允許你在模板內(nèi)部指定默認(rèn)值,我們來(lái)修改上面示例中的模板:
food: {{ .Values.favorite.food | default "rice" | upper | quote }}
正常運(yùn)行,我們還是可以得到 values.yaml 文件中定義的 pizza:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575966939-configmap data: myvalue: "Hello World" drink: "coffeecoffeecoffeecoffeecoffee" food: "PIZZA"
現(xiàn)在我們從 values.yaml 文件中移除 food 的定義:
favorite: drink: coffee # food: pizza
現(xiàn)在我們重新運(yùn)行helm install --generate-name --dry-run --debug ./mychart將渲染成如下的 YAML 文件:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575967394-configmap data: myvalue: "Hello World" drink: "coffeecoffeecoffeecoffeecoffee" food: "RICE"
在一個(gè)真實(shí)的 chart 模板中,所有的靜態(tài)默認(rèn)值都應(yīng)位于 values.yaml 文件中,并且不應(yīng)該重復(fù)使用 default 函數(shù),但是,默認(rèn)命令非常適合計(jì)算不能在 values.yaml 文件中聲明的 values 值,例如:
food: {{ .Values.favorite.food | default (printf "%s-rice" (include "fullname" .)) }}
不過(guò)在有些地方,if 條件語(yǔ)句可能比 default 函數(shù)更合適,我們會(huì)在后面了解到。
模板函數(shù)和管道是將數(shù)據(jù)轉(zhuǎn)換后然后將其插入到 YAML 文件中的一種強(qiáng)大方法,但是有的時(shí)候有必要添加一些模板邏輯,這些邏輯比僅僅插入字符串要復(fù)雜得多,下面我們將來(lái)了解模板語(yǔ)言中提供的控制流程。
運(yùn)算符函數(shù)
另外需要注意的是在模板中,運(yùn)算符(eq、ne、lt、gt、and、or 等等)均實(shí)現(xiàn)為函數(shù),在管道中,運(yùn)算符可以用括號(hào)()進(jìn)行分割。
接下來(lái)我們可以去了解控制流程條件語(yǔ)句、循環(huán)和作用域修飾符的使用。
流程控制
控制流程為模板作者提供了控制模板生成流程的功能,Helm 的模板語(yǔ)言提供了以下一些流程控制:
if/else 條件語(yǔ)句
with 指定一個(gè)作用域范圍
range 提供類(lèi)似于 for each 這樣的循環(huán)樣式
除此之外,還提供了一些聲明和使用命名模板的操作:
define 在模板內(nèi)部聲明一個(gè)新的命名模板
template 導(dǎo)入一個(gè)命名模板
block 聲明了一種特殊的可填充模板區(qū)域。
這里我們先來(lái)了解 if、with、range 語(yǔ)句的使用,其他將在后面的命名模板部分介紹。
if/else
首先我們先來(lái)了解下有條件地在模板中包含一個(gè)文本區(qū)域,就是 if/else ,這個(gè)條件判斷的基本結(jié)構(gòu)如下所示:
{{ if PIPELINE }} # Do something {{ else if OTHER PIPELINE }} # Do something else {{ else }} # Default case {{ end }}
可以看到我們這里判斷的是管道而不是一個(gè) values 值,這是因?yàn)榭刂平Y(jié)構(gòu)可以執(zhí)行整個(gè)管道,而不僅僅是判斷值。如果值為以下的一些內(nèi)容,則將管道判斷為 false:
布爾 false
數(shù)字零
一個(gè)空字符串
nil(empty 或者 null)
一個(gè)空集合(map、slice、tuple、dict、array)
在其他條件下,條件都為真。
現(xiàn)在我們?cè)谏厦娴氖纠0?ConfigMap 中添加一個(gè)簡(jiǎn)單的條件,如果 drink 設(shè)置為 coffee,我們就添加另外一個(gè)設(shè)置:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | default "tea" | quote }} food: {{ .Values.favorite.food | upper | quote }} {{ if eq .Values.favorite.drink "coffee" }}mug: true{{ end }}
我們把 values.yaml 文件內(nèi)容設(shè)置成下面的樣子:
favorite: # drink: coffee food: pizza
由于我們注釋掉了 drink: coffee,所以渲染后輸出不會(huì)包含 mug: true 的標(biāo)志,但是如果我們把注釋取消掉,則應(yīng)該輸出如下所示的內(nèi)容:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575970308-configmap data: myvalue: "Hello World" drink: "coffee" food: "PIZZA" mug: true
這是因?yàn)樯厦婺0逯形覀兲砑恿?if eq .Values.favorite.drink "coffee" 這樣的條件判斷,相當(dāng)于是判斷.Values.favorite.drink值是否等于 "coffee",如果相等則渲染 mug: true。
空格控制
還有一個(gè)非常重要的功能點(diǎn)就是關(guān)于空格的控制,因?yàn)榭崭駥?duì)于 YAML 文件非常重要的,不是說(shuō)任意縮進(jìn)就可以,依然還是以前面的例子為例,我們來(lái)格式化下模板格式以更易于閱讀:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | default "tea" | quote }} food: {{ .Values.favorite.food | upper | quote }} {{ if eq .Values.favorite.drink "coffee" }} mug: true {{ end }}
現(xiàn)在我們的模板看上去更易于閱讀了,但是我們通過(guò)模板引擎來(lái)渲染下,卻會(huì)得到如下的錯(cuò)誤信息:
$ helm install --generate-name --dry-run --debug ./mychart install.go:148: [debug] Original chart version: "" install.go:165: [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain/content/helm/manifests/mychart Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: did not find expected key
這是因?yàn)槲覀冊(cè)谀0逯刑砑恿丝崭?,生成了不正確的 YAML 文件:
apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575970308-configmap data: myvalue: "Hello World" drink: "coffee" food: "PIZZA" mug: true
我們可以看到 mug: true 的縮進(jìn)是有問(wèn)題的,不符合 YAML 文件格式,現(xiàn)在我們講縮進(jìn)去掉試看看:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | default "tea" | quote }} food: {{ .Values.favorite.food | upper | quote }} {{ if eq .Values.favorite.drink "coffee" }} mug: true {{ end }}
重新渲染模板,然后可以發(fā)現(xiàn)已經(jīng)可以正常通過(guò)了,但是渲染出來(lái)的 YAML 文件格式看上去還是有點(diǎn)奇怪:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575971172-configmap data: myvalue: "Hello World" drink: "coffee" food: "PIZZA" mug: true
我們可以看到得到的 YAML 文件中多了一些空行,這是因?yàn)槟0逡驿秩镜臅r(shí)候它會(huì)刪除 {{ 和 }} 之間的內(nèi)容,但是會(huì)完全保留其余的空格。我們知道在 YAML 文件中空格是有意義的,所以管理空格就變得非常重要了,不過(guò) Helm 模板也提供了一些工具來(lái)幫助我們管理空格。
首先可以使用特殊字符修改模板聲明的花括號(hào)語(yǔ)法,以告訴模板引擎去掉空格。{{-添加了破折號(hào)和空格表示應(yīng)將左邊的空格移除,-}}表示將右邊的空格移除,另外也需要注意的是,換行符也是空格。
需要注意的時(shí)候要確保-和指令的其余部分之間要有空格,{{- 3 }}表示刪除左邊的空格并打印3,但是{{-3 }}表示打印-3。
使用這個(gè)語(yǔ)法,我們可以修改上面的模板來(lái)移除多余的空行:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | default "tea" | quote }} food: {{ .Values.favorite.food | upper | quote }} {{- if eq .Values.favorite.drink "coffee" }} mug: true {{- end }}
渲染后可以看到空行被移除掉了:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575972373-configmap data: myvalue: "Hello World" drink: "coffee" food: "PIZZA" mug: true
為了更加清楚地說(shuō)明這個(gè)問(wèn)題,我們用*來(lái)代替將要?jiǎng)h除的每個(gè)空格,行尾的*表示被刪除的換行符:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | default "tea" | quote }} food: {{ .Values.favorite.food | upper | quote }}* **{{- if eq .Values.favorite.drink "coffee" }} mug: true* **{{- end }}
所以我們這里用{{-表示的就是刪除本行開(kāi)頭的兩個(gè)空格以及上一行的換行符,這樣是不是就將空行都刪除了啊。
在使用移除空格的時(shí)候還需要小心,比如下面的操作:
food: {{ .Values.favorite.food | upper | quote }} {{- if eq .Values.favorite.drink "coffee" -}} mug: true {{- end -}}
我們依然還是可以用*來(lái)代替空格進(jìn)行分析,如下所示:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" drink: {{ .Values.favorite.drink | default "tea" | quote }} food: {{ .Values.favorite.food | upper | quote }}* **{{- if eq .Values.favorite.drink "coffee" -}}* mug: true* **{{- end -}}
第一個(gè){{-會(huì)刪除前面的空格和前面的換行符,然后后面的-}}會(huì)刪除當(dāng)前行的換行符,這樣就會(huì)把 mug: true 移動(dòng)到 food: "PIZZA" 后面去了,最終渲染過(guò)后就會(huì)變成:food: "PIZZA"mug: true,因?yàn)樵趦蓚?cè)都去掉換行符。
有關(guān)模板中空格控制的詳細(xì)信息,可以查看 Go 模板官方文檔介紹。
不過(guò)有時(shí)候告訴模板系統(tǒng)如何縮進(jìn)比起去控制模板指令的間距更加容易,所以,有時(shí)候你會(huì)發(fā)現(xiàn)縮進(jìn)函數(shù)({{ indent 2 "mug: true" }})更有用。
使用 with 修改作用域
接下來(lái)需要了解的是 with 操作,它可以控制變量的作用域,然后重新用 . 調(diào)用就表示對(duì)當(dāng)前作用域的引用,所以,.Values是告訴模板引擎在當(dāng)前作用域下內(nèi)查找 Values 對(duì)象。
with 語(yǔ)句的語(yǔ)法和 if 語(yǔ)句比較類(lèi)似:
{{ with PIPELINE }} # 限制范圍 {{ end }}
范圍可以更改,可以讓你將當(dāng)前范圍 . 設(shè)置為特定的對(duì)象,例如,我們一直在使用Values.favorites,讓我們重寫(xiě)下模板文件 ConfigMap 來(lái)更改.的范圍指向Values.favorites:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" {{- with .Values.favorite }} drink: {{ .drink | default "tea" | quote }} food: {{ .food | upper | quote }} {{- end }}
我們這里將前面練習(xí)的 if 條件語(yǔ)句刪除了,在模板中我們添加了一個(gè){{- with .Values.favorite }}的語(yǔ)句,意思就是說(shuō)在 with 語(yǔ)句的作用范圍內(nèi)可以用.表示.Values.favorite了,所以我們可以引用.drink和.food了,但是在{{ end }}之后就會(huì)重置為之前的作用域了。
不過(guò)需要注意得是,在受限的作用域范圍內(nèi),你無(wú)法從父級(jí)范圍訪(fǎng)問(wèn)到其他對(duì)象,比如,下面得模板會(huì)失?。?/p>
{{- with .Values.favorite }} drink: {{ .drink | default "tea" | quote }} food: {{ .food | upper | quote }} release: {{ .Release.Name }} {{- end }}
因?yàn)?Release.Name 并不在 . 的限制范圍內(nèi),所以會(huì)產(chǎn)生錯(cuò)誤,但是,如果我們交換最后兩行,則就可以正常工作了,因?yàn)閧{ end }}之后會(huì)重置作用域。
{{- with .Values.favorite }} drink: {{ .drink | default "tea" | quote }} food: {{ .food | upper | quote }} {{- end }} release: {{ .Release.Name }}
下面我先來(lái)了解下 range,然后我們?cè)偃チ私庀履0遄兞?,它可以為上面得這個(gè)范圍問(wèn)題提供一種解決方案。
range 循環(huán)操作
我們知道許多編程語(yǔ)言都支持使用 for 循環(huán)、foreach 循環(huán)或者類(lèi)似功能機(jī)制進(jìn)行循環(huán)迭代,在 Helm 得模板語(yǔ)言中,迭代集合得方法是使用 range 運(yùn)算符。
比如首先我們?cè)?values.yaml 文件中添加一份 pizza 餡料列表:
favorite: drink: coffee food: pizza pizzaToppings: - mushrooms - cheese - peppers - onions
現(xiàn)在我們有了 pizzaToppings 列表(在模板中稱(chēng)為切片),我們可以來(lái)修改下模板將列表打印到 ConfigMap 中:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" {{- with .Values.favorite }} drink: {{ .drink | default "tea" | quote }} food: {{ .food | upper | quote }} {{- end }} toppings: |- {{- range .Values.pizzaToppings }} - {{ . | title | quote }} {{- end }}
我們仔細(xì)觀察下模板中的 toppings: 列表,range 函數(shù)將遍歷 Values.pizzaToppings 列表,我們看到里面使用了一個(gè) .,類(lèi)似于上面我們用 with 設(shè)置范圍一樣,運(yùn)算符也是這樣的,每次循環(huán),. 都會(huì)被設(shè)置為當(dāng)前的 pizzaTopping,也就是說(shuō)第一次設(shè)置為mushrooms,第二次迭代設(shè)置為cheese,依次類(lèi)推。
我們可以直接傳遞 . 這個(gè)值到管道上,所以我們這里 {{ . | title | quote }} 就相當(dāng)于發(fā)送 . 給 title(標(biāo)題大小寫(xiě)函數(shù))函數(shù),然后發(fā)送給 quote 函數(shù),我們渲染這個(gè)模板,會(huì)輸出如下的內(nèi)容:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575975849-configmap data: myvalue: "Hello World" drink: "coffee" food: "PIZZA" toppings: |- - "Mushrooms" - "Cheese" - "Peppers" - "Onions"
在上面模板中,我們做了一些小小的特殊處理,toppings: |- 行表示聲明一個(gè)多行字符串,所以其實(shí)我們的 toppings 列表不是一個(gè) YAML 列表,而是一個(gè)比較大的字符串,這是因?yàn)?ConfigMap 中的數(shù)據(jù)由 key/value 對(duì)組成,所有 key 和 value 都是簡(jiǎn)單的字符串,要了解為什么是這樣的,可以查看 Kubernetes ConfigMap 文檔,不過(guò)這個(gè)細(xì)節(jié)對(duì)我們這里不重要。
多行字符串可以使用 | 保留換行符,也可以使用 > 折疊換行,如: this: | Foo Bar that: > Foo Bar 對(duì)應(yīng)的意思就是:{this: 'Foo Bar ', that: 'Foo Bar '} + 表示保留文字塊末尾的換行,- 表示刪除字符串末尾的換行,如: s1: | Foo s2: |+ Foo s3: |- Foo 對(duì)應(yīng)的意思就是:{s1: 'Foo ', s2: 'Foo ', s3: 'Foo'}
有時(shí)候,在模板中快速創(chuàng)建一個(gè)列表,然后遍歷該列表很有用,Helm 模板具有簡(jiǎn)化該功能的函數(shù):tuple。元組是固定大小的列表集合,但是具有任意數(shù)據(jù)類(lèi)型,下面是元組的大概使用方法:
sizes: |- {{- range tuple "small" "medium" "large" }} - {{ . }} {{- end }}
上面的模板最終會(huì)被渲染成如下的 YAML:
sizes: |- - small - medium - large
除了列表和元組之外,range 還可以用于遍歷字典,我們?cè)谙乱还?jié)介紹模板變量的時(shí)候再來(lái)了解這個(gè)用法吧。
變量
有了函數(shù)、管道、對(duì)象以及控制結(jié)構(gòu),我們可以想象下大多數(shù)編程語(yǔ)言中更基本的思想之一:變量。在模板中,變量的使用頻率較低,但是,我們還是可以使用他們來(lái)簡(jiǎn)化代碼,以及更好地使用 with 和 range。
在前面的示例中,我們知道下面的模板渲染會(huì)出錯(cuò):
{{- with .Values.favorite }} drink: {{ .drink | default "tea" | quote }} food: {{ .food | upper | quote }} release: {{ .Release.Name }} {{- end }}
因?yàn)?Release.Name 不在 with 語(yǔ)句塊限制的范圍之內(nèi),解決作用域問(wèn)題的一種方法是將對(duì)象分配給在不考慮當(dāng)前作用域情況下訪(fǎng)問(wèn)的變量。
在 Helm 模板中,變量是對(duì)另外一個(gè)對(duì)象的命名引用。它遵循 $name 格式,變量使用特殊的賦值運(yùn)算符進(jìn)行賦值:=,我們可以修改上面的模板,為 Release.Name 聲明一個(gè)變量:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" {{- $relname := .Release.Name -}} {{- with .Values.favorite }} drink: {{ .drink | default "tea" | quote }} food: {{ .food | upper | quote }} release: {{ $relname }} {{- end }}
注意在 with 語(yǔ)句之前,我們先分配了$relname := .Release.Name,然后在 with 語(yǔ)句塊中,$relname變量仍然表示 release 的名稱(chēng),我們渲染該模板,可以得到如下的正確結(jié)果:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575982655-configmap data: myvalue: "Hello World" drink: "coffee" food: "PIZZA" release: mychart-1575982655
變量在 range 循環(huán)里面非常有用,它們可以用于類(lèi)似于列表的對(duì)象來(lái)捕獲索引和 value 值:
toppings: |- {{- range $index, $topping := .Values.pizzaToppings }} {{ $index }}: {{ $topping }} {{- end }}
注意 range 在前面,然后是變量,然后是賦值運(yùn)算符,然后才是列表,這會(huì)將整數(shù)索引(從0開(kāi)始)分配給$index,并將 value 值分配給 $topping,上面的內(nèi)容會(huì)被渲染成如下內(nèi)容:
toppings: |- 0: mushrooms 1: cheese 2: peppers 3: onions
對(duì)于同時(shí)具有 key 和 value 的數(shù)據(jù)結(jié)構(gòu),我們也可以使用 range 來(lái)獲得 key、value 的值,比如,我們可以像這樣循環(huán)遍歷.Values.favorite:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" {{- range $key, $val := .Values.favorite }} {{ $key }}: {{ $val | quote }} {{- end }}
在第一次迭代中,$key是 drink,$val是 coffee,在第二次迭代中,$key是 food,$val是 pizza。運(yùn)行上面的命令將生成下面的內(nèi)容:
# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1575983119-configmap data: myvalue: "Hello World" drink: "coffee" food: "pizza"
一般來(lái)說(shuō)變量不是全局的,它們的作用域是聲明它們的塊區(qū)域,之前,我們?cè)谀0宓捻攲臃峙淞?relname,該變量將在整個(gè)模板的范圍內(nèi),但是在我們上面的示例中,$key和$val作用域只在{{ range... }}{{ end }}區(qū)域內(nèi)。
但是,有一個(gè)始終是全局變量的$始終指向頂層根上下文,當(dāng)我們?cè)?range 循環(huán)內(nèi)需要知道 chart 包的 release 名稱(chēng)的時(shí)候,該功能就非常有用了,比如下面的模板文件:
{{- range .Values.tlsSecrets }} apiVersion: v1 kind: Secret metadata: name: {{ .name }} labels: # helm 模板經(jīng)常使用 `.`,但是這里是無(wú)效的,用 `$` 是可以生效的。 app.kubernetes.io/name: {{ template "fullname" $ }} # 這里不能引用 `.Chart.Name`,但是可用使用 `$.Chart.Name` helm.sh/chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}" app.kubernetes.io/instance: "{{ $.Release.Name }}" # 值來(lái)自于 Chart.yaml 文件中的 appVersion app.kubernetes.io/version: "{{ $.Chart.AppVersion }}" app.kubernetes.io/managed-by: "{{ $.Release.Service }}" type: kubernetes.io/tls data: tls.crt: {{ .certificate }} tls.key: {{ .key }} --- {{- end }}
到現(xiàn)在為止,我們只研究了在一個(gè)文件中聲明的一個(gè)模板,但是,Helm 模板語(yǔ)言的強(qiáng)大功能之一是它能夠聲明多個(gè)模板并將其一起使用。我們將在下面的章節(jié)中來(lái)討論這一點(diǎn)。
命令模板
前面我們都是只操作的一個(gè)模板,現(xiàn)在我們來(lái)嘗試使用多個(gè)模板文件。在本節(jié)中,我們可以了解到如何在一個(gè)文件中定義命名模板,然后在其他地方使用它們。命名模板(有時(shí)也叫子模板)只是在文件內(nèi)部定義的有名稱(chēng)的模板。主要有兩種創(chuàng)建方式以及幾種不同的使用方式。 當(dāng)使用命名模板的時(shí)候有幾個(gè)重要細(xì)節(jié):模板名稱(chēng)是全局的,如果聲明兩個(gè)具有相同名稱(chēng)的模板,則會(huì)使用最后被加載的模板。由于子 chart 中的模板是與頂級(jí)模板一起編譯的,所以需要謹(jǐn)慎命名。 一種流行的命名約定是在每個(gè)定義的模板前添加 chart 名稱(chēng):{{ define "mychart.labels" }},通過(guò)使用特定的 chart 名作為前綴,我們可以避免由于兩個(gè)不同的 chart 實(shí)現(xiàn)了相同名稱(chēng)的模板而引起的沖突。 partials 和 _ 文件? 到目前為止,我們只使用了一個(gè)模板文件,但是 Helm 的模板語(yǔ)言允許我們創(chuàng)建命名的嵌入式模板,可以在其他位置進(jìn)行訪(fǎng)問(wèn)。在編寫(xiě)這些模板之前,有一些值得一提的命名約定: templates/ 中的大多數(shù)文件都被視為 Kubernetes 資源清單文件(NOTES.txt 除外) 以 _ 開(kāi)頭命名的文件也不會(huì)被當(dāng)做 Kubernetes 資源清單文件 下劃線(xiàn)開(kāi)頭的文件不會(huì)被當(dāng)做資源清單之外,還可以被其他 chart 模板調(diào)用 _ 開(kāi)頭的這些文件其實(shí)就是 Helm 中的 partials 文件,所以其實(shí)我們完全可以將命名模板定義在這些 partials 文件中,默認(rèn)就是 _helpers.tpl 文件,其實(shí)在前面我們創(chuàng)建的 mychart 包中也可以找到這個(gè)文件。 define 和 template? define 關(guān)鍵字可以讓我們?cè)谀0逦募袆?chuàng)建命名模板,它的語(yǔ)法如下所示: {{ define "MY.NAME" }} # 模板內(nèi)容區(qū)域 {{ end }} 比如我們可以定義一個(gè)模板來(lái)封裝下 Kubernetes 的 labels 標(biāo)簽: {{- define "mychart.labels" }} labels: generator: helm date: {{ now | htmlDate }} {{- end }} 現(xiàn)在我們可以將該模板嵌入到前面的 ConfigMap 模板中,然后將其包含在模板中: {{- define "mychart.labels" }} labels: generator: helm date: {{ now | htmlDate }} {{- end }} apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap {{- template "mychart.labels" }} data: myvalue: "Hello World" {{- range $key, $val := .Values.favorite }} {{ $key }}: {{ $val | quote }} {{- end }} 當(dāng)模板引擎讀取這個(gè)文件的時(shí)候,它會(huì)存儲(chǔ) mychart.labels 的引用,直到該模板被調(diào)用,然后會(huì)內(nèi)聯(lián)渲染該模板。我們渲染這個(gè)模板可以都到如下所示的結(jié)果(記得先刪掉默認(rèn)生成的 _helpers.tpl 文件): # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1576034036-configmap labels: generator: helm date: 2019-12-11 data: myvalue: "Hello World" drink: "coffee" food: "pizza" 一般來(lái)說(shuō),Helm 中約定將這些模板統(tǒng)一放到一個(gè) partials 文件中,通常就是 _helpers.tpl 文件中,我們將上面的命名模板移動(dòng)到該文件(templates/_helpers.tpl)中去: {{/* 生成基本的 Label 標(biāo)簽 */}} {{- define "mychart.labels" }} labels: generator: helm date: {{ now | htmlDate }} {{- end }} 一般來(lái)說(shuō),我們也會(huì)用一個(gè)簡(jiǎn)單的塊({{/*...*/}})來(lái)注釋這個(gè)命名模板的作用。 現(xiàn)在雖然我們把命名模板放到了 _helpers.tpl 文件中,但是我們?cè)?configmap.yaml 模板中還是可以訪(fǎng)問(wèn),因?yàn)槊0迨侨值模? apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap {{- template "mychart.labels" }} data: myvalue: "Hello World" {{- range $key, $val := .Values.favorite }} {{ $key }}: {{ $val | quote }} {{- end }} 因?yàn)樯厦嫖覀兲岬竭^(guò)命名模板是全局的,我們可以再渲染下上面的模板可以得到正確的結(jié)果。 設(shè)置模板范圍? 上面我們定義的模板中,還沒(méi)有使用到任何對(duì)象,只使用了函數(shù),現(xiàn)在我們來(lái)修改下定義的命名模板,包含 chart 的名稱(chēng)和版本: {{/* 生成基本的 Label 標(biāo)簽 */}} {{- define "mychart.labels" }} labels: generator: helm date: {{ now | htmlDate }} chart: {{ .Chart.Name }} version: {{ .Chart.Version }} {{- end }} 現(xiàn)在我們來(lái)渲染下模板,會(huì)出現(xiàn)下面的錯(cuò)誤: $ helm install --generate-name --dry-run --debug ./my chart install.go [debug] Original chart version: "" install.go [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/cour se/k8strain/content/helm/manifests/mychart Error: unable to build kubernetes objects from release manifest: error validati ng "": error validating data: [unknown object type "nil" in ConfigMap.metadata. labels.chart, unknown object type "nil" in ConfigMap.metadata.labels.version] helm.go [debug] error validating "": error validating data: [unknown object type "nil" in ConfigMap.metadata.labels.chart, unknown object type "nil" in Co nfigMap.metadata.labels.version] ...... 我們可以看到提示 labels.chart 為 nil,這是因?yàn)槲覀兪褂玫?.Chart.Name 不在定義的這個(gè)模板的作用域范圍內(nèi),當(dāng)渲染命名模板(使用 define 定義)的時(shí)候,它將接收模板調(diào)用傳遞的作用域。在我們這個(gè)示例中,我們是這樣引用這個(gè)模板的: {{- template "mychart.labels" }} 沒(méi)有傳入任何作用域,所以在模板內(nèi)我們無(wú)法訪(fǎng)問(wèn) . 中的任何內(nèi)容,當(dāng)然要解決很簡(jiǎn)單,我們只需要把作用域范圍傳遞給模板即可: apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap {{- template "mychart.labels" . }} ...... 我們這里在使用 template 調(diào)用模板的時(shí)候傳遞了 .,我們可以很容易傳遞 .Values 或者 .Values.favorite 或者我們想要的任何范圍,但是這里我們想要的是頂級(jí)作用域,所以我們傳遞的是 .。 現(xiàn)在我們?cè)賮?lái)重新渲染我們的模板,可以得到如下所示的結(jié)果: # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1576035668-configmap labels: generator: helm date: 2019-12-11 chart: mychart version: 0.1.0 data: myvalue: "Hello World" drink: "coffee" food: "pizza" 現(xiàn)在 {{ .Chart.Name }} 解析為了 mychart,而 {{ .Chart.Version }} 解析為了 0.1.0。 include 函數(shù)? 假設(shè)我們定義了一個(gè)如下所示的簡(jiǎn)單模板: {{- define "mychart.app" -}} app_name: {{ .Chart.Name }} app_version: "{{ .Chart.Version }}" {{- end -}} 現(xiàn)在我們想把上面的內(nèi)容插入到模板的 labels 部分,在 data 部分也想要這個(gè)內(nèi)容: apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap labels: {{ template "mychart.app" . }} data: myvalue: "Hello World" {{- range $key, $val := .Values.favorite }} {{ $key }}: {{ $val | quote }} {{- end }} {{ template "mychart.app" . }} 但是我們直接渲染上面的模板還是會(huì)有錯(cuò)誤: $ helm install --generate-name --dry-run --debug ./my chart install.go [debug] Original chart version: "" install.go [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/cour se/k8strain/content/helm/manifests/mychart Error: unable to build kubernetes objects from release manifest: error validati ng "": error validating data: [ValidationError(ConfigMap): unknown field "app_n ame" in io.k8s.api.core.v1.ConfigMap, ValidationError(ConfigMap): unknown field "app_version" in io.k8s.api.core.v1.ConfigMap] helm.go [debug] error validating "": error validating data: [ValidationErro r(ConfigMap): unknown field "app_name" in io.k8s.api.core.v1.ConfigMap, Validat ionError(ConfigMap): unknown field "app_version" in io.k8s.api.core.v1.ConfigMap] ...... 因?yàn)?template 只是一個(gè)動(dòng)作,而不是一個(gè)函數(shù),所以無(wú)法將模板調(diào)用的輸出傳遞給其他函數(shù),只是內(nèi)聯(lián)插入,相當(dāng)于渲染的結(jié)果是這樣的: apiVersion: v1 kind: ConfigMap metadata: name: measly-whippet-configmap labels: app_name: mychart app_version: "0.1.0+1478129847" data: myvalue: "Hello World" drink: "coffee" food: "pizza" app_name: mychart app_version: "0.1.0+1478129847" 很明顯上面的 YAML 文件是不符合 ConfigMap 資源對(duì)象的格式要求的,所以報(bào)錯(cuò)了。為解決這個(gè)問(wèn)題,Helm 提供了代替 template 的函數(shù) include,可以將模板的內(nèi)容導(dǎo)入到當(dāng)前的管道中,這樣就可以在管道中傳遞給其他函數(shù)進(jìn)行處理了,如下所示: apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap labels: {{ include "mychart.app" . | indent 4 }} data: myvalue: "Hello World" {{- range $key, $val := .Values.favorite }} {{ $key }}: {{ $val | quote }} {{- end }} {{ include "mychart.app" . | indent 2 }} 現(xiàn)在我們重新渲染就可以得到正確的結(jié)果了,這是因?yàn)槲覀冇?include 函數(shù)得到模板內(nèi)容后通過(guò)管道傳給了后面的 indent 函數(shù)來(lái)保證了縮進(jìn): Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1576036671-configmap labels: app_name: mychart app_version: "0.1.0" data: myvalue: "Hello World" drink: "coffee" food: "pizza" app_name: mychart app_version: "0.1.0" 建議 在 Helm 模板中最好使用 include 而不是 template,這樣可以更好地處理 YAML 文檔的輸出格式。 有時(shí)候如果我們只想導(dǎo)入內(nèi)容而不是模板,這個(gè)時(shí)候我們可以通過(guò)下面描述的 .Files 對(duì)象來(lái)訪(fǎng)問(wèn)文件實(shí)現(xiàn)。
訪(fǎng)問(wèn)文件
在上一節(jié)中我們介紹了幾種創(chuàng)建和訪(fǎng)問(wèn)命名模板的方法,這使得從另一個(gè)模板中導(dǎo)入一個(gè)模板變得很容易,但是有時(shí)候需要導(dǎo)入一個(gè)不是模板的文件并注入其內(nèi)容,而不通過(guò)模板渲染器獲得內(nèi)容。 Helm 提供了一個(gè) .Files 對(duì)象對(duì)文件的訪(fǎng)問(wèn),但是在模板中使用這個(gè)對(duì)象之前,還有幾個(gè)需要注意的事項(xiàng)值得一提: 可以在 Helm chart 中添加額外的文件,這些文件也會(huì)被打包,不過(guò)需要注意,由于 Kubernetes 對(duì)象的存儲(chǔ)限制,Charts 必須小于 1M 由于一些安全原因,通過(guò) .Files 對(duì)象無(wú)法訪(fǎng)問(wèn)某些文件 無(wú)法訪(fǎng)問(wèn) templates/ 下面的文件 無(wú)法訪(fǎng)問(wèn)使用 .helmignore 排除的文件 Chart 不會(huì)保留 UNIX 模式的信息,所以,當(dāng)使用 .Files 對(duì)象時(shí),文件級(jí)別的權(quán)限不會(huì)對(duì)文件的可用性產(chǎn)生影響。 基本示例? 現(xiàn)在我們來(lái)編寫(xiě)一個(gè)模板,將3個(gè)文件讀入到 ConfigMap 模板中,首先我們?cè)?chart 中添加3個(gè)文件,將3個(gè)文件都直接放置在 mychart/ 目錄中。 config1.toml: message = Hello from config 1 config2.toml: message = This is config 2 config3.toml: message = Goodbye from config 3 3個(gè)文件都是簡(jiǎn)單的 TOML 文件,我們知道這些文件的名稱(chēng),所以我們可以使用 range 函數(shù)來(lái)遍歷它們,并將其內(nèi)容注入到 ConfigMap 中去。 apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: {{- $files := .Files }} {{- range tuple "config1.toml" "config2.toml" "config3.toml" }} {{ . }}: |- {{ $files.Get . }} {{- end }} 這里我們聲明了一個(gè) $files 的變量來(lái)保存 .Files 對(duì)象的引用,還使用了 tuple 函數(shù)來(lái)循環(huán)文件列表,然后我們打印每個(gè)文件夾 {{ . }}: |-,后面使用 {{ $files.Get . }} 獲取文件內(nèi)容。 現(xiàn)在我們渲染這個(gè)模板會(huì)產(chǎn)生包含3個(gè)文件內(nèi)容的單個(gè) ConfigMap: # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1576046462-configmap data: config1.toml: |- message = Hello from config 1 config2.toml: |- message = This is config 2 config3.toml: |- message = Goodbye from config 3 另外在處理文件的時(shí)候,對(duì)文件路徑本身執(zhí)行一些標(biāo)準(zhǔn)操作可能非常有用,為了解決這個(gè)問(wèn)題,Helm 從 Go 的路徑包中導(dǎo)入了許多功能供你使用,它們都可以使用與 Go 包中相同的相同名稱(chēng)來(lái)訪(fǎng)問(wèn),但是首字母需要小寫(xiě),比如 Base 需要變成 base,導(dǎo)入的函數(shù)有:- Base - Dir - Ext - IsAbs - Clean。 Glob 模式? 隨著 chart 的增長(zhǎng),你可能需要更多地組織文件,因此 Helm 提供了 Files.Glob 的方法來(lái)幫助我們獲取具有 glob 模式的文件。 .Glob 返回 Files 類(lèi)型,所以你可以在返回的對(duì)象上調(diào)用任何 Files 方法。比如,我們的文件目錄結(jié)構(gòu)如下所示: foo/: foo.txt foo.yaml bar/: bar.go bar.conf baz.yaml 我們可以用 Glob 進(jìn)行多種選擇: {{ range $path := .Files.Glob "**.yaml" }} {{ $path }}: | {{ .Files.Get $path }} {{ end }} 或者 {{ range $path, $bytes := .Files.Glob "foo/*" }} {{ $path }}: '{{ b64enc $bytes }}' {{ end }} ConfigMap 和 Secrets? 想要將文件內(nèi)容同時(shí)放入 ConfigMap 和 Secrets 中,以便在運(yùn)行時(shí)安裝到 Pod 中,這種需求很常見(jiàn),為了解決這個(gè)問(wèn)題,Helm 在 Files 類(lèi)型上添加了一個(gè)實(shí)用的方法。 根據(jù)上面的目錄結(jié)構(gòu),我們可以按照如下的方式進(jìn)行處理: apiVersion: v1 kind: ConfigMap metadata: name: conf data: {{ (.Files.Glob "foo/*").AsConfig | indent 2 }} --- apiVersion: v1 kind: Secret metadata: name: very-secret type: Opaque data: {{ (.Files.Glob "bar/*").AsSecrets | indent 2 }} 編碼? 我們也可以導(dǎo)入一個(gè)文件并用 base64 編碼進(jìn)行編碼: apiVersion: v1 kind: Secret metadata: name: {{ .Release.Name }}-secret type: Opaque data: token: |- {{ .Files.Get "config1.toml" | b64enc }} 上面將采用我們上面的 config1.toml 文件并對(duì)其內(nèi)容進(jìn)行 base64 編碼,渲染會(huì)得到如下所示的結(jié)果: # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: Secret metadata: name: mychart-1576048287-secret type: Opaque data: token: |- bWVzc2FnZSA9IEhlbGxvIGZyb20gY29uZmlnIDEK Lines? 有時(shí),需要訪(fǎng)問(wèn)模板中文件的每一行內(nèi)容,Helm 也提供了方法的 Lines 方法,我們可以使用 range 函數(shù)遍歷沒(méi)行內(nèi)容: data: some-file.txt: {{ range .Files.Lines "foo/bar.txt" }} {{ . }}{{ end }} 在 Helm 安裝的時(shí)候無(wú)法將文件傳遞到 chart 外部,所以,如果你要求用戶(hù)提供數(shù)據(jù)的話(huà),則必須使用 helm install -f 或者 helm install --set 來(lái)獲取。
NOTES.txt
在本節(jié)中我們將來(lái)了解為 chart 用戶(hù)提供說(shuō)明的一個(gè) NOTES.txt 文件,在 chart 安裝或者升級(jí)結(jié)束時(shí),Helm 可以為用戶(hù)打印出一些有用的信息,使用模板也可以自定義這些信息。 要將安裝說(shuō)明添加到 chart 中,只需要?jiǎng)?chuàng)建一個(gè) templates/NOTES.txt 文件,該文件純文本的,但是可以像模板一樣進(jìn)行處理,并具有所有常規(guī)模板的功能和可用對(duì)象。 現(xiàn)在讓我們來(lái)創(chuàng)建一個(gè)簡(jiǎn)單的 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 }} 現(xiàn)在我們運(yùn)行 helm install ./mychart,我們就可以在底部看到這樣的消息: RESOURCES: ==> v1/Secret NAME TYPE DATA AGE rude-cardinal-secret Opaque 1 0s ==> v1/ConfigMap NAME DATA AGE rude-cardinal-configmap 3 0s NOTES: Thank you for installing mychart. Your release is named rude-cardinal. To learn more about the release, try: $ helm status rude-cardinal $ helm get rude-cardinal 用這種方式可以向用戶(hù)提供一個(gè)有關(guān)如何使用其新安裝的 chart 的詳細(xì)信息,強(qiáng)烈建議創(chuàng)建 NOTES.txt 文件,雖然這不是必須的。
子Chart與全局值
到現(xiàn)在為止,我們從單一模板,到多個(gè)模板文件,但是都僅僅是處理的一個(gè) chart 包,但是 charts 可能具有一些依賴(lài)項(xiàng),我們稱(chēng)為 subcharts(子 chart),接下來(lái)我們將創(chuàng)建一個(gè)子 chart。 同樣在深入了解之前,我們需要了解下子 chart 相關(guān)的一些信息。 子 chart 是獨(dú)立的,這意味著子 chart 不能顯示依賴(lài)其父 chart 所以子 chart 無(wú)法訪(fǎng)問(wèn)其父級(jí)的值 父 chart 可以覆蓋子 chart 的值 Helm 中有可以被所有 charts 訪(fǎng)問(wèn)的全局值的概念 創(chuàng)建子chart? 同樣還是在之前操作的 mychart/ 這個(gè) chart 包中,我們來(lái)嘗試添加一些新的子 chart: $ cd mychart/charts $ helm create mysubchart Creating mysubchart $ rm -rf mysubchart/templates/*.* 和前面一樣,我們刪除了所有的基本模板,這樣我們可以從頭開(kāi)始。 添加 values 和 模板? 接下來(lái)我們?yōu)?mysubchart 這個(gè)子 chart 創(chuàng)建一個(gè)簡(jiǎn)單的模板和 values 值文件,mychart/charts/mysubchart 中已經(jīng)有一個(gè) values.yaml 文件了,在文件中添加下面的 values: dessert: cake 下面我們?cè)賱?chuàng)建一個(gè)新的 ConfigMap 模板 mychart/charts/mysubchart/templates/configmap.yaml: apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-cfgmap2 data: dessert: {{ .Values.dessert }} 因?yàn)槊總€(gè)子 chart 都是獨(dú)立的 chart,所以我們可以單獨(dú)測(cè)試 mysubchart: helm install --generate-name --dry-run --debug mychart/charts/mysubchart install.go [debug] Original chart version: "" install.go [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain/content/helm/manifests/mychart/charts/mysubchart NAME: mysubchart-1576050755 LAST DEPLOYED: Wed Dec 11 1536 2019 NAMESPACE: default STATUS: pending-install REVISION: 1 TEST SUITE: None USER-SUPPLIED VALUES: {} COMPUTED VALUES: dessert: cake HOOKS: MANIFEST: --- # Source: mysubchart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mysubchart-1576050755-cfgmap2 data: dessert: cake 從父 chart 覆蓋 values? 我們?cè)瓉?lái)的 chart - mychart 現(xiàn)在是 mysubchart 的父級(jí) chart 了。由于 mychart 是父級(jí),所以我們可以在 mychart 中指定配置,并將該配置發(fā)送到 mysubchart 中去,比如,我們可以這樣修改 mychart/values.yaml: favorite: drink: coffee food: pizza pizzaToppings: - mushrooms - cheese - peppers - onions mysubchart: dessert: ice cream 最后兩行,mysubchart 部分中的所有指令都回被發(fā)送到 mysubchart 子 chart 中,所以,如果我們現(xiàn)在渲染模板,我們可以看到 mysubchart 的 ConfigMap 會(huì)被渲染成如下的內(nèi)容: # Source: mychart/charts/mysubchart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1576051914-cfgmap2 data: dessert: ice cream 我們可以看到頂層的 values 值覆蓋了子 chart 中的值。這里有一個(gè)細(xì)節(jié)需要注意,我們沒(méi)有將 mychart/charts/mysubchart/templates/configmap.yaml 模板更改為指向 .Values.mysubchart.dessert,因?yàn)閺脑撃0宓慕^度來(lái)看,該值仍然位于 .Values.dessert,當(dāng)模板引擎?zhèn)鬟f values 值的時(shí)候,它會(huì)設(shè)置這個(gè)作用域,所以,對(duì)于 mysubchart 模板,.Values 中僅僅提供用于該子 chart 的值。 但是有時(shí)候如果我們確實(shí)希望某些值可以用于所有模板,這個(gè)時(shí)候就可以使用全局 chart values 值來(lái)完成了。 全局值? 全局值是可以從任何 chart 或子 chart 中都可以訪(fǎng)問(wèn)的值,全局值需要顯示的聲明,不能將現(xiàn)有的非全局對(duì)象當(dāng)作全局對(duì)象使用。 Values 數(shù)據(jù)類(lèi)型具有一個(gè)名為 Values.global 的保留部分,可以在其中設(shè)置全局值,我們?cè)?mychart/values.yaml 文件中添加一個(gè)全局值: favorite: drink: coffee food: pizza pizzaToppings: - mushrooms - cheese - peppers - onions mysubchart: dessert: ice cream global: salad: caesar 由于全局值的原因,在 mychart/templates/configmap.yaml 和 mysubchart/templates/configmap.yaml 下面都應(yīng)該可以以 {{ .Values.global.salad }} 的形式來(lái)訪(fǎng)問(wèn)這個(gè)值。 mychart/templates/configmap.yaml: apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: salad: {{ .Values.global.salad }} mysubchart/templates/configmap.yaml: apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-cfgmap2 data: dessert: {{ .Values.dessert }} salad: {{ .Values.global.salad }} 然后我們渲染這個(gè)模板,可以得到如下所示的內(nèi)容: --- # Source: mychart/charts/mysubchart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1576053485-cfgmap2 data: dessert: ice cream salad: caesar --- # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-1576053485-configmap data: salad: caesar 全局值對(duì)于傳遞這樣的數(shù)據(jù)比較有用。 共享模板? 父級(jí) chart 和子 chart 可以共享模板,任何 chart 中已定義的塊都可以用于其他 chart。比如,我們可以定義一個(gè)簡(jiǎn)單的模板,如下所示: {{- define "labels" }}from: mychart{{ end }} 前面我們提到過(guò)可以使用在模板中使用 include 和 template,但是使用 include 的一個(gè)優(yōu)點(diǎn)是可以動(dòng)態(tài)引入模板的內(nèi)容: {{ include $mytemplate }}
模板調(diào)試
調(diào)試模板可能比較麻煩,因?yàn)殇秩镜哪0鍟?huì)發(fā)送到 Kubernetes API server,而 API server 可能會(huì)因?yàn)楦袷揭酝獾囊恍┰蚨芙^ YAML 文件。 下面這些命令可以幫助你調(diào)試一些問(wèn)題: helm lint 是驗(yàn)證 chart 是否遵循最佳實(shí)踐的首選工具 helm install --dry-run --debug 或者 helm template --debug:前面我們已經(jīng)使用了這個(gè)技巧,這個(gè)是讓服務(wù)器渲染模板,然后返回生成的資源清單文件的好方法,而且不會(huì)真正的去安裝這些資源 helm get manifest:這是查看服務(wù)器上安裝了哪些模板的好方法 當(dāng)你的 YAML 文件無(wú)法解析的時(shí)候,但你想要查看生成的內(nèi)容的時(shí)候,檢索 YAML 的一種簡(jiǎn)單方法是注釋掉模板中的問(wèn)題部分,然后重新運(yùn)行 helm install --dry-run --debug: apiVersion: v2 # some: problem section # {{ .Values.foo | quote }} 上面的內(nèi)容將呈現(xiàn)并返回完整的注釋?zhuān)? apiVersion: v2 # some: problem section # "bar" 這提供了一種查看生成的內(nèi)容的快速方法。
Chart Hooks
Helm 也提供了一種 Hook 機(jī)制,可以允許 chart 開(kāi)發(fā)人員在 release 生命周期的某些時(shí)間點(diǎn)進(jìn)行干預(yù)。比如,可以使用 hook 來(lái)進(jìn)行下面的操作: 在加載任何 charts 之前,在安裝的時(shí)候加載 ConfigMap 或者 Secret 在安裝新的 chart 之前,執(zhí)行一個(gè) Job 來(lái)備份數(shù)據(jù)庫(kù),然后在升級(jí)后執(zhí)行第二個(gè) Job 還原數(shù)據(jù) 在刪除 release 之前運(yùn)行一個(gè) JOb,以在刪除 release 之前適當(dāng)?shù)厝∠嚓P(guān)服務(wù) Hooks 的工作方式類(lèi)似于普通的模板,但是他們具有特殊的注解,這些注解使 Helm 可以用不同的方式來(lái)使用他們。 Hooks? 在 Helm 中定義了如下一些可供我們使用的 Hooks: 預(yù)安裝pre-install:在模板渲染后,kubernetes 創(chuàng)建任何資源之前執(zhí)行 安裝后post-install:在所有 kubernetes 資源安裝到集群后執(zhí)行 預(yù)刪除pre-delete:在從 kubernetes 刪除任何資源之前執(zhí)行刪除請(qǐng)求 刪除后post-delete:刪除所有 release 的資源后執(zhí)行 升級(jí)前pre-upgrade:在模板渲染后,但在任何資源升級(jí)之前執(zhí)行 升級(jí)后post-upgrade:在所有資源升級(jí)后執(zhí)行 預(yù)回滾pre-rollback:在模板渲染后,在任何資源回滾之前執(zhí)行 回滾后post-rollback:在修改所有資源后執(zhí)行回滾請(qǐng)求 測(cè)試test:在調(diào)用 Helm test 子命令的時(shí)候執(zhí)行(可以查看測(cè)試文檔) 生命周期? Hooks 允許開(kāi)發(fā)人員在 release 的生命周期中的一些關(guān)鍵節(jié)點(diǎn)執(zhí)行一些鉤子函數(shù),我們正常安裝一個(gè) chart 包的時(shí)候的生命周期如下所示: 用戶(hù)運(yùn)行 helm install foo Helm 庫(kù)文件調(diào)用安裝 API 經(jīng)過(guò)一些驗(yàn)證,Helm 庫(kù)渲染 foo 模板 Helm 庫(kù)將產(chǎn)生的資源加載到 kubernetes 中去 Helm 庫(kù)將 release 對(duì)象和其他數(shù)據(jù)返回給客戶(hù)端 Helm 客戶(hù)端退出 如果開(kāi)發(fā)人員在 install 的生命周期中定義了兩個(gè) hook:pre-install和post-install,那么我們安裝一個(gè) chart 包的生命周期就會(huì)多一些步驟了: 用戶(hù)運(yùn)行helm install foo Helm 庫(kù)文件調(diào)用安裝 API 在 crds/ 目錄下面的 CRDs 被安裝 經(jīng)過(guò)一些驗(yàn)證,Helm 庫(kù)渲染 foo 模板 Helm 庫(kù)將 hook 資源加載到 kubernetes 中,準(zhǔn)備執(zhí)行pre-install hooks Helm 庫(kù)會(huì)根據(jù)權(quán)重對(duì) hooks 進(jìn)行排序(默認(rèn)分配權(quán)重0,權(quán)重相同的 hook 按升序排序) Helm 庫(kù)然后加載最低權(quán)重的 hook Helm 庫(kù)會(huì)等待,直到 hook 準(zhǔn)備就緒 Helm 庫(kù)將產(chǎn)生的資源加載到 kubernetes 中,注意如果添加了 --wait 參數(shù),Helm 庫(kù)會(huì)等待所有資源都準(zhǔn)備好,在這之前不會(huì)運(yùn)行 post-install hook Helm 庫(kù)執(zhí)行 post-install hook(加載 hook 資源) Helm 庫(kù)等待,直到 hook 準(zhǔn)備就緒 Helm 庫(kù)將 release 對(duì)象和其他數(shù)據(jù)返回給客戶(hù)端 Helm 客戶(hù)端退出 等待 hook 準(zhǔn)備就緒,這是一個(gè)阻塞的操作,如果 hook 中聲明的是一個(gè) Job 資源,Helm 將等待 Job 成功完成,如果失敗,則發(fā)布失敗,在這個(gè)期間,Helm 客戶(hù)端是處于暫停狀態(tài)的。 對(duì)于所有其他類(lèi)型,只要 kubernetes 將資源標(biāo)記為加載(添加或更新),資源就被視為就緒狀態(tài),當(dāng)一個(gè) hook 聲明了很多資源是,這些資源是被串行執(zhí)行的。 另外需要注意的是 hook 創(chuàng)建的資源不會(huì)作為 release 的一部分進(jìn)行跟蹤和管理,一旦 Helm 驗(yàn)證了 hook 已經(jīng)達(dá)到了就緒狀態(tài),它就不會(huì)去管它了。 所以,如果我們?cè)?hook 中創(chuàng)建了資源,那么不能依賴(lài) helm uninstall 去刪除資源,因?yàn)?hook 創(chuàng)建的資源已經(jīng)不受控制了,要銷(xiāo)毀這些資源,你需要將 helm.sh/hook-delete-policy 這個(gè) annotation 添加到 hook 模板文件中,或者設(shè)置 Job 資源的生存(TTL)字段。 編寫(xiě) Hook? Hooks 就是 Kubernetes 資源清單文件,在元數(shù)據(jù)部分帶有一些特殊的注解,因?yàn)樗麄兪悄0逦募?,所以你可以使用普通模板所有的功能,包括讀取 .Values、.Release 和 .Template。 例如,在 templates/post-install-job.yaml 文件中聲明一個(gè) post-install 的 hook: apiVersion: batch/v1 kind: Job metadata: name: "{{ .Release.Name }}" labels: app.kubernetes.io/managed-by: {{ .Release.Service | quote }} app.kubernetes.io/instance: {{ .Release.Name | quote }} app.kubernetes.io/version: {{ .Chart.AppVersion }} helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" annotations: # 因?yàn)樘砑恿诉@個(gè) hook,所以我們這個(gè)資源被定義為了 hook # 如果沒(méi)有這行,則當(dāng)前這個(gè) Job 會(huì)被當(dāng)成 release 的一部分內(nèi)容。 "helm.sh/hook": post-install "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": hook-succeeded spec: template: metadata: name: "{{ .Release.Name }}" labels: app.kubernetes.io/managed-by: {{ .Release.Service | quote }} app.kubernetes.io/instance: {{ .Release.Name | quote }} helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" spec: restartPolicy: Never containers: - name: post-install-job image: "alpine:3.3" command: ["/bin/sleep","{{ default "10" .Values.sleepyTime }}"] 當(dāng)前這個(gè)模板成為 hook 的原因就是添加這個(gè)注解: annotations: "helm.sh/hook": post-install 一種資源也可以實(shí)現(xiàn)多個(gè) hooks: annotations: "helm.sh/hook": post-install,post-upgrade 類(lèi)似的,實(shí)現(xiàn)給定 hook 的資源數(shù)量也沒(méi)有限制,比如可以將 secret 和一個(gè) configmap 都聲明為 pre-install hook。 當(dāng)子 chart 聲明 hooks 的時(shí)候,也會(huì)對(duì)其進(jìn)行調(diào)用,頂層的 chart 無(wú)法禁用子 chart 所聲明的 hooks。可以為 hooks 定義權(quán)重,這將有助于確定 hooks 的執(zhí)行順序: annotations: "helm.sh/hook-weight": "5" hook 權(quán)重可以是正數(shù)也可以是負(fù)數(shù),但是必須用字符串表示,當(dāng) Helm 開(kāi)始執(zhí)行特定種類(lèi)的 hooks 的時(shí)候,它將以升序的方式對(duì)這些 hooks 進(jìn)行排序。 Hook 刪除策略? 我們還可以定義確定何時(shí)刪除相應(yīng) hook 資源的策略,hook 刪除策略可以使用下面的注解進(jìn)行定義: annotations: "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 我們也可以選擇一個(gè)或多個(gè)已定義的注解: before-hook-creation:運(yùn)行一個(gè)新的 hook 之前刪除前面的資源(默認(rèn)) hook-succeeded:hook 成功執(zhí)行后刪除資源 hook-failed:hook 如果執(zhí)行失敗則刪除資源 如果未指定任何 hook 刪除策略注解,則默認(rèn)情況下會(huì)使用 before-hook-creation 策略。
示例
前面介紹了 Helm 的基本使用,以及 Helm Chart 包開(kāi)發(fā)相關(guān)的一些知識(shí)點(diǎn),下面我們用一個(gè)實(shí)例來(lái)演示下如何開(kāi)發(fā)一個(gè)真正的 Helm Chart 包。 應(yīng)用? 我們這里為前面的完整示例 Wordpress 開(kāi)發(fā)一個(gè) Chart 包,在開(kāi)發(fā) Chart 包之前很明顯我們最需要的就是要知道我們自己的應(yīng)用應(yīng)該如何使用,如何部署,不然是不可能編寫(xiě)出對(duì)應(yīng)的 Chart 包的。 我們可以用 helm create 命令來(lái)創(chuàng)建一個(gè) Chart 包,這里我們就完全手動(dòng)來(lái)創(chuàng)建,首先創(chuàng)建一個(gè)名為 wordpress 的文件夾: $ mkdir wordpress && cd wordpress 然后在目錄下面創(chuàng)建如下所示的 Chart.yaml 文件: apiVersion: v2 name: wordpress description: A Helm chart for Kubernetes home: https://wordpress.org/ type: application # chart 版本號(hào) version: 0.1.0 # wordpress 的應(yīng)用版本 appVersion: 5.3.2 由于 Wordpress 應(yīng)用本身是一來(lái) MySQL 數(shù)據(jù)庫(kù)的,所以同樣需要添加一個(gè)依賴(lài)說(shuō)明 requirements.yaml: dependencies: - name: mysql version: 1.6.2 repository: http://mirror.azure.cn/kubernetes/charts/ condition: mysql.enabled tags: - wordpress-database 這里依賴(lài)的應(yīng)用我們可以通過(guò) helm search 命令來(lái)獲取,當(dāng)然也可以隨意指定一個(gè) Chart 版本: $ helm search repo stable/mysql NAME CHART VERSION APP VERSION DESCRIPTION stable/mysql 1.6.2 5.7.28 Fast, reliable, scalable, and easy to use open-... 需要注意的是在依賴(lài)的文件中我們添加了一個(gè) condition: mysql.enabled 條件,這意味著當(dāng) Values 值 mysql.enabled 為 true 的時(shí)候才會(huì)真正去依賴(lài)這個(gè)子 Chart,因?yàn)槲覀兺耆梢灾苯邮褂靡粋€(gè)外部的數(shù)據(jù)庫(kù)。所以首先我們要先添加一個(gè) mysql.enabled 的 Values 值,添加如下所示的 values.yaml 文件: ## ## MySQL Chart 配置,參考如下 values.yaml ## https://github.com/helm/charts/blob/master/stable/mysql/values.yaml ## mysql: ## 是否部署mysql服務(wù)來(lái)滿(mǎn)足wordpress的需求。如果要使用外部數(shù)據(jù)庫(kù),將 enabled 設(shè)置為 false 并配置 externalDatabase 參數(shù)。 enabled: true ## todo,其他 mysql 配置 ## 當(dāng) mysql.enabled=false 的時(shí)候使用外部數(shù)據(jù)庫(kù) externalDatabase: ## 數(shù)據(jù)庫(kù)地址 host: localhost:3306 ## Wordpress 數(shù)據(jù)庫(kù)的非root用戶(hù) user: wordpress ## 數(shù)據(jù)庫(kù)密碼 password: wordpress ## 數(shù)據(jù)庫(kù)名稱(chēng) database: wordpress 上面的 Values 配置很好理解,就是如果 mysql.enabled=true 則我們就用 mysql 這個(gè)子 Chart 去安裝一個(gè) MySQL 數(shù)據(jù)庫(kù),如果為 false 則使用下面 externalDatabase 的外部數(shù)據(jù)庫(kù)信息來(lái)作為 wordpress 的數(shù)據(jù)庫(kù)配置,雖然現(xiàn)在這些配置還完全沒(méi)有任何作用,但是這些卻是一開(kāi)始就很容易考慮到的事情。 接下來(lái)創(chuàng)建一個(gè) templates 和 charts 目錄,并在 charts 目錄下面獲取 mysql 這個(gè)子 chart: $ mkdir templates && mkdir charts $ cd charts && helm fetch stable/mysql && cd .. 現(xiàn)在的整個(gè) Chart 包目錄結(jié)構(gòu)如下所示: $ tree . . ├── Chart.yaml ├── charts │ └── mysql-1.6.2.tgz ├── requirements.yaml ├── templates └── values.yaml 2 directories, 4 files 接下來(lái)就是我們的重頭戲模板的開(kāi)發(fā)了。我們可以將前面課程示例中的 Wordpress 資源清單直接拷貝到 templates 目錄下面,將 Deployment 和 Service 分別放在不同的 YAML 文件中,同樣還有數(shù)據(jù)持久化的資源清單,但是這里我們就需要先將 MySQL 的部分移除掉,因?yàn)槲覀儠?huì)通過(guò)外部數(shù)據(jù)庫(kù)或者子 Chart 來(lái)渲染,不需要在這里顯示的聲明了,另外還需要將所有資源對(duì)象的命名空間去掉,因?yàn)槲覀兛梢酝ㄟ^(guò) helm install 命令直接指定即可,現(xiàn)在我們的模板目錄結(jié)構(gòu)就如下所示了: $ tree templates templates ├── deployment.yaml ├── pvc.yaml └── service.yaml 0 directories, 4 files 名稱(chēng)? 這個(gè)時(shí)候我們可以嘗試去安裝調(diào)試下這個(gè) Chart 模板: $ helm install --generate-name --dry-run --debug . install.go [debug] Original chart version: "" install.go [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain/content/helm/manifests/wordpress load.go Warning: Dependencies are handled in Chart.yaml since apiVersion "v2". We recommend migrating dependencies to Chart.yaml. Error: rendered manifests contain a resource that already exists. Unable to continue with install: existing resource conflict: kind: Middleware, namespace: default, name: redirect-https helm.go [debug] existing resource conflict: kind: Middleware, namespace: default, name: redirect-https rendered manifests contain a resource that already exists. Unable to continue with install helm.sh/helm/v3/pkg/action.(*Install).Run /home/circleci/helm.sh/helm/pkg/action/install.go:242 main.runInstall /home/circleci/helm.sh/helm/cmd/helm/install.go:209 main.newInstallCmd.func1 /home/circleci/helm.sh/helm/cmd/helm/install.go:115 github.com/spf13/cobra.(*Command).execute /go/pkg/mod/github.com/spf13/cobra@v0.0.5/command.go:826 github.com/spf13/cobra.(*Command).ExecuteC /go/pkg/mod/github.com/spf13/cobra@v0.0.5/command.go:914 github.com/spf13/cobra.(*Command).Execute /go/pkg/mod/github.com/spf13/cobra@v0.0.5/command.go:864 main.main /home/circleci/helm.sh/helm/cmd/helm/helm.go:75 runtime.main /usr/local/go/src/runtime/proc.go:203 runtime.goexit /usr/local/go/src/runtime/asm_amd64.s:1357 我們看到出現(xiàn)了錯(cuò)誤,其中還有這樣的一條警告信息 load.go Warning: Dependencies are handled in Chart.yaml since apiVersion "v2". We recommend migrating dependencies to Chart.yaml.,大概意思就是在 Helm3 中依賴(lài)項(xiàng)的配置被移動(dòng)到了 Chart.yaml 文件中,也就是在 Helm2 版本的時(shí)候依賴(lài)才會(huì)在 requirements.yaml 中聲明,這里我們可以將 requirements.yaml 文件中的內(nèi)容全部拷貝到 Chart.yaml 文件中去,然后刪除 requirements.yaml 文件,這個(gè)時(shí)候重新 DEBUG 下就沒(méi)有這個(gè)提示信息了。 下面的錯(cuò)誤提示基本上都是類(lèi)似于 rendered manifests contain a resource that already exists. 這樣的信息,意思就是這些資源對(duì)象在現(xiàn)有集群中已經(jīng)包含了,這很正常,因?yàn)槲覀兊馁Y源對(duì)象名稱(chēng)都是寫(xiě)死的,是非常有可能和現(xiàn)有的集群沖突的,所以接下來(lái)我們要給這些資源清單改名,如何才能避免沖突又具有很好的擴(kuò)展性呢?這里我們創(chuàng)建一個(gè)如下所示的命名模板: {{/* 創(chuàng)建一個(gè)默認(rèn)的應(yīng)用名稱(chēng),截取63個(gè)字符是因?yàn)?Kubernetes 的 name 屬性的限制(DNS 命名規(guī)范)。 */}} {{- define "wordpress.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 -}} 根據(jù)規(guī)范命名模板的名稱(chēng)最好以 Chart 名作為前綴,這里我們命名成 wordpress.fullname,首先就是判斷是否定義了 fullnameOverride 這個(gè) Values 值,如果定義了則截取 63 個(gè)字符并移除 - 這樣的前后綴作為名稱(chēng),如果沒(méi)有定義呢?就要檢查 Chart 名是否包含 Release 名稱(chēng),如果包含則截取 Release 名的 63 個(gè)字符并移除 - 這樣的前后綴作為名稱(chēng),如果不包含,則將 Release 名稱(chēng)和 Chart 名稱(chēng)用 - 連接起來(lái)作為名稱(chēng),這個(gè)命名的方式也是符合大部分 Chart 模板中的命名,以后的模板開(kāi)發(fā)中都可以直接使用。在 templates 目錄下面新建一個(gè) _helpers.tpl 這樣的 partials 文件,然后將上面定義的命名模板放置到里面去。后面我們所有的命名模板都將在該文件中完成。 接下來(lái)將 templates 目錄下面的所有資源對(duì)象 name 屬性全都換成上面我們定義的命名模板,由于這里并沒(méi)有什么空格控制之類(lèi)的,我們直接使用 template 函數(shù)即可,同樣作為慣例,也習(xí)慣將 Release 和 Chart 名稱(chēng)之類(lèi)作為 Label 標(biāo)簽,所以最終,這些資源清單的 Meta 信息如下所示,如果有其他額外的信息當(dāng)然可以隨意添加: metadata: name: {{ template "wordpress.fullname" . }} labels: app: "{{ template "wordpress.fullname" . }}" chart: "{{ template "wordpress.chart" . }}" release: {{ .Release.Name | quote }} heritage: {{ .Release.Service | quote }} 然后當(dāng)然也需要將 Deployment 的 matchLabels 和 template 下面的 Label 標(biāo)簽進(jìn)行修改,以及 Service 的 selector 匹配的標(biāo)簽。 同樣還有 PVC 對(duì)象,以前我們直接使用的一個(gè)確定名稱(chēng)的 PVC 對(duì)象: volumes: - name: wordpress-data persistentVolumeClaim: claimName: wordpress-pvc 但是現(xiàn)在我們這里作為模板就要考慮到各種情況了,很有可能使用我們模板的用戶(hù)根本就不需要持久化,也有可能直接傳遞一個(gè)存在的 PVC 對(duì)象進(jìn)來(lái)使用,所以這里做了如下改變: volumes: - name: wordpress-data {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ .Values.persistence.existingClaim | default (include "wordpress.fullname" .) }} {{- else }} emptyDir: {} {{ end }} 當(dāng)我們定義了 Values 值 persistence.enabled=true 為 true 時(shí)候就表示要使用持久化了,所以要指定下面的 claimName 屬性,但是還需要判斷 persistence.existingClaim 這個(gè) Values 值是否存在,如果存在則表示直接使用,如果不存在則使用我們模板里面渲染的 PVC 對(duì)象,如果不需要持久化,則直接使用 emptyDir:{} 即可。最后我們的 PVC 對(duì)象模板變成了如下所示: {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} kind: PersistentVolumeClaim apiVersion: v1 metadata: name: {{ template "wordpress.fullname" . }} labels: app: "{{ template "wordpress.fullname" . }}" chart: "{{ template "wordpress.chart" . }}" release: {{ .Release.Name | quote }} heritage: {{ .Release.Service | quote }} spec: {{- if .Values.persistence.storageClass }} storageClassName: {{ .Values.persistence.storageClass | quote }} {{- end }} accessModes: - {{ .Values.persistence.accessMode | quote }} resources: requests: storage: {{ .Values.persistence.size | quote }} {{- end -}} 其中訪(fǎng)問(wèn)模式、存儲(chǔ)容量、StorageClass、存在的 PVC 都通過(guò) Values 來(lái)指定,增加了靈活性。對(duì)應(yīng)的 values.yaml 配置部分我們可以給一個(gè)默認(rèn)的配置: ## 是否使用 PVC 開(kāi)啟數(shù)據(jù)持久化 persistence: enabled: true ## 是否使用 storageClass,如果不適用則補(bǔ)配置 # storageClass: "xxx" ## ## 如果想使用一個(gè)存在的 PVC 對(duì)象,則直接傳遞給下面的 existingClaim 變量 # existingClaim: your-claim accessMode: ReadWriteMany # 訪(fǎng)問(wèn)模式 size: 2Gi # 存儲(chǔ)容量 現(xiàn)在我們?nèi)ブ匦伦鲆淮?DEBUG,可以看到正常了,但是還遠(yuǎn)遠(yuǎn)不夠,接下來(lái)我們就來(lái)定制其他部分。 定制? 比如副本數(shù)我們可以通過(guò) Values 來(lái)指定,變成 replicas: {{ .Values.replicaCount }}。 更新策略也可以,因?yàn)楦虏呗圆⒉皇且粚硬蛔兊?,這里和之前不太一樣,我們需要用到一個(gè)新的函數(shù) toYaml: {{- if .Values.updateStrategy }} strategy: {{ toYaml .Values.updateStrategy | nindent 4 }} {{- end }} 意思就是我們將 updateStrategy 這個(gè) Values 值轉(zhuǎn)換成 YAML 格式,并保留4個(gè)空格。然后添加其他的配置,比如是否需要添加 nodeSelector、容忍、親和性這些,這里我們都是使用 toYaml 函數(shù)來(lái)控制空格,如下所示: {{- if .Values.nodeSelector }} nodeSelector: {{ toYaml .Values.nodeSelector | indent 8 }} {{- end -}} {{- with .Values.affinity }} affinity: {{ toYaml . | indent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{ toYaml . | indent 8 }} {{- end }} 接下來(lái)當(dāng)然就是鏡像的配置了,如果是私有倉(cāng)庫(kù)還需要指定 imagePullSecrets: {{- if .Values.image.pullSecrets }} imagePullSecrets: {{- range .Values.image.pullSecrets }} - name: {{ . }} {{- end }} {{- end }} containers: - name: wordpress image: {{ printf "%s:%s" .Values.image.name .Values.image.tag }} imagePullPolicy: {{ .Values.image.pullPolicy | quote }} ports: - containerPort: 80 name: web 對(duì)應(yīng)的 Values 值如下所示: image: name: wordpress tag: 5.3.2-apache ## 指定 imagePullPolicy 默認(rèn)為 Always pullPolicy: IfNotPresent ## 如果是私有倉(cāng)庫(kù),需要指定 imagePullSecrets # pullSecrets: # - myRegistryKeySecretName 然后就是最重要的環(huán)境變量配置部分了,因?yàn)樯婕暗綌?shù)據(jù)庫(kù)的配置,同樣最核心的三個(gè)環(huán)境變量配置 WORDPRESS_DB_HOST、WORDPRESS_DB_USER、WORDPRESS_DB_PASSWORD,由于可能使用我們模板的用戶(hù)可能使用外部數(shù)據(jù)庫(kù),也有可能使用我們依賴(lài)的 mysql 這個(gè)子 Chart,所以我們需要分別判斷來(lái)進(jìn)行渲染: env: - name: WORDPRESS_DB_HOST {{- if .Values.mysql.enabled }} value: {{ printf "%s:%d" (include "mysql.fullname" .) (int64 .Values.mysql.service.port) }} {{- else }} value: {{ .Values.externalDatabase.host | quote }} {{- end }} - name: WORDPRESS_DATABASE_NAME {{- if .Values.mysql.enabled }} value: {{ .Values.mysql.mysqlDatabase | quote }} {{- else }} value: {{ .Values.externalDatabase.database | quote }} {{- end }} - name: WORDPRESS_DB_USER {{- if .Values.mysql.enabled }} value: {{ .Values.mysql.mysqlUser | quote }} {{- else }} value: {{ .Values.externalDatabase.user | quote }} {{- end }} - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: {{- if .Values.mysql.enabled }} name: {{ template "mysql.fullname" . }} key: mysql-password {{- else }} name: {{ printf "%s-%s" .Release.Name "externaldb" }} key: db-password {{- end }} 每一個(gè)環(huán)境變量首先都是判斷 mysql.enabled 是否為 true,才表示使用子 Chart 來(lái)渲染,而對(duì)應(yīng)的值都是子 Chart 中渲染過(guò)后的值,所以也需要我們?nèi)チ私庾?Chart 的渲染行為,如果使用外部的數(shù)據(jù)庫(kù)就要簡(jiǎn)單很多,因?yàn)橹恍枰x取 Values 值即可。另外一個(gè)值得注意的是如果是配置的密碼,我們還需要去創(chuàng)建一個(gè) Secret 資源對(duì)象來(lái)引用。所以我們?cè)?templates 目錄下面創(chuàng)建了一個(gè) externaldb-secrets.yaml 的資源文件,里面配置的密碼通過(guò) b64enc 函數(shù)轉(zhuǎn)換為 Base64 編碼格式。 {{- if not .Values.mysql.enabled }} apiVersion: v1 kind: Secret metadata: name: {{ printf "%s-%s" .Release.Name "externaldb" }} labels: app: {{ printf "%s-%s" .Release.Name "externaldb" }} chart: "{{ template "wordpress.chart" . }}" release: {{ .Release.Name | quote }} heritage: {{ .Release.Service | quote }} type: Opaque data: db-password: {{ .Values.externalDatabase.password | b64enc | quote }} {{- end }} 然后就是 resource 資源聲明,這里我們定義一個(gè)默認(rèn)的 resources 值,同樣用 toYaml 函數(shù)來(lái)控制空格: resources: {{ toYaml .Values.resources | indent 10 }} 最后是健康檢查部分,雖然我們之前沒(méi)有做 livenessProbe,但是我們開(kāi)發(fā) Chart 模板的時(shí)候就要盡可能考慮周全一點(diǎn),這里我們加上存活性和可讀性?xún)蓚€(gè)探針,并且根據(jù) livenessProbe.enabled 和 readinessProbe.enabled 兩個(gè) Values 值來(lái)判斷是否需要添加探針,探針對(duì)應(yīng)的參數(shù)也都通過(guò) Values 值來(lái)配置: {{- if .Values.livenessProbe.enabled }} livenessProbe: initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.livenessProbe.periodSeconds }} timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} successThreshold: {{ .Values.livenessProbe.successThreshold }} failureThreshold: {{ .Values.livenessProbe.failureThreshold }} httpGet: path: /wp-login.php port: 80 {{- end }} {{- if .Values.readinessProbe.enabled }} readinessProbe: initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.readinessProbe.periodSeconds }} timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} successThreshold: {{ .Values.readinessProbe.successThreshold }} failureThreshold: {{ .Values.readinessProbe.failureThreshold }} httpGet: path: /wp-login.php port: 80 {{- end }} 這樣我們的 Deployment 的模板就基本上完成了,我們可以通過(guò) DEBUG 模式來(lái)模擬渲染: $ helm install --generate-name --dry-run --debug . 可以根據(jù)結(jié)果來(lái)判斷是否符合我們的需求。 最后我們還需要來(lái)對(duì) Service 對(duì)象做模板化,因?yàn)榍懊嫖覀兪悄J(rèn)的 NodePort 類(lèi)型,我們需要通過(guò) Values 來(lái)定制: apiVersion: v1 kind: Service metadata: name: {{ template "wordpress.fullname" . }} labels: app: "{{ template "wordpress.fullname" . }}" chart: "{{ template "wordpress.chart" . }}" release: {{ .Release.Name | quote }} heritage: {{ .Release.Service | quote }} spec: selector: app: "{{ template "wordpress.fullname" . }}" type: {{ .Values.service.type }} {{- if (or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort")) }} externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} {{- end }} ports: - name: web port: {{ .Values.service.port }} targetPort: web {{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort)))}} nodePort: {{ .Values.service.nodePort }} {{- end }} 這樣我們就可以通過(guò)配置 Values 值來(lái)配置 Service 對(duì)象了。最后就是 Ingress/IngressRoute 對(duì)象了,大家可以自己嘗試講這部分補(bǔ)齊。 最后在 templates 目錄下面加上 NOTES.txt 文件來(lái)說(shuō)明如何使用我們的 Chart 包就可以了: Get the WordPress Manifests Objects: $ kubectl get all -l app={{ .Release.Name }} 最后我們來(lái)真正的使用我們的 Chart 包安裝一次來(lái)測(cè)試下: $ helm install mychart . --set service.type=NodePort NAME: mychart LAST DEPLOYED: Sun Mar 8 1704 2020 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: Get the WordPress Manifests Objects: $ kubectl get all -l app=mychart $ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION mychart default 1 2020-03-08 1704.826185 +0800 CST deployed wordpress-0.1.0 5.3.2 安裝完成后可以查看我們的資源對(duì)象: $ kubectl get all -l app=mychart-wordpress NAME READY STATUS RESTARTS AGE pod/mychart-wordpress-5f65786d89-2m45s 1/1 Running 0 70s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/mychart-wordpress NodePort 10.99.239.2980:30427/TCP 70s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/mychart-wordpress 1/1 1 1 70s NAME DESIRED CURRENT READY AGE replicaset.apps/mychart-wordpress-5f65786d89 1 1 1 70s 這個(gè)時(shí)候我們通過(guò)上面的 NodePort 就可以去訪(fǎng)問(wèn)到我們的應(yīng)用了,當(dāng)然還有很多配置我們都是可以直接通過(guò) Values 去進(jìn)行定制的。
鏈接:https://www.cnblogs.com/hahaha111122222/p/16277623.html
-
集群
+關(guān)注
關(guān)注
0文章
97瀏覽量
17341 -
管理工具
+關(guān)注
關(guān)注
0文章
25瀏覽量
7778 -
kubernetes
+關(guān)注
關(guān)注
0文章
237瀏覽量
8906
原文標(biāo)題:示例
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
Kubernetes Helm入門(mén)指南

Linux環(huán)境下如何管理Python包管理工具







使用Helm 在容器服務(wù)k8s集群一鍵部署wordpress
Python之包管理工具快速入門(mén)

評(píng)論