Helm實戰手冊|從安裝到高階Chart編排,解鎖雲原生部署的“自動化神器”​

Helm

Kubernetes 包管理工具
Helm 可以幫助我們管理 Kubernetes 應用程式 – Helm Charts 可以定義、安裝和升級複雜的 Kubernetes 應用程式,Charts 包很容易建立、版本管理、分享和分佈。Helm 對於 Kubernetes 來說就相當於 yum 對於 Centos 來說,如果沒有 yum 的話,我們在 Centos 下面要安裝一些應用程式是極度麻煩的,同樣的,對於越來越複雜的 Kubernetes 應用程式來說,如果單純依靠我們去手動維護應用程式的 YAML 資源清單檔案來說,成本也是巨大的。接下來我們就來了解了 Helm 的使用方法。

安裝

首先當然需要一個可用的 Kubernetes 叢集,然後在我們使用 Helm 的節點上已經配置好可以透過 kubectl 訪問叢集,因為 Helm 其實就是讀取的 kubeconfig 檔案來訪問叢集的。
由於 Helm V2 版本必須在 Kubernetes 叢集中安裝一個 Tiller 服務進行通訊,這樣大大降低了其安全性和可用性,所以在 V3 版本中移除了服務端,採用了通用的 Kubernetes CRD 資源來進行管理,這樣就只需要連線上 Kubernetes 即可,而且 V3 版本已經發布了穩定版,所以我們這裡來安裝最新的 v3.0.1 版本,軟體包下載地址為:https://github.com/helm/helm/releases,我們可以根據自己的節點選擇合適的包,比如我這裡是 Mac,就下載 MacOS amd64 的版本。
下載到本地解壓後,將 helm 二進位制包檔案移動到任意的 PATH 路徑下即可:
$

helm version

version.BuildInfo{

Version:"v3.0.1"

,

GitCommit:"7c22ef9ce89e0ebeb7125ba2ebf7d421f3e82ffa"

,

GitTreeState:"clean"

,

GoVersion:"go1.13.4"

}

看到上面的版本資訊證明已經成功了。
一旦 Helm 客戶端準備成功後,我們就可以新增一個 chart 倉庫,當然最常用的就是官方的 Helm stable charts 倉庫,但是由於官方的 charts 倉庫地址需要ke xue shang wang,我們可以使用微軟的 charts 倉庫代替:
$

helm repo add stable

http:

/

/mirror.azure.cn/kubernetes/charts/
$

helm repo list

NAME URL

stable

http:

/

/mirror.azure.cn/kubernetes/charts/

安裝完成後可以用 search 命令來搜尋可以安裝的 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...

......

示例

為了安裝一個 chart 包,我們可以使用 helm install 命令,Helm 有多種方法來找到和安裝 chart 包,但是最簡單的方法當然是使用官方的 stable 這個倉庫直接安裝:
首先從倉庫中將可用的 charts 資訊同步到本地,可以確保我們獲取到最新的 charts 列表:

$ helm repo

update

Hang tight while we grab the latest

from

your chart repositories...

...Successfully got an

updatefrom

the "stable" chart repository

Update

Complete. ⎈ Happy Helming

!

比如我們現在安裝一個 mysql 應用:
$helminstallstable/mysql--generate-name
NAME:mysql-1575619811
LAST DEPLOYED:FriDec616:10:142019
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 這個 chart 已經安裝成功了,我們將安裝成功的這個應用叫做一個 release,由於我們在安裝的時候指定了–generate-name 引數,所以生成的 release 名稱是隨機生成的,名為 mysql-1575619811。我們可以用下面的命令來檢視 release 安裝以後對應的 Kubernetes 資源的狀態:

$ kubectl

getall-

l

release=

mysql

-1575619811

NAME READY STATUS RESTARTS AGE

pod

/

mysql

-1575619811-8479

b5b796

-

dgggz

0/1

Pending

027

m
NAME TYPE CLUSTER

-

IP

EXTERNAL-

IP PORT(S) AGE

service

/

mysql

-1575619811

ClusterIP

10.106.141.228<none>3306/

TCP

27

m
NAME READY UP

-TO-DATE

AVAILABLE AGE

deployment.apps

/

mysql

-15756198110/11027

m
NAME DESIRED

CURRENT

READY AGE

replicaset.apps

/

mysql

-1575619811-8479

b5b796

11027

m

我們也可以 helm show chart 命令來了解 MySQL 這個 chart 包的一些特性:
$

helm show chart stable/mysql

......

如果想要了解更多資訊,可以用 helm show all 命令:
$

helm show all stable/mysql

......

需要注意的是無論什麼時候安裝 chart,都會建立一個新的 release,所以一個 chart 包是可以多次安裝到同一個叢集中的,每個都可以獨立管理和升級。
同樣我們也可以用 Helm 很容易檢視到已經安裝的 release:
$

helm

ls

NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION

mysql-1575619811 default 1 2019-12-06 16:10:14.682302 +0800 CST deployed mysql-1.5.0 5.7.27

如果需要刪除這個 release,也很簡單,只需要使用 helm uninstall 命令即可:

$ helm uninstall mysql

-1575619811
release

"mysql-1575619811" uninstalled

$ kubectl

getall-

l

release=

mysql

-1575619811
No

resources found.

$ helm status mysql

-1575619811

Error:

release

:

not

found

uninstall 命令會從 Kubernetes 中刪除 release,也會刪除與 release 相關的所有 Kubernetes 資源以及 release 歷史記錄。也可以在刪除的時候使用 --keep-history 引數,則會保留 release 的歷史記錄,可以獲取該 release 的狀態就是 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

Dec616

:

47

:

142019

NAMESPACE:

default

STATUS: uninstalled

...

$ helm ls

-

a

NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION

mysql

-1575619811default12019-12-0616

:

47

:

14.415214+0800

CST uninstalled mysql

-1.5.05.7.27

因為 Helm 會在刪除 release 後跟蹤你的 release,所以你可以審查歷史甚至取消刪除 release(使用 helm rollback 命令)。

定製

上面我們都是直接使用的 helm install 命令安裝的 chart 包,這種情況下只會使用 chart 的預設配置選項,但是更多的時候,是各種各樣的需求,索引我們希望根據自己的需求來定製 chart 包的配置引數。
我們可以使用 helm show values 命令來檢視一個 chart 包的所有可配置的引數選項:
$

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

......

上面我們看到的所有引數都是可以用自己的資料來覆蓋的,可以在安裝的時候透過 YAML 格式的檔案來傳遞這些引數:
$catconfig.yaml
mysqlUser:
user0
mysqlPassword:user0pwd
mysqlDatabase:user0db
persistence:
enabled:false
$helminstall-fconfig.yamlstable/mysql
helminstall-fconfig.yamlmysqlstable/mysql
NAME:mysql
LAST DEPLOYED:FriDec617:46:562019
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 安裝成功後,可以檢視對應的 Pod 資訊:

$ kubectl

get

pod

-

l

release=

mysql

NAME READY STATUS RESTARTS AGE

mysql

-

ddd798f48

-

gnrzd

0/1

PodInitializing

0119

s

$ kubectl

describe

pod mysql

-

ddd798f48

-

gnrzd

......

Environment:

MYSQL_ROOT_PASSWORD:

<setto

the key

'mysql-root-password'in

secret

'mysql'>

Optional:

false

MYSQL_PASSWORD:

<setto

the key

'mysql-password'in

secret

'mysql'>

Optional:

false

MYSQL_USER: user0

MYSQL_DATABASE: user0db

......

可以看到環境變數 MYSQL_USER=user0,MYSQL_DATABASE=user0db 的值和我們上面配置的值是一致的。在安裝過程中,有兩種方法可以傳遞配置資料:
  • --values(或者 -f):指定一個 YAML 檔案來覆蓋 values 值,可以指定多個值,最後邊的檔案優先
  • --set:在命令列上指定覆蓋的配置
如果同時使用這兩個值,--set 將被合併到具有更高優先順序的 --values,使用 --set 指定的值將持久化在 ConfigMap 中,對於給定的 release,可以使用 helm get values <release-name> 來檢視已經設定的值,已設定的值也透過允許 helm upgrade 並指定 --reset 值來清除。
--set 選項接收零個或多個 name/value 對,最簡單的用法就是 --set name=value,相當於 YAML 檔案中的:
name: value
多個值之間用字串“,”隔開,用法就是 --set a=b,c=d,相當於 YAML 檔案中的:
a: b
c: d
也支援更加複雜的表示式,例如 --set outer.inner=value,對應 YAML:
outer

:

inner

:

value

對於列表陣列可以用 {} 來包裹,比如 -set name={a, b, c},對應 YAML:

name:

-

a

-

b

-

c

從 Helm 2.5.0 開始,就可以使用陣列索引語法來訪問列表中某個項,比如 --set servers[0].port=80,對應的 YAML 為:
servers:
-port:80
也可以這樣設定多個值,比如 -set servers[0].port=80,servers[0].host=example,對應的 YAML 為:

servers

-

port: 80

host: example

有時候你可能需要在 --set 選項中使用特殊的字元,這個時候可以使用反斜槓來跳脫字元,比如 --set name=value1\,value2,對應的 YAML 為:
name:"value1,value2"
類似的,你還可以轉義.,當 chart 模板中使用 toYaml 函式來解析 annotations、labels 以及 node selectors 之類的時候,這非常有用,比如 --set nodeSelector."kubernetes\.io/role"=master,對應的 YAML 檔案:
nodeSelector:
kubernetes.io/role:master
深度巢狀的資料結構可能很難使用 --set 來表示,所以一般推薦還是使用 YAML 檔案來進行覆蓋,當然在設計 chart 模板的時候也可以結合考慮到 --set 這種用法,儘可能的提供更好的支援。

更多安裝方式

helm install 命令可以從多個源進行安裝:
  • chart 倉庫(類似於上面我們提到的)
  • 本地 chart 壓縮包(helm install foo-0.1.1.tgz)
  • 本地解壓縮的 chart 目錄(helm install foo path/to/foo)
  • 線上的 URL(helm install fool https://example.com/charts/foo-1.2.3.tgz)

升級或回滾

當新版本的 chart 包釋出的時候,或者當你要更改 release 的配置的時候,你可以使用 helm upgrade 命令來操作。升級需要一個現有的 release,並根據提供的資訊對其進行升級。因為 Kubernetes charts 可能很大而且很複雜,Helm 會嘗試以最小的侵入性進行升級,它只會更新自上一版本以來發生的變化:
$helmupgrade-fpanda.yamlmysqlstable/mysql
helmupgrade-fpanda.yamlmysqlstable/mysql
Release"mysql"hasbeenupgraded.HappyHelming!
NAME:mysql
LAST DEPLOYED:FriDec621:06:112019
NAMESPACE:default
STATUS:deployed
REVISION:2
...
我們這裡 mysql 這個 release 用相同的 chart 包進行升級,但是新增了一個配置項:
mysqlRootPassword: passw0rd
我們可以使用 helm get values 來檢視新設定是否生效:
$helmgetvaluesmysql
USER-SUPPLIED VALUES:
mysqlDatabase:user0db
mysqlPassword:user0pwd
mysqlRootPassword:passw0rd
mysqlUser:user0
persistence:
enabled:false
helm get 命令是檢視叢集中 release 的非常有用的命令,正如我們在上面看到的,它顯示了 panda.yaml 中的新配置值被部署到了叢集中,現在如果某個版本在釋出期間沒有按計劃進行,那麼可以使用 helm rollback [RELEASE] [REVISION] 命令很容易回滾到之前的版本:

$ helm ls

NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION

mysql

default22019-12-0621

:

06

:

11.36358+0800

CST deployed mysql

-1.5.05.7.27

$ helm history mysql

REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION

1

Fri

Dec617

:

53

:

032019

superseded mysql

-1.5.05.7.27

Install complete

2

Fri

Dec621

:

06

:

112019

deployed mysql

-1.5.05.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/1Running03

h25m

$ helm

getvalues

mysql

USER-

SUPPLIED

VALUES

:

mysqlDatabase: user0db

mysqlPassword: user0pwd

mysqlUser: user0

persistence:

enabled:

false

可以看到 values 配置已經回滾到之前的版本了。上面的命令回滾到了 release 的第一個版本,每次進行安裝、升級或回滾時,修訂號都會加 1,第一個修訂號始終為1,我們可以使用 helm history [RELEASE]來檢視某個版本的修訂號。
除此之外我們還可以指定一些有用的選項來定製 install/upgrade/rollback 的一些行為,要檢視完整的引數標誌,我們可以執行 helm <command> --help 來檢視,這裡我們介紹幾個有用的引數:
  • --timeout: 等待 Kubernetes 命令完成的時間,預設是 300(5分鐘)
  • --wait: 等待直到所有 Pods 都處於就緒狀態、PVCs 已經繫結、Deployments 具有處於就緒狀態的最小 Pods 數量(期望值減去 maxUnavailable)以及 Service 有一個 IP 地址,然後才標記 release 為成功狀態。它將等待與 –timeout 值一樣長的時間,如果達到超時,則 release 將標記為失敗。注意:在 Deployment 將副本設定為 1 並且作為滾動更新策略的一部分,maxUnavailable 未設定為0的情況下,–wait 將返回就緒狀態,因為它已滿足就緒狀態下的最小 Pod 數量
  • --no-hooks: 將會跳過命令的執行 hooks
  • --recreate-pods: 僅適用於 upgrade 和 rollback,這個標誌將導致重新建立所有的 Pods。(Helm3 中啟用了)

Charts

Helm 使用一種名為 charts 的包格式,一個 chart 是描述一組相關的 Kubernetes 資源的檔案集合,單個 chart 可能用於部署簡單的應用,比如 memcached pod,或者複雜的應用,比如一個帶有 HTTP 服務、資料庫、快取等等功能的完整 web 應用程式。
Charts 是建立在特定目錄下面的檔案集合,然後可以將它們打包到一個版本化的存檔中來部署。接下來我們就來看看使用 Helm 構建 charts 的一些基本方法。

檔案結構

chart 被組織為一個目錄中的檔案集合,目錄名稱就是 chart 的名稱(不包含版本資訊),下面是一個 WordPress 的 chart,會被儲存在 wordpress/ 目錄下面,基本結構如下所示:

wordpress/

Chart.yaml

# 包含當前 chart 資訊的 YAML 檔案

LICENSE

# 可選:包含 chart 的 license 的文字檔案

README.md

# 可選:一個可讀性高的 README 檔案

values.yaml

# 當前 chart 的預設配置 values

values.schema.json

# 可選: 一個作用在 values.yaml 檔案上的 JSON 模式

charts/

# 包含該 chart 依賴的所有 chart 的目錄

crds/

# Custom Resource Definitions

templates/

# 模板目錄,與 values 結合使用時,將渲染生成 Kubernetes 資源清單檔案

templates/NOTES.txt

# 可選: 包含簡短使用使用的文字檔案

另外 Helm 會保留 charts/、crds/ 以及 templates/ 目錄以及上面列出的檔名的使用。

Chart.yaml 檔案

對於一個 chart 包來說 Chart.yaml 檔案是必須的,它包含下面的這些欄位:
apiVersion:chartAPI版本(必須)
name:chart(必須)
version:SemVer2版本(必須)
kubeVersion:相容的Kubernetes版本(可選)
description:一句話描述(可選)
type:chart型別(可選)
keywords:
-當前專案關鍵字集合(可選)
home:當前專案的URL(可選)
sources:
-當前專案原始碼URL(可選)
dependencies:# chart 依賴列表 (可選)
-name:chart名稱(nginx)
version:chart版本("1.2.3")
repository:倉庫地址("https://example.com/charts")
maintainers:# (可選)
-name:維護者名字(對每個maintainer是必須的)
email:維護者的email(可選)
url:維護者URL(可選)
icon:chartSVG或者PNG圖示URL(可選).
appVersion:包含的應用程式版本(可選).不需要SemVer版本
deprecated:chart是否已被棄用(可選,boolean)
其他欄位預設會被忽略。

版本

每個 chart 都必須有一個版本號,版本必須遵循 SemVer2 標準,和 Helm Classic 不同,Kubernetes Helm 使用版本號作為 release 的標記,倉庫中的軟體包透過名稱加上版本號來標識的。
例如,將一個 nginx 的 chart 包 version 欄位設定為:1.2.3,則 chart 最終名稱為:

nginx-1.2.3.tgz

還支援更復雜的 SemVer2 名稱,例如版本:1.2.3-alpha.1+ef365,但是需要注意的是系統明確禁止使用非 SemVer 的名稱。
Chart.yaml 中的 version 欄位被很多 Helm 工具使用,包括 CLI 工具,生成包的時候,命令 helm package 將使用該欄位作為包名稱中的標記,系統是預設 Chart 包中的版本號與 chart.yaml 中的版本號匹配的,所以如果不匹配的話就導致一系列錯誤。

apiVersion 欄位

對於 Helm 3 以上的版本 apiVersion 欄位應該是 v2,之前版本的 Chart 應該設定為1,並且也可以有 Helm 3 進行安裝。

appVersion 欄位

要注意 appVersion 欄位與 version 欄位無關,這是一種指定應用程式版本的方法,比如 drupal 的 Chart 包可能有一個 appVersion: 8.2.1 的欄位,表示 Chart 中包含的 drupal 版本是 8.2.1,該欄位僅供參考,對 Chart 版本的計算不會產生影響。

棄用 Charts

當在 Chart 倉庫中管理 charts 的時候,有時候需要棄用一個 chart,Chart.yaml 中的可選欄位 deprecated 可以用來標記一個 chart 為棄用狀態。如果將倉庫中最新版本的 chart 標記為棄用,則整個 chart 都會被當做棄用狀態了。以後可以透過釋出一個未被標記為棄用狀態的新版本來重新使用該 chart。棄用 charts 的工作流程如下所示:
  • 更新 chart 的 Chart.yaml 來標記 chart 為棄用狀態
  • 釋出該新版本到 Chart 倉庫
  • 從原始碼倉庫(比如 git)中刪除 chart

Chart 型別

type 欄位定義 chart 的型別,可以定義兩種型別:應用程式(application)和庫(library)。應用程式是預設的型別,它是一個可以完整操作的標準 chart,庫或者輔助類 chart 為 chart 提供了一些實用的功能,library 不同於應用程式 chart,因為它沒有資源物件,所以無法安裝。
一個應用 chart 也可以當作庫進行使用。透過將型別設定為 library,然後該 chart 就會渲染成一個庫,可以在其中使用所有的實用性功能,chart 的所有資源物件都不會被渲染。

LICENSE, README 和 NOTES

Chart 還可以包含用於描述 chart 的安裝、配置、用法和許可證書的檔案。
LICENSE 是一個純文字檔案,其中包含 chart 的許可證書。chart 可以包含一個許可證書,因為它可能在模板中具有程式設計邏輯,所以不只是配置,如果需要,chart 還可以為應用程式提供單獨的 license(s)。
Chart 的 README 檔案應該採用 Markdown(README.md)格式,並且通常應該包含如下的一些資訊:
  • chart 提供的應用程式的描述資訊
  • 執行 chart 的任何先決條件或者要求
  • values.yaml 和預設值中的一些選項說明
  • 與 chart 的安裝或配置有關的任何其他資訊
chart 還可以包含簡短的純文字模板或者 NOTES.txt 檔案,該檔案將在安裝後以及檢視 release 狀態的時候打印出來。該檔案會被當成模板檔案,並且可以用於顯示使用說明,後續步驟或與 release 有關的任何其他資訊。例如,可以提供用於連線到資料或訪問 Web UI 的指令。由於在執行 helm install 或者 helm status 的時候該檔案會列印到 STDOUT 中,所以建議該檔案內容保持內容簡短然後可以指向 README 檔案來獲取更多詳細資訊。

依賴

在 Helm 中,一個 chart 包可能會依賴許多其他的 chart。這些依賴關係可以使用 Chart.yaml 中的依賴關係欄位動態連結,也可以引入到 charts/ 目錄手動進行管理。

使用 dependencies 欄位管理依賴

當前 chart 所需的依賴 chart 需要在 dependencies 欄位中進行定義,如下所示:
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 欄位是所依賴的 chart 的名稱
  • version 欄位是依賴的 chart 版本
  • repository 欄位是 chart 倉庫的完整 URL,不過需要注意,必須使用 helm repo add 在本地新增該 repo
定義了依賴項後,可以執行 helm dependency update 來更新依賴項,它將根據你的依賴項檔案把你所有指定的 chart 包下載到 charts/ 目錄中:

$ helm dependency

update

foochart

Hang tight while we grab the latest

from

your chart repositories...

...Successfully got an

updatefrom

the "local" chart repository

...Successfully got an

updatefrom

the "stable" chart repository

...Successfully got an

updatefrom

the "example" chart repository

...Successfully got an

updatefrom

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

當執行 helm dependency update 命令的時候會解析 chart 的依賴項,會將他們作為 chart 包檔案下載存放到 charts/ 目錄中,所以,對於上面的示例,我們可以在 charts 目錄中看到如下的檔案:

charts/

apache-1.2.3.tgz

mysql-3.2.1.tgz

alias 欄位

除了上面的幾個欄位之外,每個依賴項還可以包含一個可選的 alias 別名欄位。為依賴 chart 新增別名將使用別名作為依賴的名稱。在需要訪問其他名稱的 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個依賴項:

subchart

new-

subchart-1

new-

subchart-2

當然其實我們也可以手動來實現,將同一個 chart 以不同的名稱多次複製/貼上到 charts/ 目錄中也是可以的。

TEMPLATES 和 VALUES

Helm Chart 模板是用 Go template 語言 進行編寫的,另外還額外增加了(【Sprig】](https://github.com/Masterminds/sprig)庫中的50個左右的附加模板函式和一些其他專用函式。
所有模板檔案都儲存在 chart 的 templates/ 目錄下面,當 Helm 渲染 charts 的時候,它將透過模板引擎傳遞該目錄中的每個檔案。模板的 Values 可以透過兩種方式提供:
  • Chart 開發人員可以在 chart 內部提供一個名為 values.yaml 的檔案,該檔案可以包含預設的 values 值內容。
  • Chart 使用者可以提供包含 values 值的 YAML 檔案,可以在命令列中透過 helm install 來指定該檔案。
當用戶提供自定義 values 值的時候,這些值將覆蓋 chart 中 values.yaml 檔案中的相應的值。

模板檔案

模板檔案遵循編寫 Go 模板的標準約定(可以檢視 text/template 包文件檢視詳細資訊),下面是一個模板檔案示例:
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

}}

上面這個示例是 Kubernetes replication 控制器的一個模板,它可以使用以下4個模板值(通常在 values.yaml 檔案中定義的):
  • imageRegistry:Docker 映象倉庫
  • dockerTag:Docker 映象 tag
  • pullPolicy:映象拉取策略
  • storage:儲存後端,預設設定為 "minio"
這些所有的 values 值都是有模板作者來定義的,Helm 不會也不需要規定這些引數。可以可以檢視 Kubernetes Charts 專案去了解更多的 charts 專案的詳細內容。

預定義 Values

在模板中用 .Values 可以獲取到 values.yaml 檔案(或者 –set 引數)提供的 values 值,此外,還可以在模板中訪問其他預定義的資料。下面是一些預定義的、可用於每個模板、並且不能被覆蓋的 values 值,與所有 values 值一樣,名稱都是區分大小寫的:
  • Release.Name:release 的名稱(不是 chart)
  • Release.Namespace:release 被安裝到的名稱空間
  • Release.Service:渲染當前模板的服務,在 Helm 上,實際上該值始終為 Helm
  • Release.IsUpgrade:如果當前操作是升級或回滾,則該值為 true
  • Release.IsInstall:如果當前操作是安裝,則該值為 true
  • Chart:Chart.yaml 檔案的內容,可以透過 Chart.Version 來獲得 Chart 的版本,透過 Chart.Maintainers 可以獲取維護者資訊
  • Files: 一個包含 chart 中所有非特殊檔案的 map 物件,這不會給你訪問模板的許可權,但是會給你訪問存在的其他檔案的許可權(除非使用 .helmignore 排除它們),可以使用 {{ index .Files "file.name" }} 或者 {{ .Files.Get name }} 或者 {{ .Files.GetString name }} 函式來訪問檔案,你還可以使用 {{ .Files.GetBytes }} 以 []byte 的形式獲取訪問檔案的內容
  • Capabilities:也是一個類 map 的物件,其中包含有關 Kubernetes 版本({{ .Capabilities.KubeVersion }})和支援的 Kubernetes API 版本({{ .Capabilities.APIVersions.Has "batch/v1" }})資訊。
任何未知的 Chart.yaml 欄位都會被刪除,在 Chart 物件內部無法訪問他們,所以,Chart.yaml 不能用於將任意結構化的資料傳遞到模板中,但是可以使用 values 檔案來傳遞。

Values 檔案

為模板提供一些必須的 values 值的 values.yaml 檔案如下所示:
imageRegistry:"quay.io/deis"
dockerTag:"latest"
pullPolicy:"Always"
storage:"s3"
values 檔案的格式是 YAML,一個 chart 包可能包含一個預設的 values.yaml 檔案,helm install 命令允許使用者透過提供其他的 YAML 值檔案來覆蓋預設的值:

$ helm install

--values=myvals.yaml wordpress

用這種方式來傳遞 values 值的時候,它們將合併到預設值檔案中,比如有一個 myvals.yaml 檔案如下所示:
storage:"gcs"
將其與 chart 的 values.yaml 檔案合併後,得到的結果為:
imageRegistry:"quay.io/deis"
dockerTag:"latest"
pullPolicy:"Always"
storage:"gcs"
我們可以看到只有最後一個欄位被覆蓋了。
chart 內包含的預設 values 檔案必須命名為 values.yaml,但是在命令列上指定的檔案可以任意命名。 如果在 helm install 或者 helm upgrade 的時候使用 –set 引數,則這些值將在客戶端轉換為 YAML 格式。 如果 values 檔案存在任何必須的條目,則可以使用 required 函式在 chart 模板中將它們宣告為必須選項。
然後我們就可以使用 .Values 物件在模板中訪問任意一個 values 值,類似於下面的模板檔案:
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

}}

作用範圍、依賴和 Values

values 檔案可以宣告頂級的 chart 以及該 chart 的 charts/ 目錄中包含的任何 chart 的值。或者,換句話說,values 檔案可以為 chart 以及他的任何依賴項提供 values 值。例如,上面提到了 WordPress 這個 chart 同時依賴 mysql 和 apache 這兩個依賴項,values 檔案可以為所有這些元件提供 values 值:
title:"My WordPress Site"# 傳遞到 WordPress 模板

mysql:
max_connections:100# 傳遞到 MySQL
password:"secret"

apache:
port:8080# 傳遞到 Apache

較高級別的 Charts 可以訪問下面定義的所有變數,所以,WordPress 這個 chart 可以透過 .Values.mysql.password 來訪問 MySQL 的密碼,但是較低級別的 chart 是無法訪問父 chart 中的內容的,所有 MySQL 無法獲取到 title 屬性,當然同樣也不能訪問 apache.port。
Values 是有名稱空間的,但是會對其進行調整,比如對於 WordPress 這個 chart 來說,它可以透過 .Values.mysql.password 來進行訪問,但是對於 MySQL 這個 chart 本身來說,values 的範圍縮小了,名稱空間字首會被刪除,所以它只需要透過 .Values.password 就可以訪問到。

全域性 Values

從 2.0.0-Alpha.2 版本開始,Helm 開始支援特殊的 global 全域性值,比如將上面的示例修改如下:
title:"My WordPress Site"# 傳遞到 WordPress 模板

global:
app:MyWordPress

mysql:
max_connections:100# 傳遞到 MySQL
password:"secret"

apache:
port:8080# 傳遞到 Apache

上面我們添加了一個全域性範圍的 value 值:app: MyWordPress,該值可以透過 .Values.global.app 提供給所有 chart 使用。
例如,mysql 模板可以以 {{ .Values.global.app }} 來訪問 app,apache chart 也可以,實際上,上面的 values 檔案會這樣重新生成:
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 共享一個頂級變數的方式,這對於設定 meta 資料這種屬性是非常有用的。如果子 chart 聲明瞭全域性變數,則該全域性變數將向下(傳遞到子 chart 的子 chart 中)傳遞,而不會向上傳遞到父 chart,子 chart 無法影響 父 chart的值。同樣,父 chart 的全域性遍歷優先與子 chart 中的全域性變數。

Schema 檔案

有時候,chart 開發者可能希望在其 values 值上面定義一個結構,這種情況下可以透過在 values.schema.json 檔案中定義一個 schema 來完成,這裡的 schema 就是一個 JSON Schema 檔案結構規範,如下所示:

{

"$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 會對 values 值進行校驗,呼叫以下任何命令時,都會進行驗證:
  • helm install
  • helm upgrade
  • helm lint
  • helm template
比如下面的示例檔案就可以滿足上面的 schema 要求:
name: frontend
protocol: https
port: 443
需要注意的是該 schema 將應用於最終的 .Values 物件,而不僅僅是應用於 values.yaml 檔案,所以下面的檔案也是可以滿足 schema 要求的:
name: frontend
protocol: https
因為在安裝的時候我們透過 --set 選項傳遞了必須的 port 屬性:
$

helm install --

set

port=443

此外,還會根據所有的子 chart schemas 來檢查最終的 .Values 物件,這意味著父 chart 無法規避對子 chart 的限制。同樣的,如果子 chart 要求未滿足子 chart 的 values.yaml 檔案,則父 chart 必須滿足這些限制才能生效。

參考文件

在編寫模板、values、和 schema 檔案的時候,下面這些文件可以提供一些幫助:
  • Go Template
  • 額外的模板函式
  • YAML 檔案
  • JSON Schema

CRDS

Kubernetes 提供了一種宣告新型別的 Kubernetes 物件的機制,使用 CustomResourceDefinitions(CRDS)可以讓 Kubernetes 開發人員宣告自定義資源型別。
在 Helm 3 中,CRD 被視為一種特殊的物件,它們在 chart 部分之前被安裝,並且會受到一些限制。CRD YAML 檔案應該放置 chart 內的 crds/ 目錄下面。多個 CRDs 可以放在同一個檔案中,Helm 將嘗試將 CRD 目錄中的所有檔案載入到 Kubernetes 中。
需要注意的是 CRD 檔案不能模板化,它們必須是純的 YAML 檔案。
當 Helm 安裝一個新的 chart 的時候,它將會安裝 CRDs,然後會暫停直到 API Server 提供 CRD 為止,然後才開始啟動模板引擎,渲染其餘的 chart 模板,並將其安裝到 Kubernetes 中。由於這個安裝順序,CRD 資訊在 Helm 模板的 .Capabilities 物件中是可以獲取到的,並且 Helm 模板可能會建立在 CRD 中宣告的物件的新例項。
比如,如果你的呃 chart 在 crds 目錄下面有一個 CronTab 的 CRD,則可以在 templates/ 目錄下面建立 CronTab 型別的例項:

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 可以建立一個新的 CronTab(和平時使用模板一樣):
apiVersion:stable.example.com
kind:CronTab
metadata:
name:

{{

.Values.name

}}

spec:
# ...

在繼續安裝 templates/ 之前,Helm 會確保已經安裝上了 CronTab 型別,並且可以從 Kubernetes API server 上獲得該型別。

CRDs 的限制

與 Kubernetes 中的大多數物件不同,CRDs 是全域性安裝的,所以 Helm 在管理 CRD 的時候比較謹慎,會有一些限制:
  • CRDs 不會重新安裝,如果 Helm 確定 crds/ 目錄中的 CRD 已經存在(無論版本如何),Helm 都不會重新安裝或升級。
  • CRDs 不會在升級或回滾的時候安裝,只會在安裝操作的時候建立 CRDs。
  • CRDs 不會被刪除,刪除 CRD 會自動刪除叢集中所有 namespace 中的 CRDs 內容,所以 Helm 不會刪除 CRD。
Helm 希望想要升級或刪除 CRDs 的操作人員可以手動來仔細地操作。

使用 Helm 管理 Charts

helm 工具有幾個用於操作 charts 的命令,如下所示。
建立一個新的 chart 包:

$ helm

create

mychart

Created mychart/

一旦你已經編輯了一個 chart 包,Helm 可以將其打包到一個獨立檔案中:

$ helm

package

mychart

Archived mychart

-0.1

.-.tgz

你還可以使用 helm 幫助你查詢 chart 包的格式要求方面或其他問題:
$

helm lint mychart

No issues found

Chart 倉庫

chart 倉庫實際上就是一個 HTTP 伺服器,其中包含一個或多個打包的 chart 包,雖然可以使用 helm 來管理本地 chart 目錄,但是在共享 charts 的時候,最好的還是使用 chart 倉庫。
可以提供 YAML 檔案和 tar檔案並可以相應 GET 請求的任何 HTTP 伺服器都可以作為 chart 倉庫伺服器。倉庫的主要特徵是存在一個名為 index.yaml 的特殊檔案,該檔案具有倉庫中提供的所有軟體包的列表以及允許檢索和驗證這些軟體包的元資料。
在客戶端,可以使用 helm repo 命令來管理倉庫,但是 Helm 不提供用於將 chart 上傳到遠端 chart 倉庫的工具。

模板開發

內建物件

前面我們介紹了 Helm Chart 的一些基本概念和使用,接下來我們重點介紹下 Chart 模板的編寫。模板會渲染成 Kubernetes 的資源清單檔案,下面我們將來學習下模板的結構,如何使用它們,如何編寫 Go 模板以及如何除錯。
物件從模板引擎傳遞到模板中,在程式碼中可以傳遞物件,也有一種方法可以在模板宏建立新的物件,比如 tuple 函式。物件可以很簡單,也可以包含其他物件或函式,例如,Release 物件就包含幾個物件(比如 Release.Name),Files 物件就包含幾個函式。
前面提到過我們可以在模板中使用 {{ .Release.Name }} 獲取 release 的名稱,Release 是我們可以在模板中訪問的幾個頂級物件之一:
  • Release:該物件描述了 release 本身的相關資訊,它內部有幾個物件:
    • Release.Name:release 名稱
    • Release.Namespace:release 安裝到的名稱空間
    • Release.IsUpgrade:如果當前操作是升級或回滾,則該值為 true
    • Release.IsInstall:如果當前操作是安裝,則將其設定為 true
    • Release.Revision:release 的 revision 版本號,在安裝的時候,值為1,每次升級或回滾都會增加
    • Reelase.Service:渲染當前模板的服務,在 Helm 上,實際上該值始終為 Helm
  • Values:從 values.yaml 檔案和使用者提供的 values 檔案傳遞到模板的 Values 值,預設情況下,Values 是空的。
  • Chart:獲取 Chart.yaml 檔案的內容,該檔案中的任何資料都可以訪問,例如 {{ .Chart.Name }}-{{ .Chart.Version}} 可以渲染成 mychart-0.1.0,該物件下面可用的欄位前面我們已經提到過了。
  • Files:可以訪問 chart 中的所有非特殊檔案,雖然無法使用它來訪問模板檔案,但是可以來訪問 chart 中的其他檔案。
    • Files.Get:用於根據名稱獲取檔案(比如 .Files.Get config.ini)
    • Files.GetBytes:用於以 bytes 陣列而不是字串的形式來獲取檔案內容的函式,這對於類似於圖片之類的東西很有用
    • Files.Glob:用於返回名稱於給定的 shell glob 模式匹配的檔案列表
    • Files.Lines:可以逐行讀取檔案的函式,對於遍歷檔案中的每行內容很有用
    • Files.AsSecrets:將檔案內容以 Base64 編碼的字串返回的函式
    • Files.AsConfig:將檔案正文作為 YAML 字典返回的函式
  • Capabilities:提供了獲取有關 Kubernetes 叢集支援功能的資訊的物件
    • Capabilities.APIVersions:支援的版本集合
    • Capabilities.APIVersions.Has $version:判斷一個版本(比如 batch/v1)或資源(比如 apps/v1/Deployment)是否可用
    • Capabilities.Kube.Version:Kubernetes 的版本
    • Capabilities.Kube:是 Kubernetes 版本的縮寫
    • Capabilities.Kube.Major:Kubernetes 主版本
    • Capabilities.Kube.Minor:Kubernetes 的次版本
  • Template:包含當前正在執行的模板的相關資訊
    • Name:當前模板的名稱空間檔案路徑(比如 mychart/templates/mytemplate.yaml)
    • BaePath:當前 chart 的模板目錄的名稱空間路徑(比如 mychart/templates)
需要注意的是內建的物件始終是以大寫字母開頭的,這也是符合 Go 的命名約定的,建立自己的名稱的時候,可以自由使用適合你團隊的約定,一些團隊,比如 Kubernetes Charts 團隊,選擇僅使用首字母小寫,以區分本地名稱和內建名稱,這裡我們也會遵循該約定。

Values

前面我們介紹了 Helm 模板提供的內建物件,其中就有一個內建物件 Values,該物件提供對傳遞到 chart 忠的 values 值的訪問,其內容主要有4個來源:
  • chart 檔案中的 values.yaml 檔案
  • 如果這是子 chart,父 chart 的 values.yaml 檔案
  • 用 -f 引數傳遞給 helm install 或 helm upgrade 的 values 值檔案(例如 helm install -f myvals.yaml ./mychart)
  • 用 --set 傳遞的各個引數(例如 helm install –set foo=bar ./mychart)
values.yaml 檔案是預設值,可以被父 chart 的 values.yaml 檔案覆蓋,而後者又可以由使用者提供的 values 值檔案覆蓋,而該檔案又可以被 --set 引數覆蓋。
values 值檔案是純 YAML 檔案,我們可以來編輯 mychart/values.yaml 檔案然後編輯 ConfigMap 模板。刪除 values.yaml 中的預設設定後,我們將只設置一個引數:
favoriteDrink: coffee
現在我們可以在模板中直接使用它:
apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap
data:
myvalue:"Hello World"
drink:

{{

.Values.favoriteDrink

}}

可以看到在最後一行我們將 favoriteDrink 作為 Values 的屬性進行訪問:{{ .Values.favoriteDrink }}。我們可以來看看是如何渲染的:
$helminstall--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

NAME:mychart-1575963545
LAST DEPLOYED:TueDec1015:39:062019
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

由於在預設的 values.yaml 檔案中將 favoriteDrink 設定為了 coffee,所以這就是模板中顯示的值,我們可以透過在呼叫 helm install 的過程中新增 --set 引數來覆蓋它:
$helminstall--generate-name--dry-run--debug--setfavoriteDrink=slurm./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

NAME:mychart-1575963760
LAST DEPLOYED:TueDec1015:42:432019
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

因為 --set 的優先順序高於預設的 values.yaml 檔案,所以我們的模板會生成 drink: slurm。Values 值檔案也可以包含更多結構化的內容,例如我們可以在 values.yaml 檔案中建立一個 favorite 的部分,然後在其中新增幾個 keys:
favorite:
drink:coffee
food:pizza
現在我們再去修改下我們的模板:
apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap
data:
myvalue:"Hello World"
drink:

{{

.Values.favorite.drink

}}

food:

{{

.Values.favorite.food

}}

雖然我們可以透過這種方式來構造資料,但是還是建議你將 values 樹保持更淺,這樣在使用的時候更加簡單。當我們考慮為子 chart 分配 values 值的時候,我們就可以看到如何使用樹形結構來命名 values 值了。

刪除預設 KEY

如果你需要從預設值中刪除 key,則可以將該 key 的值覆蓋為 null,在這種情況下,Helm 將從覆蓋的 values 中刪除該 key。例如,在 Drupal chart 中配置一個 liveness 探針:
livenessProbe:
httpGet:
path:/user/login
port:http
initialDelaySeconds:120
如果你想使用 --set livenessProbe.exec.command=[cat, docroot/CHANGELOG.txt] 將 livenessProbe 的處理程式覆蓋為 exec 而不是 httpGet,則 Helm 會將預設鍵和覆蓋鍵合併在一起,如下所示:
livenessProbe:
httpGet:
path:/user/login
port:http
exec:
command:
-cat
-docroot/CHANGELOG.txt
initialDelaySeconds:120
但是,這樣卻有一個問題,因為你不能宣告多個 livenessProbe 處理程式,為了解決這個問題,你可以讓 Helm 透過將 livenessProbe.httpGet 設定為 null 來刪除它:
$

helm install stable/drupal --

set

image=my-registry/drupal:0.1.0 --

set

livenessProbe.exec.command=[

cat

, docroot/CHANGELOG.txt] --

set

livenessProbe.httpGet=null

到這裡我們已經瞭解到了幾個內建物件,並利用它們將資訊注入到了模板中,現在我們來看看模板引擎的另外方面:函式和管道。

函式與管道

在我們已經瞭解瞭如何將資訊加入到模板中,但是這些資訊都是直接原樣的放置過去的,有時候,我們希望以一種對我們更有用的方式來轉換提供的資料。
下面讓我們從一個最佳實踐開始:將 .Values 物件中的字串注入模板時,我們應該引用這些字串,我們可以透過在 template 指令中呼叫 quote 函式來實現,比如:
apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap
data:
myvalue:"Hello World"
drink:

{{

quote.Values.favorite.drink

}}

food:

{{

quote.Values.favorite.food

}}

模板函式遵循的語法規則是 functionName arg1 arg2…,在上面的程式碼片段中,quote .Values.favorite.drink 會呼叫 quote 函式並傳遞一個單個引數。
Helm 有60多種可用的函式,其中一些是由 Go 模板語言本身定義的,其他大多數都是 Sprig 模板庫提供的,接下來我們會透過部分示例來逐步介紹其中的一些功能函式。
當我們談論 Helm 模板語言 的時候,就好像是特定於 Helm 一樣,但實際上它是 Go 模板語言加上一些額外的函式以及各種封裝程式的組合,以將某些物件暴露給模板。當我們需要學習模板的時候,Go 模板上有許多資源會對我們有所幫助的。

管道

模板語言有一個強大的功能就是管道(Pipeline)概念,管道利用 UNIX 的概念,將一系列模板命令連結在一起,一起對外提供服務,換句話說,管道是按順序完成多項工作的有效方式,我們來使用管道重寫上面的示例模板:
apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap
data:
myvalue:"Hello World"
drink:

{{

.Values.favorite.drink|quote

}}

food:

{{

.Values.favorite.food|quote

}}

在這裡我們沒有呼叫 quote ARGUMENT 函式,而是顛倒了下順序,我們使用管道符(|)將引數傳送給函式:.Values.favorite.drink | quote,使用管道,我們可以將多個功能連結在一起:
apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap
data:
myvalue:"Hello World"
drink:

{{

.Values.favorite.drink|quote

}}

food:

{{

.Values.favorite.food|upper|quote

}}

管道順序: 反轉順序是模板中常見的做法,我們會看到 .val | quote 比 quote .val 用法更多,雖然兩種方法都是可以的。
最後,模板渲染後,會產生如下所示的結果:
$helminstall--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

NAME:mychart-1575966483
LAST DEPLOYED:TueDec1016:28:042019
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 值已經被轉換成了 "PIZZA"。當這樣傳遞引數的時候,第一個求值結果(.Values.favorite.drink)會作為一個引數傳送給函式,我們可以修改上面的 drink 示例,用一個帶有兩個引數的函式進行說明:repeat COUNT STRING。
apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap
data:
myvalue:"Hello World"
drink:

{{

.Values.favorite.drink|repeat5|quote

}}

food:

{{

.Values.favorite.food|upper|quote

}}

repeat 函式將重複字串給定的次數,渲染後我們可以得到如下的輸出結果:
# Source: mychart/templates/configmap.yaml
apiVersion:v1
kind:ConfigMap
metadata:
name:mychart-1575966939-configmap
data:
myvalue:"Hello World"
drink:"coffeecoffeecoffeecoffeecoffee"
food:"PIZZA"

default 函式

在模板中經常會使用到的一個函式是 default 函式:default DEFAULT_VALUE GIVEN_VALUE,該函式允許你在模板內部指定預設值,我們來修改上面示例中的模板:

food: {{ .Values.favorite.food |

default"rice"

| upper | quote }}

正常執行,我們還是可以得到 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"
現在我們從 values.yaml 檔案中移除 food 的定義:
favorite:
drink:coffee
# food: pizza
現在我們重新執行 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"
在一個真實的 chart 模板中,所有的靜態預設值都應位於 values.yaml 檔案中,並且不應該重複使用 default 函式,但是,預設命令非常適合計算不能在 values.yaml 檔案中宣告的 values 值,例如:

food: {{ .Values.favorite.food | default (

printf"%s-rice"

(include

"fullname"

.)) }}

不過在有些地方,if 條件語句可能比 default 函式更合適,我們會在後面瞭解到。
模板函式和管道是將資料轉換後然後將其插入到 YAML 檔案中的一種強大方法,但是有的時候有必要新增一些模板邏輯,這些邏輯比僅僅插入字串要複雜得多,下面我們將來了解模板語言中提供的控制流程。

運算子函式

另外需要注意的是在模板中,運算子(eq、ne、lt、gt、and、or 等等)均實現為函式,在管道中,運算子可以用括號()進行分割。
接下來我們可以去了解控制流程條件語句、迴圈和作用域修飾符的使用。

流程控制

控制流程為模板作者提供了控制模板生成流程的功能,Helm 的模板語言提供了以下一些流程控制:
  • if/else 條件語句
  • with 指定一個作用域範圍
  • range 提供類似於 for each 這樣的迴圈樣式
除此之外,還提供了一些宣告和使用命名模板的操作:
  • define 在模板內部宣告一個新的命名模板
  • template 匯入一個命名模板
  • block 聲明瞭一種特殊的可填充模板區域。
這裡我們先來了解 if、with、range 語句的使用,其他將在後面的命名模板部分介紹。

if/else

首先我們先來了解下有條件地在模板中包含一個文字區域,就是 if/else ,這個條件判斷的基本結構如下所示:

{{

if

PIPELINE }}

# Do something

{{

elseif

OTHER PIPELINE }}

# Do something else

{{

else

}}

# Default case

{{ end }}

可以看到我們這裡判斷的是管道而不是一個 values 值,這是因為控制結構可以執行整個管道,而不僅僅是判斷值。如果值為以下的一些內容,則將管道判斷為 false:
  • 布林 false
  • 數字零
  • 一個空字串
  • nil(empty 或者 null)
  • 一個空集合(map、slice、tuple、dict、array)
在其他條件下,條件都為真。
現在我們在上面的示例模板 ConfigMap 中新增一個簡單的條件,如果 drink 設定為 coffee,我們就新增另外一個設定:
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

}}

{{

ifeq.Values.favorite.drink"coffee"

}}

mug:true

{{

end

}}

我們把 values.yaml 檔案內容設定成下面的樣子:
favorite:
# drink: coffee
food:pizza
由於我們註釋掉了 drink: coffee,所以渲染後輸出不會包含 mug: true 的標誌,但是如果我們把註釋取消掉,則應該輸出如下所示的內容:
# Source: mychart/templates/configmap.yaml
apiVersion:v1
kind:ConfigMap
metadata:
name:mychart-1575970308-configmap
data:
myvalue:"Hello World"
drink:"coffee"
food:"PIZZA"
mug:true
這是因為上面模板中我們添加了 if eq .Values.favorite.drink "coffee" 這樣的條件判斷,相當於是判斷 .Values.favorite.drink 值是否等於 "coffee",如果相等則渲染 mug: true。

空格控制

還有一個非常重要的功能點就是關於空格的控制,因為空格對於 YAML 檔案非常重要的,不是說任意縮排就可以,依然還是以前面的例子為例,我們來格式化下模板格式以更易於閱讀:
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

}}

{{

ifeq.Values.favorite.drink"coffee"

}}

mug:true

{{

end

}}

現在我們的模板看上去更易於閱讀了,但是我們透過模板引擎來渲染下,卻會得到如下的錯誤資訊:

$ 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

erroron

mychart/templates/configmap.yaml:

error

converting YAML

to

JSON: yaml:

line9

: did

not

find expected key

這是因為我們在模板中添加了空格,生成了不正確的 YAML 檔案:
apiVersion:v1
kind:ConfigMap
metadata:
name:mychart-1575970308-configmap
data:
myvalue:"Hello World"
drink:"coffee"
food:"PIZZA"
mug:true
我們可以看到 mug: true 的縮排是有問題的,不符合 YAML 檔案格式,現在我們講縮排去掉試看看:
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

}}

{{

ifeq.Values.favorite.drink"coffee"

}}

mug:true

{{

end

}}

重新渲染模板,然後可以發現已經可以正常通過了,但是渲染出來的 YAML 檔案格式看上去還是有點奇怪:
# 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 檔案中多了一些空行,這是因為模板引擎渲染的時候它會刪除 {{ 和 }} 之間的內容,但是會完全保留其餘的空格。我們知道在 YAML 檔案中空格是有意義的,所以管理空格就變得非常重要了,不過 Helm 模板也提供了一些工具來幫助我們管理空格。
首先可以使用特殊字元修改模板宣告的花括號語法,以告訴模板引擎去掉空格。{{- 添加了破折號和空格表示應將左邊的空格移除,-}}表示將右邊的空格移除,另外也需要注意的是,換行符也是空格。
需要注意的時候要確保 - 和指令的其餘部分之間要有空格,{{- 3 }} 表示刪除左邊的空格並列印3,但是{{-3 }}表示列印-3
使用這個語法,我們可以修改上面的模板來移除多餘的空行:
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

}}

{{

-ifeq.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
為了更加清楚地說明這個問題,我們用*來代替將要刪除的每個空格,行尾的*表示被刪除的換行符:
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

}}

*
**{{-ifeq.Values.favorite.drink"coffee"}}
mug:true*
**{{-end}}

所以我們這裡用 {{- 表示的就是刪除本行開頭的兩個空格以及上一行的換行符,這樣是不是就將空行都刪除了啊。
在使用移除空格的時候還需要小心,比如下面的操作:

food: {{ .Values.favorite.food |

upper

| quote }}

{{-

if

eq .Values.favorite.drink

"coffee"

-}}

mug:

true

{{-

end

-}}

我們依然還是可以用 * 來代替空格進行分析,如下所示:
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

}}

*
**{{-ifeq.Values.favorite.drink"coffee"-}}*
mug:true*
**{{-end-}}

第一個 {{- 會刪除前面的空格和前面的換行符,然後後面的-}} 會刪除當前行的換行符,這樣就會把 mug: true 移動到 food: "PIZZA" 後面去了,最終渲染過後就會變成:food: "PIZZA"mug: true,因為在兩側都去掉換行符。
有關模板中空格控制的詳細資訊,可以檢視 Go 模板官方文件介紹。
不過有時候告訴模板系統如何縮排比起去控制模板指令的間距更加容易,所以,有時候你會發現縮排函式({{ indent 2 "mug: true" }})更有用。

使用 with 修改作用域

接下來需要了解的是 with 操作,它可以控制變數的作用域,然後重新用 . 呼叫就表示對當前作用域的引用,所以,.Values 是告訴模板引擎在當前作用域下內查詢 Values 物件。
with 語句的語法和 if 語句比較類似:

{{

with

PIPELINE }}

# 限制範圍

{{ end }}

範圍可以更改,可以讓你將當前範圍 . 設定為特定的物件,例如,我們一直在使用 Values.favorites,讓我們重寫下模板檔案 ConfigMap 來更改 . 的範圍指向 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

}}

我們這裡將前面練習的 if 條件語句刪除了,在模板中我們添加了一個 {{- with .Values.favorite }} 的語句,意思就是說在 with 語句的作用範圍內可以用 . 表示 .Values.favorite 了,所以我們可以引用 .drink 和 .food 了,但是在 {{ end }} 之後就會重置為之前的作用域了。
不過需要注意得是,在受限的作用域範圍內,你無法從父級範圍訪問到其他物件,比如,下面得模板會失敗:

{{-

with.Values.favorite

}}

drink: {{

.drink

|

default"tea"

| quote }}

food: {{

.food

| upper | quote }}

release

: {{

.Release.Name

}}

{{-

end

}}

因為 Release.Name 並不在 . 的限制範圍內,所以會產生錯誤,但是,如果我們交換最後兩行,則就可以正常工作了,因為 {{ end }} 之後會重置作用域。

{{-

with.Values.favorite

}}

drink: {{

.drink

|

default"tea"

| quote }}

food: {{

.food

| upper | quote }}

{{-

end

}}

release

: {{

.Release.Name

}}

下面我先來了解下 range,然後我們再去了解下模板變數,它可以為上面得這個範圍問題提供一種解決方案。

range 迴圈操作

我們知道許多程式語言都支援使用 for 迴圈、foreach 迴圈或者類似功能機制進行迴圈迭代,在 Helm 得模板語言中,迭代集合得方法是使用 range 運算子。
比如首先我們在 values.yaml 檔案中新增一份 pizza 餡料列表:

favorite:

drink: coffee

food: pizza

pizzaToppings:

-

mushrooms

-

cheese

-

peppers

-

onions

現在我們有了 pizzaToppings 列表(在模板中稱為切片),我們可以來修改下模板將列表列印到 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 }}

我們仔細觀察下模板中的 toppings: 列表,range 函式將遍歷 Values.pizzaToppings 列表,我們看到裡面使用了一個 .,類似於上面我們用 with 設定範圍一樣,運算子也是這樣的,每次迴圈,. 都會被設定為當前的 pizzaTopping,也就是說第一次設定為mushrooms,第二次迭代設定為cheese,依次類推。
我們可以直接傳遞 . 這個值到管道上,所以我們這裡 {{ . | title | quote }} 就相當於傳送 . 給 title(標題大小寫函式)函式,然後傳送給 quote 函式,我們渲染這個模板,會輸出如下的內容:
# 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: |- 行表示宣告一個多行字串,所以其實我們的 toppings 列表不是一個 YAML 列表,而是一個比較大的字串,這是因為 ConfigMap 中的資料由 key/value 對組成,所有 key 和 value 都是簡單的字串,要了解為什麼是這樣的,可以檢視 Kubernetes ConfigMap 文件,不過這個細節對我們這裡不重要。
多行字串可以使用|保留換行符,也可以使用>摺疊換行,如:
this:

|

Foo

Bar

that:

>

Foo

Bar


對應的意思就是:{this:'Foo\nBar\n',that:'Foo Bar\n'}

+表示保留文字塊末尾的換行,-表示刪除字串末尾的換行,如:
s1:|
Foo

s2:|+
Foo

s3:|-
Foo

對應的意思就是:{s1:'Foo\n',s2:'Foo\n\n\n',s3:'Foo'}

有時候,在模板中快速建立一個列表,然後遍歷該列表很有用,Helm 模板具有簡化該功能的函式:tuple。元組是固定大小的列表集合,但是具有任意資料型別,下面是元組的大概使用方法:

sizes: |-

{{-

rangetuple"small""medium""large"

}}

- {{ . }}

{{- end }}

上面的模板最終會被渲染成如下的 YAML:

sizes: |-

-

small

-

medium

-

large

除了列表和元組之外,range 還可以用於遍歷字典,我們在下一節介紹模板變數的時候再來了解這個用法吧。

變數

有了函式、管道、物件以及控制結構,我們可以想象下大多數程式語言中更基本的思想之一:變數。在模板中,變數的使用頻率較低,但是,我們還是可以使用他們來簡化程式碼,以及更好地使用 with 和 range。
在前面的示例中,我們知道下面的模板渲染會出錯:

{{-

with.Values.favorite

}}

drink: {{

.drink

|

default"tea"

| quote }}

food: {{

.food

| upper | quote }}

release

: {{

.Release.Name

}}

{{-

end

}}

因為 Release.Name 不在 with 語句塊限制的範圍之內,解決作用域問題的一種方法是將物件分配給在不考慮當前作用域情況下訪問的變數。
在 Helm 模板中,變數是對另外一個物件的命名引用。它遵循 $name 格式,變數使用特殊的賦值運算子進行賦值 :=,我們可以修改上面的模板,為 Release.Name 宣告一個變數:
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 語句之前,我們先分配了 $relname := .Release.Name,然後在 with 語句塊中,$relname 變數仍然表示 release 的名稱,我們渲染該模板,可以得到如下的正確結果:
# 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 迴圈裡面非常有用,它們可以用於類似於列表的物件來捕獲索引和 value 值:

toppings: |-

{{- range

$index

,

$topping

:= .Values.pizzaToppings }}

{{

$index

}}: {{

$topping

}}

{{- end }}

注意 range 在前面,然後是變數,然後是賦值運算子,然後才是列表,這會將整數索引(從0開始)分配給 $index,並將 value 值分配給 $topping,上面的內容會被渲染成如下內容:
toppings:

|-

0: mushrooms

1: cheese

2: peppers

3: onions

對於同時具有 key 和 value 的資料結構,我們也可以使用 range 來獲得 key、value 的值,比如,我們可以像這樣迴圈遍歷 .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。執行上面的命令將生成下面的內容:
# Source: mychart/templates/configmap.yaml
apiVersion:v1
kind:ConfigMap
metadata:
name:mychart-1575983119-configmap
data:
myvalue:"Hello World"
drink:"coffee"
food:"pizza"
一般來說變數不是全域性的,它們的作用域是宣告它們的塊區域,之前,我們在模板的頂層分配了 $relname,該變數將在整個模板的範圍內,但是在我們上面的示例中,$key 和 $val 作用域只在 {{ range... }}{{ end }} 區域內。
但是,有一個始終是全域性變數的 $ 始終指向頂層根上下文,當我們在 range 迴圈內需要知道 chart 包的 release 名稱的時候,該功能就非常有用了,比如下面的模板檔案:

{{

-range.Values.tlsSecrets

}}

apiVersion:v1
kind:Secret
metadata:
name:

{{

.name

}}

labels:
# helm 模板經常使用 `.`,但是這裡是無效的,用 `$` 是可以生效的。
app.kubernetes.io/name:

{{

template"fullname"$

}}

# 這裡不能引用 `.Chart.Name`,但是可用使用 `$.Chart.Name`
helm.sh/chart:"{{ $.Chart.Name }}-{{ $.Chart.Version }}"
app.kubernetes.io/instance:"{{ $.Release.Name }}"
# 值來自於 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

}}

到現在為止,我們只研究了在一個檔案中宣告的一個模板,但是,Helm 模板語言的強大功能之一是它能夠宣告多個模板並將其一起使用。我們將在下面的章節中來討論這一點。

命令模板

前面我們都是隻操作的一個模板,現在我們來嘗試使用多個模板檔案。在本節中,我們可以瞭解到如何在一個檔案中定義命名模板,然後在其他地方使用它們。命名模板(有時也叫子模板)只是在檔案內部定義的有名稱的模板。主要有兩種建立方式以及幾種不同的使用方式。

當使用命名模板的時候有幾個重要細節:模板名稱是全域性的,如果宣告兩個具有相同名稱的模板,則會使用最後被載入的模板。由於子chart中的模板是與頂級模板一起編譯的,所以需要謹慎命名。

一種流行的命名約定是在每個定義的模板前新增chart名稱:{{define"mychart.labels"}},透過使用特定的chart名作為字首,我們可以避免由於兩個不同的chart實現了相同名稱的模板而引起的衝突。
partials_檔案¶

到目前為止,我們只使用了一個模板檔案,但是Helm的模板語言允許我們建立命名的嵌入式模板,可以在其他位置進行訪問。在編寫這些模板之前,有一些值得一提的命名約定:

templates/中的大多數檔案都被視為Kubernetes資源清單檔案(NOTES.txt除外)
_開頭命名的檔案也不會被當做Kubernetes資源清單檔案
下劃線開頭的檔案不會被當做資源清單之外,還可以被其他chart模板呼叫

_開頭的這些檔案其實就是Helm中的partials檔案,所以其實我們完全可以將命名模板定義在這些partials檔案中,預設就是_helpers.tpl檔案,其實在前面我們建立的mychart包中也可以找到這個檔案。
definetemplate¶

define關鍵字可以讓我們在模板檔案中建立命名模板,它的語法如下所示:

{{

define"MY.NAME"

}}

# 模板內容區域

{{

end

}}

比如我們可以定義一個模板來封裝下Kuberneteslabels標籤:

{{

-define"mychart.labels"

}}

labels:
generator:helm
date:

{{

now|htmlDate

}}

{{

-end

}}

現在我們可以將該模板嵌入到前面的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

}}

當模板引擎讀取這個檔案的時候,它會儲存mychart.labels的引用,直到該模板被呼叫,然後會內聯渲染該模板。我們渲染這個模板可以都到如下所示的結果(記得先刪掉預設生成的_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"

一般來說,Helm中約定將這些模板統一放到一個partials檔案中,通常就是_helpers.tpl檔案中,我們將上面的命名模板移動到該檔案(templates/_helpers.tpl)中去:

{{

/*生成基本的Label標籤*/

}}

{{

-define"mychart.labels"

}}

labels:
generator:helm
date:

{{

now|htmlDate

}}

{{

-end

}}

一般來說,我們也會用一個簡單的塊({{/*...*/}})來註釋這個命名模板的作用。

現在雖然我們把命名模板放到了_helpers.tpl檔案中,但是我們在configmap.yaml模板中還是可以訪問,因為命名模板是全域性的:

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

}}

因為上面我們提到過命名模板是全域性的,我們可以再渲染下上面的模板可以得到正確的結果。
設定模板範圍¶

上面我們定義的模板中,還沒有使用到任何物件,只使用了函式,現在我們來修改下定義的命名模板,包含chart的名稱和版本:

{{

/*生成基本的Label標籤*/

}}

{{

-define"mychart.labels"

}}

labels:
generator:helm
date:

{{

now|htmlDate

}}

chart:

{{

.Chart.Name

}}

version:

{{

.Chart.Version

}}

{{

-end

}}

現在我們來渲染下模板,會出現下面的錯誤:

$helminstall--generate-name--dry-run--debug./my
chart
install.go:148:

[

debug

]

Original chart version:""
install.go:165:

[

debug

]

CHART PATH:/Users/ych/devs/workspace/yidianzhishi/cour
se/k8strain/content/helm/manifests/mychart

Error: unable to build kubernetes objects from release manifest:errorvalidati
ng"":error validating data:

[

unknownobjecttype"nil"inConfigMap.metadata.
labels.chart

,

unknownobjecttype"nil"inConfigMap.metadata.labels.version

]

helm.go:76:

[

debug

]

errorvalidating"":error validating data:

[

unknownobject
type"nil"inConfigMap.metadata.labels.chart

,

unknownobjecttype"nil"inCo
nfigMap.metadata.labels.version

]

......

我們可以看到提示labels.chartnil,這是因為我們使用的.Chart.Name不在定義的這個模板的作用域範圍內,當渲染命名模板(使用define定義)的時候,它將接收模板呼叫傳遞的作用域。在我們這個示例中,我們是這樣引用這個模板的:

{{

-template"mychart.labels"

}}

沒有傳入任何作用域,所以在模板內我們無法訪問.中的任何內容,當然要解決很簡單,我們只需要把作用域範圍傳遞給模板即可:

apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap

{{

-template"mychart.labels".

}}

......

我們這裡在使用template呼叫模板的時候傳遞了.,我們可以很容易傳遞.Values或者.Values.favorite或者我們想要的任何範圍,但是這裡我們想要的是頂級作用域,所以我們傳遞的是.。

現在我們再來重新渲染我們的模板,可以得到如下所示的結果:

# 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"

現在

{{

.Chart.Name

}}

解析為了mychart,而

{{

.Chart.Version

}}

解析為了0.1.0
include函式¶

假設我們定義了一個如下所示的簡單模板:

{{

-define"mychart.app"-

}}

app_name:

{{

.Chart.Name

}}

app_version:"{{ .Chart.Version }}"

{{

-end-

}}

現在我們想把上面的內容插入到模板的labels部分,在data部分也想要這個內容:

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".

}}

但是我們直接渲染上面的模板還是會有錯誤:

$helminstall--generate-name--dry-run--debug./my
chart
install.go:148:

[

debug

]

Original chart version:""
install.go:165:

[

debug

]

CHART PATH:/Users/ych/devs/workspace/yidianzhishi/cour
se/k8strain/content/helm/manifests/mychart

Error: unable to build kubernetes objects from release manifest:errorvalidati
ng"":error validating data:

[

ValidationError(ConfigMap):unknownfield

"app_n

ame"

inio.k8s.api.core.v1.ConfigMap

,

ValidationError(ConfigMap):unknownfield
"app_version"inio.k8s.api.core.v1.ConfigMap

]

helm.go:76:

[

debug

]

errorvalidating"":error validating data:

[

ValidationErro
r(ConfigMap):unknownfield"app_name"inio.k8s.api.core.v1.ConfigMap

,

Validat
ionError(ConfigMap):unknownfield"app_version"inio.k8s.api.core.v1.ConfigMap

]

......

因為template只是一個動作,而不是一個函式,所以無法將模板呼叫的輸出傳遞給其他函式,只是內聯插入,相當於渲染的結果是這樣的:

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資源物件的格式要求的,所以報錯了。為解決這個問題,Helm提供了代替template的函式include,可以將模板的內容匯入到當前的管道中,這樣就可以在管道中傳遞給其他函式進行處理了,如下所示:

apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap
labels:

{{

include"mychart.app".|indent4

}}

data:
myvalue:"Hello World"

{{

-range$key

,

$val:=.Values.favorite

}}

{{

$key

}}

:

{{

$val|quote

}}

{{

-end

}}

{{

include"mychart.app".|indent2

}}

現在我們重新渲染就可以得到正確的結果了,這是因為我們用include函式得到模板內容後透過管道傳給了後面的indent函式來保證了縮排:

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文件的輸出格式。

有時候如果我們只想匯入內容而不是模板,這個時候我們可以透過下面描述的.Files物件來訪問檔案實現。

訪問檔案

在上一節中我們介紹了幾種建立和訪問命名模板的方法,這使得從另一個模板中匯入一個模板變得很容易,但是有時候需要匯入一個不是模板的檔案並注入其內容,而不透過模板渲染器獲得內容。

Helm提供了一個.Files物件對檔案的訪問,但是在模板中使用這個物件之前,還有幾個需要注意的事項值得一提:

可以在Helmchart中新增額外的檔案,這些檔案也會被打包,不過需要注意,由於Kubernetes物件的儲存限制,Charts必須小於1M

由於一些安全原因,透過.Files物件無法訪問某些檔案
無法訪問templates/下面的檔案
無法訪問使用.helmignore排除的檔案

Chart不會保留UNIX模式的資訊,所以,當使用.Files物件時,檔案級別的許可權不會對檔案的可用性產生影響。

基本示例¶

現在我們來編寫一個模板,將3個檔案讀入到ConfigMap模板中,首先我們在chart中新增3個檔案,將3個檔案都直接放置在mychart/目錄中。

config1.toml:

message=Hellofromconfig1

config2.toml:

message=Thisisconfig2

config3.toml:

message=Goodbyefromconfig3

3個檔案都是簡單的TOML檔案,我們知道這些檔案的名稱,所以我們可以使用range函式來遍歷它們,並將其內容注入到ConfigMap中去。

apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-configmap
data:

{{

-$files:=.Files

}}

{{

-rangetuple"config1.toml""config2.toml""config3.toml"

}}

{{

.

}}

:

|-

{{ $files.Get . }}

{{

-end

}}

這裡我們聲明瞭一個$files的變數來儲存.Files物件的引用,還使用了tuple函式來迴圈檔案列表,然後我們列印每個資料夾

{{

.

}}

:|-,後面使用

{{

$files.Get.

}}

獲取檔案內容。

現在我們渲染這個模板會產生包含3個檔案內容的單個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


另外在處理檔案的時候,對檔案路徑本身執行一些標準操作可能非常有用,為了解決這個問題,HelmGo的路徑包中匯入了許多功能供你使用,它們都可以使用與Go包中相同的相同名稱來訪問,但是首字母需要小寫,比如Base需要變成base,匯入的函式有:-Base-Dir-Ext-IsAbs-Clean。
Glob模式¶

隨著chart的增長,你可能需要更多地組織檔案,因此Helm提供了Files.Glob的方法來幫助我們獲取具有glob模式的檔案。

.Glob返回Files型別,所以你可以在返回的物件上呼叫任何Files方法。比如,我們的檔案目錄結構如下所示:

foo/:
foo.txtfoo.yaml

bar/:
bar.gobar.confbaz.yaml

我們可以用Glob進行多種選擇:

{{

range$path:=.Files.Glob"**.yaml"

}}

{{

$path

}}

:|

{{

.Files.Get$path

}}

{{

end

}}

或者

{{

range$path

,

$bytes:=.Files.Glob"foo/*"

}}

{{

$path

}}

:'{{ b64enc $bytes }}'

{{

end

}}

ConfigMapSecrets¶

想要將檔案內容同時放入ConfigMapSecrets中,以便在執行時安裝到Pod中,這種需求很常見,為了解決這個問題,HelmFiles型別上添加了一個實用的方法。

根據上面的目錄結構,我們可以按照如下的方式進行處理:

apiVersion:v1
kind:ConfigMap
metadata:
name:conf
data:

{{

(.Files.Glob"foo/*").AsConfig|indent2

}}

---
apiVersion:v1
kind:Secret
metadata:
name:very-secret
type:Opaque
data:

{{

(.Files.Glob"bar/*").AsSecrets|indent2

}}

編碼¶

我們也可以匯入一個檔案並用base64編碼進行編碼:

apiVersion:v1
kind:Secret
metadata:
name:

{{

.Release.Name

}}

-secret
type:Opaque
data:
token:

|-

{{ .Files.Get "config1.toml" | b64enc }}


上面將採用我們上面的config1.toml檔案並對其內容進行base64編碼,渲染會得到如下所示的結果:

# Source: mychart/templates/configmap.yaml
apiVersion:v1
kind:Secret
metadata:
name:mychart-1576048287-secret
type:Opaque
data:
token:

|-

bWVzc2FnZSA9IEhlbGxvIGZyb20gY29uZmlnIDEK


Lines¶

有時,需要訪問模板中檔案的每一行內容,Helm也提供了方法的Lines方法,我們可以使用range函式遍歷沒行內容:

data:
some-file.txt:

{{

range.Files.Lines"foo/bar.txt"

}}

{{

.

}}{{

end

}}

Helm安裝的時候無法將檔案傳遞到chart外部,所以,如果你要求使用者提供資料的話,則必須使用helminstall-f或者helminstall--set來獲取。

NOTES.txt

在本節中我們將來了解為 chart 使用者提供說明的一個 NOTES.txt 檔案,在 chart 安裝或者升級結束時,Helm 可以為使用者打印出一些有用的資訊,使用模板也可以自定義這些資訊。
要將安裝說明新增到 chart 中,只需要建立一個 templates/NOTES.txt 檔案,該檔案純文字的,但是可以像模板一樣進行處理,並具有所有常規模板的功能和可用物件。
現在讓我們來建立一個簡單的 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 }

}
現在我們執行 helm install ./mychart,我們就可以在底部看到這樣的訊息:
RESOURCES:

==> v1/Secret

NAMETYPE

DATA AGE

rude-cardinal-secret Opaque

10

s
==> v1/ConfigMap

NAME

DATA AGE

rude-cardinal-configmap

30

s

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
用這種方式可以向用戶提供一個有關如何使用其新安裝的 chart 的詳細資訊,強烈建議建立 NOTES.txt 檔案,雖然這不是必須的。

子Chart與全域性值

到現在為止,我們從單一模板,到多個模板檔案,但是都僅僅是處理的一個chart包,但是charts可能具有一些依賴項,我們稱為subcharts(子chart),接下來我們將建立一個子chart。

同樣在深入瞭解之前,我們需要了解下子chart相關的一些資訊。

chart是獨立的,這意味著子chart不能顯示依賴其父chart
所以子chart無法訪問其父級的值
chart可以覆蓋子chart的值
Helm中有可以被所有charts訪問的全域性值的概念

建立子chart¶

同樣還是在之前操作的mychart/這個chart包中,我們來嘗試新增一些新的子chart:

$cdmychart/charts
$helmcreatemysubchart
Creatingmysubchart
$rm-rfmysubchart/templates/*.*

和前面一樣,我們刪除了所有的基本模板,這樣我們可以從頭開始。
新增values模板¶

接下來我們為mysubchart這個子chart建立一個簡單的模板和values值檔案,mychart/charts/mysubchart中已經有一個values.yaml檔案了,在檔案中新增下面的values:

dessert:cake

下面我們再建立一個新的ConfigMap模板mychart/charts/mysubchart/templates/configmap.yaml:

apiVersion:v1
kind:ConfigMap
metadata:
name:

{{

.Release.Name

}}

-cfgmap2
data:
dessert:

{{

.Values.dessert

}}

因為每個子chart都是獨立的chart,所以我們可以單獨測試mysubchart:

helminstall--generate-name--dry-run--debugmychart/charts/mysubchart
install.go:148:

[

debug

]

Original chart version:""
install.go:165:

[

debug

]

CHART PATH:/Users/ych/devs/workspace/yidianzhishi/course/k8strain/content/helm/manifests/mychart/charts/mysubchart

NAME:mysubchart-1576050755
LAST DEPLOYED:WedDec1115:52:362019
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¶

我們原來的chart-mychart現在是mysubchart的父級chart了。由於mychart是父級,所以我們可以在mychart中指定配置,並將該配置傳送到mysubchart中去,比如,我們可以這樣修改mychart/values.yaml:

favorite:
drink:coffee
food:pizza
pizzaToppings:
-mushrooms
-cheese
-peppers
-onions

mysubchart:
dessert:icecream

最後兩行,mysubchart部分中的所有指令都回被髮送到mysubchartchart中,所以,如果我們現在渲染模板,我們可以看到mysubchartConfigMap會被渲染成如下的內容:

# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion:v1
kind:ConfigMap
metadata:
name:mychart-1576051914-cfgmap2
data:
dessert:icecream

我們可以看到頂層的values值覆蓋了子chart中的值。這裡有一個細節需要注意,我們沒有將mychart/charts/mysubchart/templates/configmap.yaml模板更改為指向.Values.mysubchart.dessert,因為從該模板的絕度來看,該值仍然位於.Values.dessert,當模板引擎傳遞values值的時候,它會設定這個作用域,所以,對於mysubchart模板,.Values中僅僅提供用於該子chart的值。

但是有時候如果我們確實希望某些值可以用於所有模板,這個時候就可以使用全域性chartvalues值來完成了。
全域性值¶

全域性值是可以從任何chart或子chart中都可以訪問的值,全域性值需要顯示的宣告,不能將現有的非全域性物件當作全域性物件使用。

Values資料型別具有一個名為Values.global的保留部分,可以在其中設定全域性值,我們在mychart/values.yaml檔案中新增一個全域性值:

favorite:
drink:coffee
food:pizza
pizzaToppings:
-mushrooms
-cheese
-peppers
-onions

mysubchart:
dessert:icecream

global:
salad:caesar

由於全域性值的原因,在mychart/templates/configmap.yamlmysubchart/templates/configmap.yaml下面都應該可以以

{{

.Values.global.salad

}}

的形式來訪問這個值。

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

}}

然後我們渲染這個模板,可以得到如下所示的內容:

---
# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion:v1
kind:ConfigMap
metadata:
name:mychart-1576053485-cfgmap2
data:
dessert:icecream
salad:caesar
---
# Source: mychart/templates/configmap.yaml
apiVersion:v1
kind:ConfigMap
metadata:
name:mychart-1576053485-configmap
data:
salad:caesar

全域性值對於傳遞這樣的資料比較有用。
共享模板¶

父級chart和子chart可以共享模板,任何chart中已定義的塊都可以用於其他chart。比如,我們可以定義一個簡單的模板,如下所示:

{{

-define"labels"

}}

from:mychart{{end}}

前面我們提到過可以使用在模板中使用includetemplate,但是使用include的一個優點是可以動態引入模板的內容:

{{

include$mytemplate

}}

模板除錯

除錯模板可能比較麻煩,因為渲染的模板會發送到 Kubernetes API server,而 API server 可能會因為格式以外的一些原因而拒絕 YAML 檔案。
下面這些命令可以幫助你除錯一些問題:
helm lint 是驗證 chart 是否遵循最佳實踐的首選工具

helm

install

--dry-run --

debug

或者 helm template --

debug

:前面我們已經使用了這個技巧,這個是讓伺服器渲染模板,然後返回生成的資源清單檔案的好方法,而且不會真正的去安裝這些資源

helm get manifest:這是檢視伺服器上安裝了哪些模板的好方法
當你的 YAML 檔案無法解析的時候,但你想要檢視生成的內容的時候,檢索 YAML 的一種簡單方法是註釋掉模板中的問題部分,然後重新執行 helm

install

--dry-run --

debug


apiVersion:

v2

# some: problem section
# {{ .Values.foo | quote }}

上面的內容將呈現並返回完整的註釋:


apiVersion:

v2

# some: problem section
# "bar"

這提供了一種檢視生成的內容的快速方法。

Chart Hooks

Helm也提供了一種Hook機制,可以允許chart開發人員在release生命週期的某些時間點進行干預。比如,可以使用hook來進行下面的操作:

在載入任何charts之前,在安裝的時候載入ConfigMap或者Secret
在安裝新的chart之前,執行一個Job來備份資料庫,然後在升級後執行第二個Job還原資料
在刪除release之前執行一個JOb,以在刪除release之前適當地取消相關服務

Hooks的工作方式類似於普通的模板,但是他們具有特殊的註解,這些註解使Helm可以用不同的方式來使用他們。
Hooks¶

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

預安裝pre-install:在模板渲染後,kubernetes建立任何資源之前執行
安裝後post-install:在所有kubernetes資源安裝到集群后執行
預刪除pre-delete:在從kubernetes刪除任何資源之前執行刪除請求
刪除後post-delete:刪除所有release的資源後執行
升級前pre-upgrade:在模板渲染後,但在任何資源升級之前執行
升級後post-upgrade:在所有資源升級後執行
預回滾pre-rollback:在模板渲染後,在任何資源回滾之前執行
回滾後post-rollback:在修改所有資源後執行回滾請求
測試test:在呼叫Helmtest子命令的時候執行(可以檢視測試文件)

生命週期¶

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

使用者執行helminstallfoo
Helm庫檔案呼叫安裝API
經過一些驗證,Helm庫渲染foo模板
Helm庫將產生的資源載入到kubernetes中去
Helm庫將release物件和其他資料返回給客戶端
Helm客戶端退出

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

使用者執行helminstallfoo
Helm庫檔案呼叫安裝API
crds/目錄下面的CRDs被安裝
經過一些驗證,Helm庫渲染foo模板
Helm庫將hook資源載入到kubernetes中,準備執行pre-installhooks
Helm庫會根據權重對hooks進行排序(預設分配權重0,權重相同的hook按升序排序)
Helm庫然後載入最低權重的hook
Helm庫會等待,直到hook準備就緒
Helm庫將產生的資源載入到kubernetes中,注意如果添加了--wait引數,Helm庫會等待所有資源都準備好,在這之前不會執行post-installhook
Helm庫執行post-installhook(載入hook資源)
Helm庫等待,直到hook準備就緒
Helm庫將release物件和其他資料返回給客戶端
Helm客戶端退出

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

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

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

所以,如果我們在hook中建立了資源,那麼不能依賴helmuninstall去刪除資源,因為hook建立的資源已經不受控制了,要銷燬這些資源,你需要將helm.sh/hook-delete-policy這個annotation新增到hook模板檔案中,或者設定Job資源的生存(TTL)欄位。
編寫Hook¶

Hooks就是Kubernetes資源清單檔案,在元資料部分帶有一些特殊的註解,因為他們是模板檔案,所以你可以使用普通模板所有的功能,包括讀取.Values、.Release.Template。

例如,在templates/post-install-job.yaml檔案中宣告一個post-installhook:

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:
# 因為添加了這個 hook,所以我們這個資源被定義為了 hook
# 如果沒有這行,則當前這個 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:
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 }}"

]

當前這個模板成為hook的原因就是新增這個註解:

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

一種資源也可以實現多個hooks:

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

類似的,實現給定hook的資源數量也沒有限制,比如可以將secret和一個configmap都宣告為pre-installhook。

當子chart宣告hooks的時候,也會對其進行呼叫,頂層的chart無法停用子chart所宣告的hooks。可以為hooks定義權重,這將有助於確定hooks的執行順序:

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

hook權重可以是正數也可以是負數,但是必須用字串表示,當Helm開始執行特定種類的hooks的時候,它將以升序的方式對這些hooks進行排序。
Hook刪除策略¶

我們還可以定義確定何時刪除相應hook資源的策略,hook刪除策略可以使用下面的註解進行定義:

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

我們也可以選擇一個或多個已定義的註解:

before-hook-creation:執行一個新的hook之前刪除前面的資源(預設)
hook-succeeded:hook成功執行後刪除資源
hook-failed:hook如果執行失敗則刪除資源

如果未指定任何hook刪除策略註解,則預設情況下會使用before-hook-creation策略。

示例

前面介紹了Helm的基本使用,以及HelmChart包開發相關的一些知識點,下面我們用一個例項來演示下如何開發一個真正的HelmChart包。
應用¶

我們這裡為前面的完整示例WordPress開發一個Chart包,在開發Chart包之前很明顯我們最需要的就是要知道我們自己的應用應該如何使用,如何部署,不然是不可能編寫出對應的Chart包的。

我們可以用helmcreate命令來建立一個Chart包,這裡我們就完全手動來建立,首先建立一個名為wordpress的資料夾:

$mkdirwordpress&&cdwordpress

然後在目錄下面建立如下所示的Chart.yaml檔案:

apiVersion:v2
name:wordpress
description:AHelmchartforKubernetes
home:https://wordpress.org/
type:application
# chart 版本號
version:0.1.0
# wordpress 的應用版本
appVersion:5.3.2

由於WordPress應用本身是一來MySQL資料庫的,所以同樣需要新增一個依賴說明requirements.yaml:

dependencies:
-name:mysql
version:1.6.2
repository:http://mirror.azure.cn/kubernetes/charts/
condition:mysql.enabled
tags:
-wordpress-database

這裡依賴的應用我們可以透過helmsearch命令來獲取,當然也可以隨意指定一個Chart版本:

$helmsearchrepostable/mysql
NAMECHARTVERSIONAPPVERSIONDESCRIPTION
stable/mysql1.6.25.7.28Fast,reliable,scalable,andeasytouseopen-...

需要注意的是在依賴的檔案中我們添加了一個condition:mysql.enabled條件,這意味著當Valuesmysql.enabledtrue的時候才會真正去依賴這個子Chart,因為我們完全可以直接使用一個外部的資料庫。所以首先我們要先新增一個mysql.enabledValues值,新增如下所示的values.yaml檔案:

##
## MySQL Chart 配置,參考如下 values.yaml
## https://github.com/helm/charts/blob/master/stable/mysql/values.yaml
##
mysql:
## 是否部署mysql服務來滿足wordpress的需求。如果要使用外部資料庫,將 enabled 設定為 false 並配置 externalDatabase 引數。
enabled:true
## todo,其他 mysql 配置

## 當 mysql.enabled=false 的時候使用外部資料庫
externalDatabase:
## 資料庫地址
host:localhost:3306

## WordPress 資料庫的非root使用者
user:wordpress

## 資料庫密碼
password:wordpress

## 資料庫名稱
database:wordpress

上面的Values配置很好理解,就是如果mysql.enabled=true則我們就用mysql這個子Chart去安裝一個MySQL資料庫,如果為false則使用下面externalDatabase的外部資料庫資訊來作為wordpress的資料庫配置,雖然現在這些配置還完全沒有任何作用,但是這些卻是一開始就很容易考慮到的事情。

接下來建立一個templatescharts目錄,並在charts目錄下面獲取mysql這個子chart:

$mkdirtemplates&&mkdircharts
$cdcharts&&helmfetchstable/mysql&&cd..

現在的整個Chart包目錄結構如下所示:

$tree.
.
├──Chart.yaml
├──charts
└──mysql-1.6.2.tgz
├──requirements.yaml
├──templates
└──values.yaml

2directories,4files

接下來就是我們的重頭戲模板的開發了。我們可以將前面課程示例中的WordPress資源清單直接複製到templates目錄下面,將DeploymentService分別放在不同的YAML檔案中,同樣還有資料持久化的資源清單,但是這裡我們就需要先將MySQL的部分移除掉,因為我們會透過外部資料庫或者子Chart來渲染,不需要在這裡顯示的聲明瞭,另外還需要將所有資源物件的名稱空間去掉,因為我們可以透過helminstall命令直接指定即可,現在我們的模板目錄結構就如下所示了:

$treetemplates
templates
├──deployment.yaml
├──pvc.yaml
└──service.yaml

0directories,4files

名稱¶

這個時候我們可以嘗試去安裝除錯下這個Chart模板:

$helminstall--generate-name--dry-run--debug.
install.go:148:

[

debug

]

Original chart version:""
install.go:165:

[

debug

]

CHART PATH:/Users/ych/devs/workspace/yidianzhishi/course/k8strain/content/helm/manifests/wordpress

load.go:112: Warning:DependenciesarehandledinChart.yamlsinceapiVersion"v2".WerecommendmigratingdependenciestoChart.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:76:

[

debug

]

existing resource conflict: kind:Middleware,namespace:default,name:redirect-https
renderedmanifestscontainaresourcethatalreadyexists.Unabletocontinuewithinstall
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/[email protected]/command.go:826
github.com/spf13/cobra.(*Command).ExecuteC
/go/pkg/mod/github.com/spf13/[email protected]/command.go:914
github.com/spf13/cobra.(*Command).Execute
/go/pkg/mod/github.com/spf13/[email protected]/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

我們看到出現了錯誤,其中還有這樣的一條警告資訊load.go:112: Warning:DependenciesarehandledinChart.yamlsinceapiVersion"v2".WerecommendmigratingdependenciestoChart.yaml.,大概意思就是在Helm3中依賴項的配置被移動到了Chart.yaml檔案中,也就是在Helm2版本的時候依賴才會在requirements.yaml中宣告,這裡我們可以將requirements.yaml檔案中的內容全部複製到Chart.yaml檔案中去,然後刪除requirements.yaml檔案,這個時候重新DEBUG下就沒有這個提示資訊了。

下面的錯誤提示基本上都是類似於renderedmanifestscontainaresourcethatalreadyexists.這樣的資訊,意思就是這些資源物件在現有叢集中已經包含了,這很正常,因為我們的資源物件名稱都是寫死的,是非常有可能和現有的叢集衝突的,所以接下來我們要給這些資源清單改名,如何才能避免衝突又具有很好的擴充套件性呢?這裡我們建立一個如下所示的命名模板:

{{

/*
建立一個預設的應用名稱,擷取63個字元是因為Kubernetesname屬性的限制(DNS命名規範)。
*/

}}

{{

-define"wordpress.fullname"-

}}

{{

-if.Values.fullnameOverride-

}}

{{

-.Values.fullnameOverride|trunc63|trimSuffix"-"-

}}

{{

-else-

}}

{{

-$name:=default.Chart.Name.Values.nameOverride-

}}

{{

-ifcontains$name.Release.Name-

}}

{{

-.Release.Name|trunc63|trimSuffix"-"-

}}

{{

-else-

}}

{{

-printf"%s-%s".Release.Name$name|trunc63|trimSuffix"-"-

}}

{{

-end-

}}

{{

-end-

}}

{{

-end-

}}

根據規範命名模板的名稱最好以Chart名作為字首,這裡我們命名成wordpress.fullname,首先就是判斷是否定義了fullnameOverride這個Values值,如果定義了則擷取63個字元並移除-這樣的前後綴作為名稱,如果沒有定義呢?就要檢查Chart名是否包含Release名稱,如果包含則擷取Release名的63個字元並移除-這樣的前後綴作為名稱,如果不包含,則將Release名稱和Chart名稱用-連線起來作為名稱,這個命名的方式也是符合大部分Chart模板中的命名,以後的模板開發中都可以直接使用。在templates目錄下面新建一個_helpers.tpl這樣的partials檔案,然後將上面定義的命名模板放置到裡面去。後面我們所有的命名模板都將在該檔案中完成。

接下來將templates目錄下面的所有資源物件name屬性全都換成上面我們定義的命名模板,由於這裡並沒有什麼空格控制之類的,我們直接使用template函式即可,同樣作為慣例,也習慣將ReleaseChart名稱之類作為Label標籤,所以最終,這些資源清單的Meta資訊如下所示,如果有其他額外的資訊當然可以隨意新增:

metadata:
name:

{{

template"wordpress.fullname".

}}

labels:
app:"{{ template "wordpress.fullname" . }}"
chart:"{{ template "wordpress.chart" . }}"
release:

{{

.Release.Name|quote

}}

heritage:

{{

.Release.Service|quote

}}

然後當然也需要將DeploymentmatchLabelstemplate下面的Label標籤進行修改,以及Serviceselector匹配的標籤。

同樣還有PVC物件,以前我們直接使用的一個確定名稱的PVC物件:

volumes:
-name:wordpress-data
persistentVolumeClaim:
claimName:wordpress-pvc

但是現在我們這裡作為模板就要考慮到各種情況了,很有可能使用我們模板的使用者根本就不需要持久化,也有可能直接傳遞一個存在的PVC物件進來使用,所以這裡做了如下改變:

volumes:
-name:wordpress-data

{{

-if.Values.persistence.enabled

}}

persistentVolumeClaim:
claimName:

{{

.Values.persistence.existingClaim|default(include"wordpress.fullname".)

}}

{{

-else

}}

emptyDir:

{}

{{

end

}}

當我們定義了Valuespersistence.enabled=truetrue時候就表示要使用持久化了,所以要指定下面的claimName屬性,但是還需要判斷persistence.existingClaim這個Values值是否存在,如果存在則表示直接使用,如果不存在則使用我們模板裡面渲染的PVC物件,如果不需要持久化,則直接使用emptyDir:{}即可。最後我們的PVC物件模板變成了如下所示:

{{

-ifand.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-

}}

其中訪問模式、儲存容量、StorageClass、存在的PVC都透過Values來指定,增加了靈活性。對應的values.yaml配置部分我們可以給一個預設的配置:

## 是否使用 PVC 開啟資料持久化
persistence:
enabled:true
## 是否使用 storageClass,如果不適用則補配置
# storageClass: "xxx"
##
## 如果想使用一個存在的 PVC 物件,則直接傳遞給下面的 existingClaim 變數
# existingClaim: your-claim
accessMode:ReadWriteMany# 訪問模式
size:2Gi# 儲存容量

現在我們去重新做一次DEBUG,可以看到正常了,但是還遠遠不夠,接下來我們就來定製其他部分。
定製¶

比如副本數我們可以透過Values來指定,變成replicas:

{{

.Values.replicaCount

}}

更新策略也可以,因為更新策略並不是一層不變的,這裡和之前不太一樣,我們需要用到一個新的函式toYaml:

{{

-if.Values.updateStrategy

}}

strategy:

{{

toYaml.Values.updateStrategy|nindent4

}}

{{

-end

}}

意思就是我們將updateStrategy這個Values值轉換成YAML格式,並保留4個空格。然後新增其他的配置,比如是否需要新增nodeSelector、容忍、親和性這些,這裡我們都是使用toYaml函式來控制空格,如下所示:

{{

-if.Values.nodeSelector

}}

nodeSelector:

{{

toYaml.Values.nodeSelector|indent8

}}

{{

-end-

}}

{{

-with.Values.affinity

}}

affinity:

{{

toYaml.|indent8

}}

{{

-end

}}

{{

-with.Values.tolerations

}}

tolerations:

{{

toYaml.|indent8

}}

{{

-end

}}

接下來當然就是映象的配置了,如果是私有倉庫還需要指定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

對應的Values值如下所示:

image:
name:wordpress
tag:5.3.2-apache
## 指定 imagePullPolicy 預設為 Always
pullPolicy:IfNotPresent
## 如果是私有倉庫,需要指定 imagePullSecrets
# pullSecrets:
# - myRegistryKeySecretName

然後就是最重要的環境變數配置部分了,因為涉及到資料庫的配置,同樣最核心的三個環境變數配置WORDPRESS_DB_HOST、WORDPRESS_DB_USER、WORDPRESS_DB_PASSWORD,由於可能使用我們模板的使用者可能使用外部資料庫,也有可能使用我們依賴的mysql這個子Chart,所以我們需要分別判斷來進行渲染:

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

}}

每一個環境變數首先都是判斷mysql.enabled是否為true,才表示使用子Chart來渲染,而對應的值都是子Chart中渲染過後的值,所以也需要我們去了解子Chart的渲染行為,如果使用外部的資料庫就要簡單很多,因為只需要讀取Values值即可。另外一個值得注意的是如果是配置的密碼,我們還需要去建立一個Secret資源物件來引用。所以我們在templates目錄下面建立了一個externaldb-secrets.yaml的資原始檔,裡面配置的密碼透過b64enc函式轉換為Base64編碼格式。

{{

-ifnot.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資源宣告,這裡我們定義一個預設的resources值,同樣用toYaml函式來控制空格:

resources:

{{

toYaml.Values.resources|indent10

}}

最後是健康檢查部分,雖然我們之前沒有做livenessProbe,但是我們開發Chart模板的時候就要儘可能考慮周全一點,這裡我們加上存活性和可讀性兩個探針,並且根據livenessProbe.enabledreadinessProbe.enabled兩個Values值來判斷是否需要新增探針,探針對應的引數也都透過Values值來配置:

{{

-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的模板就基本上完成了,我們可以透過DEBUG模式來模擬渲染:

$helminstall--generate-name--dry-run--debug.

可以根據結果來判斷是否符合我們的需求。

最後我們還需要來對Service物件做模板化,因為前面我們是預設的NodePort型別,我們需要透過Values來定製:

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

}}

這樣我們就可以透過配置Values值來配置Service物件了。最後就是Ingress/IngressRoute物件了,大家可以自己嘗試講這部分補齊。

最後在templates目錄下面加上NOTES.txt檔案來說明如何使用我們的Chart包就可以了:

Get the WordPress Manifests Objects:

$kubectlgetall-lapp={{.Release.Name}}

最後我們來真正的使用我們的Chart包安裝一次來測試下:

$helminstallmychart.--setservice.type=NodePort
NAME:mychart
LAST DEPLOYED:SunMar817:52:042020
NAMESPACE:default
STATUS:deployed
REVISION:1
NOTES:
Get the WordPress Manifests Objects:

$kubectlgetall-lapp=mychart

$helmls
NAMENAMESPACEREVISIONUPDATEDSTATUSCHARTAPPVERSION
mychartdefault12020-03-08 17:52:04.826185+0800CSTdeployedwordpress-0.1.05.3.2

安裝完成後可以檢視我們的資源物件:

$kubectlgetall-lapp=mychart-wordpress
NAMEREADYSTATUSRESTARTSAGE
pod/mychart-wordpress-5f65786d89-2m45s1/1Running070s

NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE
service/mychart-wordpressNodePort10.99.239.29<none>80:30427/TCP70s

NAMEREADYUP-TO-DATEAVAILABLEAGE
deployment.apps/mychart-wordpress1/11170s

NAMEDESIREDCURRENTREADYAGE
replicaset.apps/mychart-wordpress-5f65786d8911170s

這個時候我們透過上面的NodePort就可以去訪問到我們的應用了,當然還有很多配置我們都是可以直接透過Values去進行定製的。

連結:https://www.cnblogs.com/hahaha111122222/p/16277623.html
(版權歸原作者所有,侵刪)
文末福利一

馬哥教育三月IT充電計劃

【 特惠入學+你學習我返現 】
💰全款節省高至 4500 元!
🎁直播間專屬禮包+3倍積分!
📚贈實戰專題
🏆獎學金等你來拿!高達 500 元。
💎贈送阿里云云計算 ACP 考試券(價值 1200 元)
💎活動期間報名,送鑽石學習卡。
想系統學習網路安全的師傅,千萬不要錯過!
掃描下方二維碼,瞭解活動詳情↓
【參與方式】
掃描二維碼找豆豆
文末福利二
【AI大模型】訓練營(有回放)
馬哥教育創始人 馬哥親講
無論你是渴望提升技術能力的 IT 工程師,
還是希望深入瞭解大模型應用的運維人員或開發者,
本次大模型訓練營公開課都將為你提供寶貴的學習機會。
內容乾貨,還有現場抽獎和免費資料可領取↑
趕緊掃描下方二維碼,報名直播吧↑(有回放)


相關文章