認識Helm
每次我們要部署一個應用都需要寫一個配置清單(維護一套yaml
文件),但是每個環境又不一樣。部署一套新的環境成本是真的很高。如果我們能夠使用類似於yum的工具來安裝我們的應用的話那就太好了。Helm
就相當於kubernetes
環境下的yum
包管理工具。
Helm重要概念
- chart:是helm的一個程序包。包含了創建
Kubernetes
的一個應用實例的必要信息 - repository: charts倉庫,http/https服務
- config:包含了應用發布配置信息
- release:是一個 chart 及其配置的一個運行實例
Helm組件:
Helm Client
是用戶命令行工具,其主要負責如下:
- 本地 chart 開發
- 倉庫管理
- 與 Tiller sever 交互
- 發送預安裝的 chart
- 查詢 release 信息
- 要求升級或卸載已存在的 release
Tiller Server
是一個部署在Kubernetes
集群內部的 server,其與 Helm client、Kubernetes API server 進行交互。Tiller server 主要負責如下:
- 監聽來自 Helm client 的請求
- 通過 chart 及其配置構建一次發布
- 安裝 chart 到
Kubernetes
集群,並跟蹤隨后的發布 - 通過與
Kubernetes
交互升級或卸載 chart - 簡單的說,client 管理 charts,而 server 管理發布 release
架構圖如下:
安裝Helm
配置Helm客戶端訪問權限
Helm在集群上安裝tiller
服務以管理charts
. 由於kubeadm默認啟用RBAC, 因此我們需要使用kubectl
來創建一個serviceaccount
,clusterrolebinding
才能讓tiller
具有部署到集群的權限。
kubectl -n kube-system create serviceaccount tiller
kubectl create clusterrolebinding tiller --clusterrole cluster-admin --serviceaccount=kube-system:tiller
安裝Helm客戶端
1、下載預期的版本 releases
2、解壓縮並將可執行文件helm拷貝到/usr/local/bin
目錄下即可
mv linux-amd64/helm /usr/bin/
安裝Helm Server(Tiller)
快速集群內安裝
helm init --service-account tiller --tiller-image registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.11.0 --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
Helm常用命令:
release相關命令:
- install 安裝
- delete 刪除
- upgrade/rollback 更新/回滾
- history 歷史
- list 查看
chart相關命令
- create 創建
- fetch 下載 (打包格式)
- get 下載
- inspect 查看chart詳細信息
- verify 校驗
- package 打包
- search 查看哪些charts可用
charts解讀
常用文件
tree hello-helm/ hello-helm/ ├── charts/ ├── Chart.yaml ├── templates/ ├── requirements.yaml └── values.yaml
- charts/ (必需項)是一個目錄,存放一些調用的charts
- Chart.yaml (必需項)定義一些基礎信息。例如作者、版本等
- templates/ (必需項)是應用需要的yaml文件模板
- requirements.yaml (可選項)同charts一樣的。
- values.yaml (必需項)默認配置值。例如定義的一些變量等。
Chart.yaml 描述
- ap
iVersion:api版本(v1)
- name:chart的名稱
- version:版本
- kube
Version:k8s的兼容版本
- d
escription:描述本項目
- k
eywords:關於本項目
- h
ome:本項目主頁
- m
aintainers:作者
- i
con:圖標
- a
ppVersion:應用版本
- d
eprecated:是否已棄用
- t
illerVersion:tiller版本
requirements.yaml 描述
樣例:
dependencies: - name: apache version: 1.2.3 repository: http://example.com/charts - name: mysql version: 3.2.1 repository: http://another.example.com/charts
使用dependencies引用一個列表。每個對象列表都是要依賴到的chart。
- name: chart的名稱
- version: chart的版本
- repository: chart所在的倉庫
templates 描述
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ ├── NOTES.txt
│ └── service.yaml
- NOTES.txt:chart 的 “幫助文本”。這會在用戶運行 helm install 時顯示給用戶。
- deployment.yaml:創建 Kubernetes deployment 的基本 manifest
- service.yaml:為 deployment 創建 service 的基本 manifest
- ingress.yaml: 創建 ingress 對象的資源清單文件
- _helpers.tpl:放置模板助手的地方,可以在整個 chart 中重復使用
如何創建模板
1、我們刪除templates下的所有文件。創建一個configmap.yaml
$ rm -rf ./templates/*.*
apiVersion: v1 kind: ConfigMap metadata: name: mychart-configmap data: myvalue: "Hello World"
2、安裝這個charts
$ helm install ./hello-helm/ NAME: sullen-indri LAST DEPLOYED: Mon Mar 4 15:47:00 2019 NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1/ConfigMap NAME AGE mychart-configmap 0s
3、查看渲染后的資源文件
$ helm get manifest sullen-indri --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mychart-configmap data: myvalue: "Hello World"
通過變量創建一個簡單的模板
Helm Chart 模板使用的是Go
語言模板編寫而成,並添加了Sprig
庫中的50多個附件模板函數以及一些其他特殊的函。
現在我們來重新定義下上面的 configmap.yaml 文件:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World"
我們將名稱替換成了 {{ .Release.Name }}-configmap ,其中包含在{{
和}}
之中的就是模板指令, {{ .Release.Name }} 將 release 的名稱注入到模板中來,這樣最終生成的 ConfigMap 名稱就是以 release 的名稱開頭的了。這里的 Release 模板對象屬於 Helm 內置的一種對象,還有其他很多內置的對象,稍后我們將接觸到。
1、首選刪除原chart
helm delete sullen-indri
2、重新安裝這個新的chart
$ helm install ./hello-helm/ NAME: zeroed-warthog LAST DEPLOYED: Mon Mar 4 15:58:03 2019 NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1/ConfigMap NAME AGE zeroed-warthog-configmap 0s
這里我們可以看到名稱變成zeroed-warthog-configmap,說明我們的{{ .Release.Name }}是有效的。
內置對象
剛剛我們使用 {{.Release.Name}} 將 release 的名稱插入到模板中。這里的 Release 就是 Helm 的內置對象,下面是一些常用的內置對象,在需要的時候直接使用就可以:
- Release:這個對象描述了 release 本身。它里面有幾個對象:
- Release.Name:release 名稱
- Release.Time:release 的時間
- Release.Namespace:release 的 namespace(如果清單未覆蓋)
- Release.Service:release 服務的名稱(始終是 Tiller)。
- Release.Revision:此 release 的修訂版本號,從1開始累加。
- Release.IsUpgrade:如果當前操作是升級或回滾,則將其設置為 true。
- Release.IsInstall:如果當前操作是安裝,則設置為 true。
- Values:從
values.yaml
文件和用戶提供的文件傳入模板的值。默認情況下,Values 是空的。 - Chart:
Chart.yaml
文件的內容。所有的 Chart 對象都將從該文件中獲取。chart 指南中Charts Guide列出了可用字段,可以前往查看。 - Files:這提供對 chart 中所有非特殊文件的訪問。雖然無法使用它來訪問模板,但可以使用它來訪問 chart 中的其他文件。請參閱 "訪問文件" 部分。
- Files.Get 是一個按名稱獲取文件的函數(.Files.Get config.ini)
- Files.GetBytes 是將文件內容作為字節數組而不是字符串獲取的函數。這對於像圖片這樣的東西很有用。
- Capabilities:這提供了關於 Kubernetes 集群支持的功能的信息。
- Capabilities.APIVersions 是一組版本信息。
- Capabilities.APIVersions.Has $version 指示是否在群集上啟用版本(batch/v1)。
- Capabilities.KubeVersion 提供了查找 Kubernetes 版本的方法。它具有以下值:Major,Minor,GitVersion,GitCommit,GitTreeState,BuildDate,GoVersion,Compiler,和 Platform。
- Capabilities.TillerVersion 提供了查找 Tiller 版本的方法。它具有以下值:SemVer,GitCommit,和 GitTreeState。
- Template:包含有關正在執行的當前模板的信息
- Name:到當前模板的文件路徑(例如 mychart/templates/mytemplate.yaml)
- BasePath:當前 chart 模板目錄的路徑(例如 mychart/templates)
上面這些值可用於任何頂級模板,要注意內置值始終以大寫字母開頭。這也符合Go
的命名約定。當你創建自己的名字時,你可以自由地使用適合你的團隊的慣例。
values.yaml 描述
上面的內置對象中有一個對象就是 Values,該對象提供對傳入 chart 的值的訪問,Values 對象的值有4個來源:
- chart 包中的 values.yaml 文件
- 父 chart 包的 values.yaml 文件
- 通過 helm install 或者 helm upgrade 的
-f
或者--values
參數傳入的自定義的 yaml 文件 - 通過
--set
參數傳入的值
chart 的 values.yaml 提供的值可以被用戶提供的 values 文件覆蓋,而該文件同樣可以被--set
提供的參數所覆蓋。
這里我們來重新編輯 mychart/values.yaml 文件,將默認的值全部清空,添加一個新的數據:(values.yaml)
course: k8s
然后我們在上面的 templates/configmap.yaml 模板文件中就可以使用這個值了:(configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" course: {{ .Values.course }}
可以看到最后一行我們是通過 {{ .Values.course }} 來獲取 course 的值的。現在我們用 debug 模式來查看下我們的模板會被如何渲染:
$ helm install --dry-run --debug ./hello-helm/ [debug] Created tunnel using local port: '46226' [debug] SERVER: "127.0.0.1:46226" [debug] Original chart version: "" [debug] CHART PATH: /root/hello-helm NAME: left-snail REVISION: 1 RELEASED: Mon Mar 4 16:43:35 2019 CHART: hello-helm-0.1.0 USER-SUPPLIED VALUES: {} COMPUTED VALUES: course: k8s HOOKS: MANIFEST: --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: left-snail-configmap data: myvalue: "Hello World" course: k8s
我們可以看到 ConfigMap 中 course 的值被渲染成了 k8s,這是因為在默認的 values.yaml 文件中該參數值為 k8s,同樣的我們可以通過--set
參數來輕松的覆蓋 course 的值:
$ helm install --dry-run --debug --set course=jaxzhai ./hello-helm/ [debug] Created tunnel using local port: '45477' [debug] SERVER: "127.0.0.1:45477" [debug] Original chart version: "" [debug] CHART PATH: /root/hello-helm NAME: wise-cat REVISION: 1 RELEASED: Mon Mar 4 16:44:42 2019 CHART: hello-helm-0.1.0 USER-SUPPLIED VALUES: course: jaxzhai COMPUTED VALUES: course: jaxzhai HOOKS: MANIFEST: --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: wise-cat-configmap data: myvalue: "Hello World" course: jaxzhai
由於--set
比默認 values.yaml 文件具有更高的優先級,所以我們的模板生成為 course: jaxzhai。
values 文件也可以包含更多結構化內容,例如,我們在 values.yaml 文件中可以創建 course 部分,然后在其中添加幾個鍵:
course:
k8s: devops
python: django
現在我們稍微修改模板:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" k8s: {{ .Values.course.k8s }} python: {{ .Values.course.python }}
同樣可以使用 debug 模式查看渲染結果:
helm install --dry-run --debug ./hello-helm/ [debug] Created tunnel using local port: '44008' [debug] SERVER: "127.0.0.1:44008" [debug] Original chart version: "" [debug] CHART PATH: /root/hello-helm NAME: garish-magpie REVISION: 1 RELEASED: Mon Mar 4 16:45:58 2019 CHART: hello-helm-0.1.0 USER-SUPPLIED VALUES: {} COMPUTED VALUES: course: k8s HOOKS: MANIFEST: --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: garish-magpie-configmap data: myvalue: "Hello World" course: k8s
可以看到模板中的參數已經被 values.yaml 文件中的值給替換掉了。雖然以這種方式構建數據是可以的,但我們還是建議保持 value 樹淺一些,平一些,這樣維護起來要簡單一點。
模板函數
比如我們需要從.Values
中讀取的值變成字符串的時候就可以通過調用quote
模板函數來實現:(templates/configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" k8s: {{ quote .Values.course.k8s }} python: {{ .Values.course.python }}
模板函數遵循調用的語法為:functionName arg1 arg2...
。在上面的模板文件中,quote .Values.course.k8s
調用quote
函數並將后面的值作為一個參數傳遞給它。最終被渲染為:
$ helm install --dry-run --debug . [debug] Created tunnel using local port: '43513' [debug] SERVER: "127.0.0.1:43513" [debug] Original chart version: "" [debug] CHART PATH: /root/hello-helm NAME: full-aardwolf REVISION: 1 RELEASED: Wed Mar 6 09:52:02 2019 CHART: hello-helm-0.1.0 USER-SUPPLIED VALUES: {} COMPUTED VALUES: course: k8s: devops python: django HOOKS: MANIFEST: --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: full-aardwolf-configmap data: myvalue: "Hello World" k8s: "devops" python: django
我們可以看到.Values.course.k8s
被渲染成了字符串devops
。一部分是由Go 模板語言本身定義的,其他大部分都是Sprig
模板庫提供的一部分,我們可以前往這兩個文檔中查看這些函數的用法。
比如我們這里使用的quote
函數就是Sprig 模板庫
提供的一種字符串函數,用途就是用雙引號將字符串括起來,如果需要雙引號"
,則需要添加\
來進行轉義,而squote
函數的用途則是用雙引號將字符串括起來,而不會對內容進行轉義。
管道用法
模板語言除了提供了豐富的內置函數之外,其另一個強大的功能就是管道的概念。和UNIX
中一樣,管道我們通常稱為Pipeline
,是一個鏈在一起的一系列模板命令的工具,以緊湊地表達一系列轉換。簡單來說,管道是可以按順序完成一系列事情的一種方法。比如我們用管道來重寫上面的 ConfigMap 模板:(templates/configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" k8s: {{ .Values.course.k8s | quote }} python: {{ .Values.course.python }}
這里我們直接調用quote
函數,而是調換了一個順序,使用一個管道符|
將前面的參數發送給后面的模板函數:{{ .Values.course.k8s | quote }}
,使用管道我們可以將幾個功能順序的連接在一起,比如我們希望上面的 ConfigMap 模板中的 k8s 的 value 值被渲染后是大寫的字符串,則我們就可以使用管道來修改:(templates/configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" k8s: {{ .Values.course.k8s | upper | quote }} python: {{ .Values.course.python }}
這里我們在管道中增加了一個upper
函數,該函數同樣是Sprig 模板庫
提供的,表示將字符串每一個字母都變成大寫。然后我們用debug
模式來查看下上面的模板最終會被渲染成什么樣子:
helm install --dry-run --debug . [debug] Created tunnel using local port: '46279' ......... # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: ponderous-serval-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: django
我們可以看到之前我們的devops
已經被渲染成了"DEVOPS"
了,要注意的是使用管道操作的時候,前面的操作結果會作為參數傳遞給后面的模板函數,比如我們這里希望將上面模板中的 python 的值渲染為重復出現3次的字符串,則我們就可以使用到Sprig 模板庫
提供的repeat
函數,不過該函數需要傳入一個參數repeat COUNT STRING
表示重復的次數:(templates/configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: "Hello World" k8s: {{ .Values.course.k8s | upper | quote }} python: {{ .Values.course.python | repeat 3 | quote }}
該repeat
函數會將給定的字符串重復3次返回,所以我們將得到這個輸出:
helm install --dry-run --debug . [debug] Created tunnel using local port: '37071' ....... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: morbid-fox-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango"
default 函數
另外一個我們會經常使用的一個函數是default 函數
:default DEFAULT_VALUE GIVEN_VALUE
。該函數允許我們在模板內部指定默認值,以防止該值被忽略掉了。比如我們來修改上面的 ConfigMap 的模板:(templates/configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: {{ .Values.hello | default "Hello World" | quote }} k8s: {{ .Values.course.k8s | upper | quote }} python: {{ .Values.course.python | repeat 3 | quote }}
由於我們的values.yaml
文件中只定義了 course 結構的信息,並沒有定義 hello 的值,所以如果沒有設置默認值的話是得不到{{ .Values.hello }}
的值的,這里我們為該值定義了一個默認值:Hello World
,所以現在如果在values.yaml
文件中沒有定義這個值,則我們也可以得到默認值:
helm install --dry-run --debug . [debug] Created tunnel using local port: '42931' [debug] SERVER: "127.0.0.1:42931" [debug] Original chart version: "" [debug] CHART PATH: /root/hello-helm ........ --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: boiling-lynx-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango"
我們可以看到myvalue
值被渲染成了Hello World,證明我們的默認值生效了。
模板之控制流程
模板函數和管道
是通過轉換信息並將其插入到YAML
文件中的強大方法。但有時候需要添加一些比插入字符串更復雜一些的模板邏輯。這就需要使用到模板語言中提供的控制結構了。
控制流程為我們提供了控制模板生成流程的一種能力,Helm 的模板語言提供了以下幾種流程控制:
if/else
條件塊- with 指定范圍
- range 循環塊
除此之外,它還提供了一些聲明和使用命名模板段的操作:
define
在模板中聲明一個新的命名模板template
導入一個命名模板block
聲明了一種特殊的可填寫的模板區域
關於命名模板
的相關知識點,我們會在后面的課程中和大家接觸到,這里我們暫時和大家介紹if/else
、with
、range
這3中控制流程的用法。
if/else 條件
if/else
塊是用於在模板中有條件地包含文本塊的方法,條件塊的基本結構如下:
{{ if PIPELINE }} # Do something {{ else if OTHER PIPELINE }} # Do something else {{ else }} # Default case {{ end }}
當然要使用條件塊就得判斷條件是否為真,如果值為下面的幾種情況,則管道的結果為 false:
- 一個布爾類型的
假
- 一個數字
零
- 一個
空
的字符串 - 一個
nil
(空或null
) - 一個空的集合(
map
、slice
、tuple
、dict
、array
)
除了上面的這些情況外,其他所有條件都為真
。
同樣還是以上面的 ConfigMap 模板文件為例,添加一個簡單的條件判斷,如果 python 被設置為 django,則添加一個web: true
:(tempaltes/configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: {{ .Values.hello | default "Hello World" | quote }} k8s: {{ .Values.course.k8s | upper | quote }} python: {{ .Values.course.python | repeat 3 | quote }} {{ if eq .Values.course.python "django" }}web: true{{ end }}
在上面的模板文件中我們增加了一個條件語句判斷{{ if eq .Values.course.python "django" }}web: true{{ end }}
,其中運算符eq
是判斷是否相等的操作,除此之外,還有ne
、lt
、gt
、and
、or
等運算符都是 Helm 模板已經實現了的,直接使用即可。這里我們{{ .Values.course.python }}
的值在values.yaml
文件中默認被設置為了django,所以正常來說下面的條件語句判斷為真,所以模板文件最終被渲染后會有web: true
這樣的的一個條目:
helm install --dry-run --debug . [debug] Created tunnel using local port: '41513' ..... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: vehement-toad-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango" web: true
可以看到上面模板被渲染后出現了web: true
的條目,如果我們在安裝的時候覆蓋下 python 的值呢,比如我們改成 ai:
helm install --dry-run --debug --set course.python=ai . [debug] Created tunnel using local port: '42231' .... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: terrific-snail-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "aiaiai"
根據我們模板文件中的定義,如果{{ .Values.course.python }}
的值為django
的話就會新增web: true
這樣的一個條目,但是現在我們是不是通過參數--set
將值設置為了 ai,所以這里條件判斷為假,正常來說就不應該出現這個條目了,上面我們通過 debug 模式查看最終被渲染的值也沒有出現這個條目,證明條件判斷是正確的。
空格控制
上面我們的條件判斷語句是在一整行中的,如果平時經常寫代碼的同學可能非常不習慣了,我們一般會將其格式化為更容易閱讀的形式,比如:
{{ if eq .Values.course.python "django" }} web: true {{ end }}
這樣的話看上去比之前要清晰很多了,但是我們通過模板引擎來渲染一下,會得到如下結果:
helm install --dry-run --debug . [debug] Created tunnel using local port: '36895' ....... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: clunky-hydra-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango" web: true
我們可以看到渲染出來會有多余的空行,這是因為當模板引擎運行時,它將一些值渲染過后,之前的指令被刪除,但它之前所占的位置完全按原樣保留剩余的空白了,所以就出現了多余的空行。YAML
文件中的空格是非常嚴格的,所以對於空格的管理非常重要,一不小心就會導致你的YAML
文件格式錯誤。
我們可以通過使用在模板標識{{
后面添加破折號和空格{{-
來表示將空白左移,而在}}
前面添加一個空格和破折號-}}
表示應該刪除右邊的空格,另外需要注意的是換行符也是空格!
使用這個語法,我們來修改我們上面的模板文件去掉多余的空格:(templates/configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: {{ .Values.hello | default "Hello World" | quote }} k8s: {{ .Values.course.k8s | upper | quote }} python: {{ .Values.course.python | repeat 3 | quote }} {{- if eq .Values.course.python "django" }} web: true {{- end }}
現在我們來查看上面模板渲染過后的樣子:
helm install --dry-run --debug . [debug] Created tunnel using local port: '46673' ...... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: foolhardy-buffalo-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango" web: true
現在是不是沒有多余的空格了,另外我們需要謹慎使用-}}
,比如上面模板文件中:
python: {{ .Values.course.python | repeat 3 | quote }} {{- if eq .Values.course.python "django" -}} web: true {{- end }}
如果我們在if
條件后面增加-}}
,這會渲染成:
helm install --dry-run --debug . [debug] Created tunnel using local port: '33234' ..... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: juiced-lionfish-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango"web: true
因為-}}
它刪除了雙方的換行符,顯然這是不正確的。
使用 with 修改范圍
接下來我們來看下with
關鍵詞的使用,它用來控制變量作用域。還記得之前我們的{{ .Release.xxx }}
或者{{ .Values.xxx }}
嗎?其中的.
就是表示對當前范圍的引用,.Values
就是告訴模板在當前范圍中查找Values
對象的值。而with
語句就可以來控制變量的作用域范圍,其語法和一個簡單的if
語句比較類似:
{{ with PIPELINE }}
# restricted scope
{{ end }}
with
語句可以允許將當前范圍.
設置為特定的對象,比如我們前面一直使用的.Values.course
,我們可以使用with
來將.
范圍指向.Values.course
:(templates/configmap.yaml)
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: {{ .Values.hello | default "Hello World" | quote }} {{- with .Values.course }} k8s: {{ .k8s | upper | quote }} python: {{ .python | repeat 3 | quote }} {{- if eq .python "django" -}} web: true {{- end }} {{- end }}
可以看到上面我們增加了一個{{- with .Values.course }}xxx{{- end }}
的一個塊,這樣的話我們就可以在當前的塊里面直接引用.python
和.k8s
了,而不需要進行限定了,這是因為該with
聲明將.
指向了.Values.course
,在{{- end }}
后.
就會復原其之前的作用范圍了,我們可以使用模板引擎來渲染上面的模板查看是否符合預期結果。
不過需要注意的是在with
聲明的范圍內,此時將無法從父范圍訪問到其他對象了,比如下面的模板渲染的時候將會報錯,因為顯然.Release
根本就不在當前的.
范圍內,當然如果我們最后兩行交換下位置就正常了,因為{{- end }}
之后范圍就被重置了:
{{- with .Values.course }} k8s: {{ .k8s | upper | quote }} python: {{ .python | repeat 3 | quote }} release: {{ .Release.Name }} {{- end }}
range 循環
如果大家對編程語言熟悉的話,幾乎所有的編程語言都支持類似於for
、foreach
或者類似功能的循環機制,在 Helm 模板語言中,是使用range
關鍵字來進行循環操作。
我們在values.yaml
文件中添加上一個列表:
course: k8s: devops python: django courselist: - k8s - python - search - golang
現在我們有一個列表,修改 ConfigMap 模板文件來循環打印出該列表:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: myvalue: {{ .Values.hello | default "Hello World" | quote }} {{- with .Values.course }} k8s: {{ .k8s | upper | quote }} python: {{ .python | repeat 3 | quote }} {{- if eq .python "django" }} web: true {{- end }} {{- end }} courselist: {{- range .Values.courselist }} - {{ . | title | quote }} {{- end }}
可以看到最下面我們使用了一個range
函數,該函數將會遍歷{{ .Values.courselist }}
列表,循環內部我們使用的是一個.
,這是因為當前的作用域就在當前循環內,這個.
從列表的第一個元素一直遍歷到最后一個元素,然后在遍歷過程中使用了title
和quote
這兩個函數,前面這個函數是將字符串首字母變成大寫,后面就是加上雙引號變成字符串,所以按照上面這個模板被渲染過后的結果為:
helm install --dry-run --debug . [debug] Created tunnel using local port: '45869' ......... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: knotted-porcupine-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango" web: true courselist: - "K8s" - "Python" - "Search" - "Golang"
我們可以看到courselist
按照我們的要求循環出來了。除了 list 或者 tuple,range 還可以用於遍歷具有鍵和值的集合(如map 或 dict),這個就需要用到變量的概念了。
變量
前面我們知道了函數、管理以及控制流程的使用方法,我們知道編程語言中還有一個很重要的概念叫:變量,在 Helm 模板中,使用變量的場合不是特別多,但是在合適的時候使用變量可以很好的解決我們的問題。如下面的模板:
{{- with .Values.course }} k8s: {{ .k8s | upper | quote }} python: {{ .python | repeat 3 | quote }} release: {{ .Release.Name }} {{- end }}
我們在with
語句塊內添加了一個.Release.Name
對象,但這個模板是錯誤的,編譯的時候會失敗,這是因為.Release.Name
不在該with
語句塊限制的作用范圍之內,我們可以將該對象賦值給一個變量可以來解決這個問題:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: {{- $releaseName := .Release.Name -}} {{- with .Values.course }} k8s: {{ .k8s | upper | quote }} python: {{ .python | repeat 3 | quote }} release: {{ $releaseName }} {{- end }}
我們可以看到我們在with
語句上面增加了一句{{- $releaseName := .Release.Name -}}
,其中$releaseName
就是后面的對象的一個引用變量,它的形式就是$name
,賦值操作使用:=
,這樣with
語句塊內部的$releaseName
變量仍然指向的是.Release.Name
,同樣,我們 DEBUG 下查看結果:
helm install --dry-run --debug . [debug] Created tunnel using local port: '33977' ....... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: giddy-puffin-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango" release: giddy-puffin
可以看到已經正常了,另外變量在range
循環中也非常有用,我們可以在循環中用變量來同時捕獲索引的值:
courselist: {{- range $index, $course := .Values.courselist }} - {{ $index }}: {{ $course | title | quote }} {{- end }}
例如上面的這個列表,我們在range
循環中使用$index
和$course
兩個變量來接收后面列表循環的索引和對應的值,最終可以得到如下結果:
helm install --dry-run --debug . [debug] Created tunnel using local port: '46754' ...... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: pouring-zebu-configmap data: myvalue: "Hello World" k8s: "DEVOPS" python: "djangodjangodjango" release: pouring-zebu courselist: - 0: "K8s" - 1: "Python" - 2: "Search" - 3: "Golang"
我們可以看到 courselist 下面將索引和對應的值都打印出來了,實際上具有鍵和值的數據結構我們都可以使用range
來循環獲得二者的值,比如我們可以對.Values.course
這個字典來進行循環:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: {{- range $key, $value := .Values.course }} {{ $key }}: {{ $value | quote }} {{- end }}
直接使用range
循環,用變量$key
、$value
來接收字段.Values.course
的鍵和值。這就是變量在 Helm 模板中的使用方法。
模板之命名模板
命名模板我們也可以稱為子模板,是限定在一個文件內部的模板,然后給一個名稱。在使用命名模板的時候有一個需要特別注意的是:模板名稱是全局的,如果我們聲明了兩個相同名稱的模板,最后加載的一個模板會覆蓋掉另外的模板,由於子 chart 中的模板也是和頂層的模板一起編譯的,所以在命名的時候一定要注意,不要重名了。
為了避免重名,有個通用的約定就是為每個定義的模板添加上 chart 名稱:{{define "mychart.labels"}}
,define
關鍵字就是用來聲明命名模板的,加上 chart 名稱就可以避免不同 chart 間的模板出現沖突的情況。
聲明和使用命名模板
使用define
關鍵字就可以允許我們在模板文件內部創建一個命名模板,它的語法格式如下:
{{ define "ChartName.TplName" }} # 模板內容區域 {{ end }}
比如,現在我們可以定義一個模板來封裝一個 label 標簽:
{{- define "mychart.labels" }} labels: from: helm date: {{ now | htmlDate }} {{- end }}
然后我們可以將該模板嵌入到現有的 ConfigMap 中,然后使用template
關鍵字在需要的地方包含進來即可:
{{- define "mychart.labels" }} labels: from: helm date: {{ now | htmlDate }} {{- end }} apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap {{- template "mychart.labels" }} data: {{- range $key, $value := .Values.course }} {{ $key }}: {{ $value | quote }} {{- end }}
我們這個模板文件被渲染過后的結果如下所示:
helm install --dry-run --debug . [debug] Created tunnel using local port: '38374' ....... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: esteemed-scorpion-configmap labels: from: helm date: 2019-03-06 data: k8s: "devops" python: "django"
我們可以看到define
區域定義的命名模板被嵌入到了template
所在的區域,但是如果我們將命名模板全都寫入到一個模板文件中的話無疑也會增大模板的復雜性。
還記得我們在創建 chart 包的時候,templates 目錄下面默認會生成一個_helpers.tpl
文件嗎?我們前面也提到過 templates 目錄下面除了NOTES.txt
文件和以下划線_
開頭命令的文件之外,都會被當做 kubernetes 的資源清單文件,而這個下划線開頭的文件不會被當做資源清單外,還可以被其他 chart 模板中調用,這個就是 Helm 中的partials
文件,所以其實我們完全就可以將命名模板定義在這些partials
文件中,默認就是_helpers.tpl
文件了。
現在我們將上面定義的命名模板移動到 templates/_helpers.tpl 文件中去:
{{/* 生成基本的 labels 標簽 */}} {{- define "mychart.labels" }} labels: from: helm date: {{ now | htmlDate }} {{- end }}
一般情況下面,我們都會在命名模板頭部加一個簡單的文檔塊,用/**/
包裹起來,用來描述我們這個命名模板的用途的。
現在我們講命名模板從模板文件 templates/configmap.yaml 中移除,當然還是需要保留 template 來嵌入命名模板內容,名稱還是之前的 mychart.lables,這是因為模板名稱是全局的,所以我們可以能夠直接獲取到。我們再用 DEBUG 模式來調試下是否符合預期?
模板范圍
上面我們定義的命名模板中,沒有使用任何對象,只是使用了一個簡單的函數,如果我們在里面來使用 chart 對象相關信息呢:
{{/* 生成基本的 labels 標簽 */}} {{- define "mychart.labels" }} labels: from: helm date: {{ now | htmlDate }} chart: {{ .Chart.Name }} version: {{ .Chart.Version }} {{- end }}
如果這樣的直接進行渲染測試的話,是不會得到我們的預期結果的:
helm install --dry-run --debug . [debug] Created tunnel using local port: '35119' .......... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: killjoy-poodle-configmap labels: from: helm date: 2019-03-06 chart: version: data: k8s: "devops" python: "django"
chart 的名稱和版本都沒有正確被渲染,這是因為他們不在我們定義的模板范圍內,當命名模板被渲染時,它會接收由 template 調用時傳入的作用域,由於我們這里並沒有傳入對應的作用域,因此模板中我們無法調用到 .Chart 對象,要解決也非常簡單,我們只需要在 template 后面加上作用域范圍即可:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap {{- template "mychart.labels" . }} data: {{- range $key, $value := .Values.course }} {{ $key }}: {{ $value | quote }} {{- end }}
我們在 template 末尾傳遞了.
,表示當前的最頂層的作用范圍,如果我們想要在命名模板中使用.Values
范圍內的數據,當然也是可以的,現在我們再來渲染下我們的模板:
helm install --dry-run --debug . [debug] Created tunnel using local port: '43331' ......... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: manageable-squid-configmap labels: from: helm date: 2019-03-06 chart: hello-helm version: 0.1.0 data: k8s: "devops" python: "django"
我們可以看到 chart 的名稱和版本號都已經被正常渲染出來了。
include 函數
假如現在我們將上面的定義的 labels 單獨提取出來放置到 _helpers.tpl 文件中:
{{/* 生成基本的 labels 標簽 */}} {{- define "mychart.labels" }} from: helm date: {{ now | htmlDate }} chart: {{ .Chart.Name }} version: {{ .Chart.Version }} {{- end }}
現在我們將該命名模板插入到 configmap 模板文件的 labels 部分和 data 部分:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap labels: {{- template "mychart.labels" . }} data: {{- range $key, $value := .Values.course }} {{ $key }}: {{ $value | quote }} {{- end }} {{- template "mychart.labels" . }}
然后同樣的查看下渲染的結果:
helm install --dry-run --debug . [debug] Created tunnel using local port: '40094' ........... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: misty-mandrill-configmap labels: from: helm date: 2019-03-06 chart: hello-helm version: 0.1.0 data: k8s: "devops" python: "django" from: helm date: 2019-03-06 chart: hello-helm version: 0.1.0
我們可以看到渲染結果是有問題的,不是一個正常的 YAML 文件格式,這是因為template
只是表示一個嵌入動作而已,不是一個函數,所以原本命名模板中是怎樣的格式就是怎樣的格式被嵌入進來了,比如我們可以在命名模板中給內容區域都空了兩個空格,再來查看下渲染的結構:
helm install --dry-run --debug . [debug] Created tunnel using local port: '45696' .......... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: exiled-otter-configmap labels: from: helm date: 2019-03-06 chart: hello-helm version: 0.1.0 data: k8s: "devops" python: "django" from: helm date: 2019-03-06 chart: hello-helm version: 0.1.0
我們可以看到 data 區域里面的內容是渲染正確的,但是上面 labels 區域是不正常的,因為命名模板里面的內容是屬於 labels 標簽的,是不符合我們的預期的,但是我們又不可能再去把命名模板里面的內容再增加兩個空格,因為這樣的話 data 里面的格式又不符合預期了。
為了解決這個問題,Helm 提供了另外一個方案來代替template
,那就是使用include
函數,在需要控制空格的地方使用indent
管道函數來自己控制,比如上面的例子我們替換成include
函數:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap labels: {{- include "mychart.labels" . | indent 4 }} data: {{- range $key, $value := .Values.course }} {{ $key }}: {{ $value | quote }} {{- end }} {{- include "mychart.labels" . | indent 2 }}
在 labels 區域我們需要4個空格,所以在管道函數indent
中,傳入參數4就可以,而在 data 區域我們只需要2個空格,所以我們傳入參數2即可以,現在我們來渲染下我們這個模板看看是否符合預期呢:
helm install --dry-run --debug . [debug] Created tunnel using local port: '39206' ......... --- # Source: hello-helm/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: foolhardy-bison-configmap labels: from: helm date: 2019-03-06 chart: hello-helm version: 0.1.0 data: k8s: "devops" python: "django" from: helm date: 2019-03-06 chart: hello-helm version: 0.1.0
可以看到是符合我們的預期,所以在 Helm 模板中我們使用 include 函數要比 template 更好,可以更好地處理 YAML 文件輸出格式。