Go Helm
時間救星
Helm將一個應用所需要宣告的所有Kubernetes資源,以模板的形式打包到一個資料夾下,然後通過預定義的或使用者自定義的K-V檔案進行渲染,最後排序後一併提交到Kubernetes執行。Helm還提供一個額外的抽象release,它存在的形式是 ConfigMap
,記錄每一次應用的釋出。如果你對如何利用Kubernetes部署不同型別的應用沒有經驗,那麼可以先行閱讀 ofollow,noindex" target="_blank">官方文件 。如果你無需瞭解如何使用Helm部署你的複雜應用,那麼瞭解上述內容已經足夠。
Helm & Kubernetes
Helm
是一款服務於Kubernetes生態的工具。根據 官方文件 的解釋,Helm是一個管理Kubernetes Chart
的工具。而Chart是預配置Kubernetes資源的包。從功能上來說,Helm是一個生成預定義 全棧應用
配置包,根據自定義的引數來渲染配置包,並在Kubernetes叢集中宣告對應的資源物件的工具。Chart全棧地定義了一個應用,不僅僅是應用本身,包括它依賴的DB、訊息元件、一致性元件等其他應用,結合自定義的引數,Chart可以渲染為Kubernetes認識的資源物件描述檔案,例如deployment.yaml。
熟悉Kubernetes的同學應該清楚,Kubernetes沒有定義一個型別叫做 Application
。Kubernetes倡導微服務架構,一個複雜應用應當是一個抽象的概念,由若干個鬆散耦合的微服務共同組成。雖然Kubernetes在DevOps上做得非常好,但對於使用者來說,手動地定義、部署、運維若干個微服務,而不是一個應用,代價是相當高昂的。Helm的出現就是為了解決微服務架構的這個痛點,通過 預定義檔案
與 自定義配置檔案
的組合,來定義一個複雜應用,並在Kubernetes中宣告它。
有了Helm、Chart、Kubernetes,我們可以做到類似 一鍵建站
的敏捷性,並且在拆分應用、實踐微服務架構的過程中,避開鬆散耦合帶來的管理痛點。
使用Helm
先前條件:
安裝 Helm命令列工具,並在Kubernetes叢集中安裝Tiller。
-
編寫或獲取Charts
我們可以從Repository獲取Charts,也可以從零開始或基於Repository的成品或半成品自定義自己需要的Charts。
自定義:
➜~ helm create test-andy Creating test-andy ➜~ cd test-andy ➜test-andy ls Chart.yamlchartstemplatesvalues.yaml ➜test-andy cat Chart.yaml apiVersion: v1 description: A Helm chart for Kubernetes name: test-andy version: 0.1.0 ➜test-andy ls templates NOTES.txt_helpers.tpldeployment.yaml ingress.yamlservice.yaml ➜test-andy cat values.yaml # Default values for test-andy. # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 ...
獲取:
➜~ helm search wordpress NAMEVERSION DESCRIPTION stable/wordpress2.0.0Web publishing platform for building blogs and ... ➜~ helm fetch stable/wordpress --untar=true # untar=true 可以解開 ➜~ ls wordpress Chart.yamlchartsrequirements.yaml values.yaml README.mdrequirements.lock templates
-
安裝
helm可以通過命令
helm install
安裝Charts或Charts壓縮包.tgz
。可以在引數中使用自定義的values.yaml
檔案或自定義的k-v引數,它們總是會覆蓋Charts或.tgz
檔案中的值。可以通過命令helm list
檢視已經安裝的release。➜~ helm install --name my-test-andy --set "key1=value1,key2=value2" ./test-andy
- 更新或解除安裝release
helm可以通過命令helm upgrade
和helm uninstall
來更新或解除安裝已經安裝的release。➜~ helm upgrade my-test-andy test-andy --set "key1=value1,key2=value2" ➜~ helm uninstall my-test-andy
Charts與模板
Helm使用Chart模板加上預定義或自定義的值來渲染出所需要的 .yaml
檔案,通過Kubernetes建立對應的資源。模板與引數共同決定了Helm應用的行為。Helm支援標準Go模板語言,支援約60個來自 Go Template Language
以及 Sprig template library
的自定義函式,同樣支援管道、條件語句等特性。本節將在介紹 WordPress
模板的過程中,簡要介紹Helm使用的模板語言,並介紹Chart的一些特性。
➜wordpress tree . |____Chart.yaml# 該Chart的描述檔案 |____LICENSE |____README.md |____charts# 依賴的Chart的集合資料夾 |____requirements.yaml# 描述當前Chart依賴的其他Chart |____templates# 該Chart所需的Kubernetes物件模板 | |____deployment.yaml# deployment模板 | |____NOTES.txt# 該模板在release安裝完成後會列印到控制檯 | |____secrets.yaml# 儲存secrect敏感配置資訊的物件模板 | |____svc.yaml# service物件模板 | |____tests# 用於測試的pod物件描述集合 | | |____test-mariadb-connection.yaml# 用於測試的pod物件描述 |____values.yaml# 預定義的值,它們將被加入Value物件
如上所示,Chart目錄實際上包含了定義以及安裝WordPress應用所需要的所有元素。上述第一層檔案目錄中的的所有元素皆為Chart保留的檔案及資料夾。其他的檔案將被保留,不作處理。
除了應用執行時Kubernetes資源以外,Helm還額外增加了一些 hook
、 test
物件,在應用安裝的過程中,以及應用執行時,執行任務,並驗證、定位問題。這些物件實際上是打上 annotations: "helm.sh/hook"
的Kubernetes Pod
或 Job
,如下所示:
# 這是wordpress中的一個test,test-mariadb-connection.yaml {{- if .Values.mariadb.enabled }} apiVersion: v1 kind: Pod metadata: name: "{{ .Release.Name }}-credentials-test" annotations: "helm.sh/hook": test-success spec: containers: - name: {{ .Release.Name }}-credentials-test image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" restartPolicy: Never ... {{- end }}
hook會在Release生命週期中被執行,而test會在helm客戶端執行命令 helm test <ReleaseName>
被執行。
上述test中,多處使用到了Helm模板命令,如 {{ .Release.Name }}
。它們都被 {{
與 }}
包裹。對 .Release.Name
,開頭的 .
表示使用當前的上下文, Release
與 Name
皆為物件名。Release為helm內建物件,類似的命名還有 Template
, Capabilities
等,詳見 內建物件簡介 。其他的引用方式,例如 .Value.image.registry
,則表示Values物件下的image物件的registry物件。Value物件來自 value.yaml
以及命令列的 --set
以及 --values
定義的值,它們生效的優先順序遵循頂層優先原則,後申明的值會覆蓋先宣告的值。
更加複雜一點的語法還有類似Unix系統的管道,函式,以及 IF/ELSE
、 RANGE
控制的模板塊。詳見 文件 。
實際上,從應用的角度,我們需要設計並實現兩個Helm應用,WordPress與資料庫。你當然可以在一個Chart中完整地描述這兩個元件,不過在所有的應用中都描述一次資料庫顯然顯得過於冗雜。Helm提供一種更優雅的方式來解決這個問題:專注設計WordPress,以依賴的方式在WordPress中引用資料庫Helm。
➜wordpress cat requirements.yaml dependencies: - name: mariadb version: 4.x.x repository: https://kubernetes-charts.storage.googleapis.com/ condition: mariadb.enabled tags: - wordpress-database
如上所示,在 requirements.yaml
中宣告依賴,helm將在 repository
中下載對應的chart到 ./charts
下。
➜wordpress cd charts ➜charts tree . |____mariadb | |____.helmignore | |____Chart.yaml | |____OWNERS | |____README.md | |____templates | | |_____helpers.tpl | | |____master-configmap.yaml | | |____master-statefulset.yaml | | |____master-svc.yaml | | |____NOTES.txt | | |____secrets.yaml | | |____slave-configmap.yaml | | |____slave-statefulset.yaml | | |____slave-svc.yaml | | |____test-runner.yaml | | |____tests.yaml | |____values-production.yaml | |____values.yaml
最後,這裡給出一個在阿里雲環境安裝WordPress的解決方案。在阿里雲環境,容器服務打通了自動購買SLB的流程,但沒有打通購買持久化儲存的流程,因此,首先需要購買NAS並在叢集中宣告PersistenceVolume物件,如下所示。
➜helm-test cat wp-nas.yaml apiVersion: v1 kind: PersistentVolume metadata: name: wp-nas spec: accessModes: - ReadWriteOnce capacity: storage: 40Gi flexVolume: driver: alicloud/nas options: path: /wp server: {{ mountDomain }} # 掛載到交換機上的域名 vers: "4.0" storageClassName: nas ➜helm-test kubectl apply -f wp-nas.yaml
然後執行下面的命令部署helm。
➜helm-test helm fetch --untarstable/wordpress ➜helm-test helm install --name wordpress-test --debug --set "persistence.enabled=false,mariadb.master.persistence.storageClass=nas" wordpress ➜helm-test kubectl get deployment wordpress-test-wordpress NAMEDESIREDCURRENTUP-TO-DATEAVAILABLEAGE wordpress-test-wordpress11111m
至此,在阿里雲容器服務下建立你的WordPress應用圓滿成功。
Helm整體架構與實現分析
跟大部分其他的Kubernetes生態工具一樣,Helm也將自身的服務端沉入Kubernetes中,成為一個名稱空間為kube-system的應用(Tiller),並提供外部的命令列工具與Tiller進行互動。因此,Helm從架構上來說,分為兩大塊: Helm-client
, Tiller-server
。在介紹這兩塊之前,先明確幾個概念。
Chart: Chart是定義一個Kubernetes應用的資訊的集合。
Config: Config包含了一系列配置資訊。將這些配置資訊與Chart組合,可以用來在Kubernetes中建立一個Release。
Release: Release上述Chart與Config組合的執行時例項,可以理解為Kubernetes中一個抽象的應用。Release包含Helm建立的應用的元資料資訊。
Repository: Repository是一個存放釋出Chart的倉庫,開發者可以將自制的Chart釋出到倉庫分享。
Helm-client是一個命令列工具,它負責如下工作:
- 在本地實現Chart的研發。
- 配置管理Chart倉庫Repository。
- 與Tiller進行互動,包括上傳需要安裝的Chart,獲取Release執行時資訊,更新或解除安裝已經存在的Release。
Tiller-server是一個Kubernetes叢集中的應用,它負責如下工作:
- 與Helm-client互動,響應client的請求。
- 將Chart於Config融合為一個可以建立執行在Kubernetes叢集中的Release的資源物件。
- 建立相關的Release。
- 對Release進行更新或解除安裝。
Helm的實現完全使用了與Kubernetes相同的技術棧,使用Go語言實現客戶端與服務端,採用RPC/">gRPC作為客戶端服務端互動的協議,服務端與Kubernetes集×××互採用官方提供的REST API,沒有采用hack的方式,越過Kubernetes API Server直接操作Kubernetes叢集。除了Kubernetes,Tiller-server是一個無依賴無狀態的應用,它的狀態依靠Kubernetes中的ConfigMap來實現持久化,儲存release的元資料與狀態,不需要額外的DB。
從上述的介紹來看,Helm是一個輕量級的,標準化的,完全依附於並服務於Kubernetes生態的一個工具。我們甚至可以在短時間之內重新設計一個類似的產品,在Kubernetes外部來實現相同的能力。
下面以一次建立為例,說明Tiller的實現(原始碼可以在 pkg/tiller/release_install.go#InstallRelease
中看到):
-
- prepare
-
- validation: 驗證命名唯一以及資源用量。
... name, err := s.uniqName(req.Name, req.ReuseName) ... caps, err := capabilities(s.clientset.Discovery()) ...
- validation: 驗證命名唯一以及資源用量。
-
- renderResources: 解析依賴,利用values.yaml檔案生成的k-v來渲染Charts,生成hooks以及manifest物件,最後結合為Release物件。hooks、manifest中Kubernetes資源編排順序由兩個物件來確定:
pkg/tiller/kind_sorter.go#UninstallOrder
、pkg/tiller/kind_sorter.go#InstallOrder
,相同型別的資源按命名排序(這個順序對於Kubernetes系統來說其實並不太敏感)valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) ... // 將chart與values融合為manifest,也就是可以被k8s認識的一系列k8s資源的排列 hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) //生成release物件,跟蹤它的版本、行為、狀態等資訊 rel := &release.Release{ ... }
- renderResources: 解析依賴,利用values.yaml檔案生成的k-v來渲染Charts,生成hooks以及manifest物件,最後結合為Release物件。hooks、manifest中Kubernetes資源編排順序由兩個物件來確定:
-
- prepare
-
- performRelease
-
- 執行hooks。包括自定義資源的install-hooks以及pre-install-hooks,並在過程中再次驗證manifest是否正確。
if !req.DisableHooks && !req.DisableCrdHook { if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.CRDInstall, req.Timeout); err != nil { fmt.Printf("Finished installing CRD: %s", err) return res, err } } ... if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil { return res, fmt.Errorf("validation failed: %s", err) } ... if !req.DisableHooks { if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { return res, err } }
- 執行hooks。包括自定義資源的install-hooks以及pre-install-hooks,並在過程中再次驗證manifest是否正確。
-
- 2. 判斷該次操作是建立或更新,呼叫相應的Kubernetes-client端介面,記錄release資訊。 ```go switch h, err := s.env.Releases.History(req.Name); // 資源存在,更新操作,記錄release case: case req.ReuseName && err == nil && len(h) >= 1: if err := s.ReleaseModule.Update(old, r, updateReq, s.env); err != nil { ... s.recordRelease(old, true) s.recordRelease(r, true) ... } // 資源不存在,建立,記錄release default: if err := s.ReleaseModule.Create(r, req, s.env); err != nil { ... s.recordRelease(r, true) ... }
-
- 執行post-install-hooks。
if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil { ... s.recordRelease(r, true) ... }
- 執行post-install-hooks。
- performRelease
其他的Tiller能力實現與install大同小異,基本都為渲染hooks、manifest、release物件,然後記錄並執行release,通過呼叫Kubernetes REST API,在Kubernetes中宣告相應的資源即可。
Helm實現的缺陷
Helm這款工具非常簡單優雅,在社群內幾乎沒有對它的詬病。但是作者個人認為,它的依賴部分的設計存在一些不足之處。由於水平有限,歡迎各位讀者指正。
Helm對依賴的理解,事實上是將依賴的元件,例如資料庫、中介軟體、鑑權元件等,打包到應用的整個描述檔案中,部署一個由當前應用獨佔的資源。然而在現實中大部分場景下,這類元件一般不是應用獨佔的,這會造成很大的浪費。Helm的設計,應當允許使用者選擇另一種依賴形式:以非獨佔的方式,依賴這型別可以複用的Kubernetes的資源。
舉個例子:A應用依賴並部署了一個MQ元件,MQ元件暴露出服務service-mq,那麼B應用完全可以複用該MQ元件,使用service-mq暴露的服務。
我們當然可以採用一些tricks來解決這個問題,例如將共同的元件抽取出來,單獨部署,然後在依賴它的應用中通過硬編碼或者動態配置的方式注入元件的資訊。然而這種方式從一定程度上違背了Helm一鍵部署全棧應用的思路。公用元件應該是作為應用的依賴部署的,而不是單獨部署的,否則Helm就失去了定義一個全棧應用的意義,不斷拆分的服務將重新令Kubernetes應用的部署運維變得複雜。在上述場景中,可以在B應用部署時,通過依賴檢查,可以發現存在一個符合要求(映象名+版本+允許共享+...)的MQ元件,並且它暴露了服務,那麼B應用就不會去建立一個新的MQ元件,從而將資源節約出來。當然,這種設計方式,有很多問題需要解決,例如如何在兩個應用之間共享公共元件的描述資訊以及配置資訊。
上述設計,既可以滿足使用者一鍵部署全棧應用的需求,也可以滿足使用者希望複用元件,節約成本的訴求。
總結
在Kubernetes生態中,通過Helm完成對全棧應用的定義,可以很容易做到一鍵建站。
幾乎每一個開源產品都有值得我們學習的點,發現這些點其實並不難,將開源產品優秀的思想、設計與自身從事的產品研發工作相結合,找到可以真正實現的改進的點,才是最困難的。希望在以後的工作與學習中,可以不忘初心,儘可能跟上社群的腳步。
文章的部分觀點限於作者水平,可能存在客觀錯誤或不足之處,尚希各位讀者指正。
轉載,請聯絡:微信(zjjxg2018)