K8s應用部署:Helm Chart倉庫搭建與版本管理實踐)
企業(yè)項目里的k8s用了一段時間后你會發(fā)現(xiàn)每天寫那一堆YAML簡直是噩夢。同一個應用在開發(fā)、測試、生產(chǎn)環(huán)境不同配置變來變?nèi)ヒ粋€服務從Deployment到Service、Ingress、ConfigMap、Secret加起來十幾份清單要發(fā)一版改一遍。今天這個系列的第16篇咱們就聊Helm這套打包、分發(fā)、版本管理的機制以及怎么在企業(yè)里搭一個自己的Chart倉庫。這套東西解決了我團隊里最頭疼的兩個問題一是“應用部署定義”沒法版本化二是新環(huán)境上線要靠人工回憶改參數(shù)。如果你也在維護多套環(huán)境、多個業(yè)務應用這篇文章應該能直接省下你不少熬夜時間。1. 為什么企業(yè)k8s項目需要Helm1.1 手動管理YAML的痛點說個我自己的經(jīng)歷。早年間做某個后臺管理系統(tǒng)的上云改造十幾個微服務每個服務一個目錄里面放著deployment.yaml、service.yaml、configmap.yaml。每次要發(fā)布一個新版本我需要在服務器上執(zhí)行一串kubectl apply -f然后自己盯著Pod狀態(tài)。這還算好的更崩潰的是換一個環(huán)境比如從測試環(huán)境切到生產(chǎn)環(huán)境我得手工把鏡像tag改掉、把副本數(shù)調(diào)高、把某些配置項換掉稍有不慎漏改一個字段線上就出幺蛾子。另一個痛點是用文件復制的方式來“備份”應用定義根本沒法回溯。昨天改完的YAML到底和上周有什么差異靠肉眼diff整目錄文件那是反人類的。一旦出問題想回滾只能寄希望于自己還記得之前是什么樣。1.2 Helm的三層模型Chart、Release、RepositoryHelm的核心思想特別樸素把一組相關的k8s資源定義打包成一個應用包叫Chart這個包安裝到集群里就產(chǎn)生一個Release一次發(fā)布的實例而包本身可以放到一個共享倉庫Repository里供團隊下載。這三個概念配合起來解決了我的痛點Chart就是一個目錄/壓縮包里面含有Chart.yaml元數(shù)據(jù)、values.yaml默認參數(shù)、templates/模板文件、charts/依賴的子Chart。Release一條記錄表示“這個Chart在某一次安裝/升級后生成的實際K8s資源集合”。每次升級都會生成新的版本記錄可以隨時回滾。Repository存放Chart包的HTTP服務器可以理解成YUM倉庫、npm私服的翻版供helm search、install時拉取。1.3 Helm帶來的企業(yè)級價值對團隊協(xié)作來說Helm真正的價值不只是少寫幾個YAML而是把“應用部署定義”當成軟件制品來管理。部署什么版本、什么參數(shù)、什么依賴全部被Chart文件化、版本化、可審計。后續(xù)不管誰來發(fā)布只要拿著Chart包和values文件就能在另一個集群完整復現(xiàn)一套應用。對于管理層來說“上線可回滾、變更可追溯”這種要求用Helm比用散裝YAML容易落地得多。注意這里說的版本管理主要靠Chart包的版本和Release的版本記錄而不是簡單靠Git記錄。兩者要配合起來用。2. Helm環(huán)境準備與基礎使用2.1 安裝Helm客戶端Helm 3是當前主流它去掉了Tiller客戶端直接和kube-apiserver交互權限取決于你的kubeconfig安全性好了很多。安裝方法很簡單也可以從官方GitHub下載二進制然后放到/usr/local/bin下。我用的是v3.14.0wget https://get.helm.sh/helm-v3.14.0-linux-amd64.tar.gz tar -zxvf helm-v3.14.0-linux-amd64.tar.gz mv linux-amd64/helm /usr/local/bin/helm helm version裝完以后確保你的kubeconfig能連到集群。測一下kubectl cluster-info helm env建議把HELM_NAMESPACE設置成你常用的命名空間避免每次都要寫-n。不過在企業(yè)里不同業(yè)務最好還是顯式指定namespace防止串環(huán)境。2.2 常用命令速覽這些命令是我每天用到的建議先混個臉熟helm repo add 別名 地址添加Chart倉庫。helm repo update更新本地倉庫緩存。helm search repo 關鍵詞搜索倉庫里的Chart。helm install Release名 Chart -f values文件安裝一個Chart成為一個Release。helm upgrade Release名 Chart -f values文件升級Release。helm history Release名查看Release的所有版本。helm rollback Release名 版本號回滾到某個歷史版本。helm uninstall Release名卸載Release。helm template Chart本地渲染模板而不實際安裝調(diào)試用。如果用的時候記不住helm help本身就是最好的速查手冊。我強烈建議在實際操作前先拿一個簡單Chart跑通install、upgrade、rollback這一條鏈路后面才能在企業(yè)里玩得轉。2.3 第一個Chart的創(chuàng)建與安裝演示用helm create可以快速生成一個標準Chart結構helm create my-demo tree my-demo生成的結構里有幾個關鍵文件my-demo/ ├── Chart.yaml ├── charts/ ├── templates/ │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress.yaml │ ├── service.yaml │ └── serviceaccount.yaml └── values.yamlChart.yaml里最重要的是name和version這兩個字段決定了這個Chart在倉庫里的標識。比如apiVersion: v2 name: my-demo description: A Helm chart for Kubernetes type: application version: 0.1.0 appVersion: 1.16.0version是Chart版本appVersion是應用版本兩者不要混。我習慣把模板文件精簡一下只保留Deployment和Service再加個ConfigMap。values.yaml里設置鏡像地址、副本數(shù)、端口這些變量。修改好以后直接試安裝helm install my-demo-release ./my-demo --namespace dev如果模板有報錯它會直接在終端提示你。安裝完成后可以看一下Release狀態(tài)helm list -n dev helm status my-demo-release -n dev實踐中最常用的調(diào)試方式是helm template它會把模板渲染成最終的YAML輸出到終端而且不會真的創(chuàng)建資源helm template my-demo ./my-demo -f values-dev.yaml這個命令在CI里特別有用先渲染出來檢查一遍再apply能避免很多低級失誤。3. Chart倉庫搭建從零構建企業(yè)私有倉庫3.1 倉庫選型對比企業(yè)里搭建Chart倉庫主流方案大概是這三種Harbor功能最全支持鏡像倉庫和Chart倉庫帶權限控制、審計、復制適合已經(jīng)用了Harbor作為鏡像倉庫的團隊。ChartMuseum專門的Chart倉庫服務輕量、部署簡單支持很多后端存儲本地磁盤、OSS、S3等適合獨立部署。Nginx 靜態(tài)文件最簡方案直接把Chart包放到目錄里用helm repo index生成索引文件Nginx托管配合basic auth可做簡單權限。適合國內(nèi)網(wǎng)絡環(huán)境或者內(nèi)網(wǎng)小規(guī)模團隊不想多維護組件。我建議如果團隊已經(jīng)有Harbor直接用它少一套系統(tǒng)。如果只是想快速搭一個讓開發(fā)們能拉取Chart用Nginx方案最快。下面把這兩種都寫一下。3.2 基于Harbor搭建Chart倉庫如果你的Harbor版本在2.x以上默認支持OCI的Chart倉庫但傳統(tǒng)Helm 3也兼容。操作上需要先開啟Harbor的“Helm Chart”功能。我這邊步驟大概這樣首先在Harbor的web界面上創(chuàng)建一個項目例如叫k8s-charts。然后在這個項目底下把“Helm Chart”這個tab激活不同版本入口略有差異最新版在項目配置里打開“Helm Chart”開關。之后你就能得到一個倉庫地址形如https://harbor.example.com/chartrepo/k8s-charts。在客戶端側添加倉庫并認證helm repo add myrepo https://harbor.example.com/chartrepo/k8s-charts \ --username admin --password xxxx helm repo update然后打包并推送Chart。Helm 3有現(xiàn)成的helm push命令但需要先安裝helm-push插件helm plugin install https://github.com/chartmuseum/helm-push cd my-demo helm package . helm push my-demo-0.1.0.tgz myrepo如果推送沒問題你可以在Harbor頁面上看到這個Chart包點開還能看到版本、依賴等信息。注意Harbor使用過程中如果走的是自簽名HTTPS客戶端需要在Trust store里放證書否則helm repo add會報x509證書錯誤。3.3 基于Nginx搭建輕量級Chart倉庫如果你沒那么多組件依賴用一個Nginx來托Chart倉庫也很穩(wěn)。原理很簡單把Chart包和index.yaml放在同一個目錄Nginx對外提供該目錄的靜態(tài)文件服務即可。具體步驟在服務器上建目錄比如/opt/helm-repo/charts。把你打包好的*.tgz文件放進去。用helm repo index生成索引文件helm repo index /opt/helm-repo/charts --url https://charts.example.com/這會在目錄下生成一個index.yaml里面記錄了所有Chart包的名稱、版本、URL和校驗值。配置Nginxserver { listen 80; server_name charts.example.com; root /opt/helm-repo/charts; autoindex on; location / { try_files $uri $uri/ 404; } }如果要加訪問控制可以用Basic Auth生成類似Caddy或者Nginx的賬戶配置文件。在企業(yè)里建議至少套一層HTTPS和認證否則任何人都能下載內(nèi)網(wǎng)Chart包敏感信息容易泄露。在其他機器上添加這個倉庫helm repo add corp http://charts.example.com --username dev --password xxxx helm repo update helm search repo corp這樣就很方便了。我團隊早期就是這么干的內(nèi)網(wǎng)幾十個ChartNginx一臺小機器夠用了。3.4 將Chart上傳至倉庫與下載驗證倉庫搭好后需要驗證整個流轉是不是通的。流程一般是本地打包 → push/復制到倉庫 → 遠程添加倉庫 → install。以Nginx方案為例更常見的做法是在CI里用腳本把Chart包上傳到目錄然后再執(zhí)行一次helm repo index更新索引。注意helm repo index不是自動增量的它會重新掃描目錄生成新索引。如果目錄里已有舊的tgz它會一并加進去。所以更新索引這個動作要小心別把舊包丟了。驗證一下下載helm repo update helm search repo myrepo/my-demo --versions helm pull myrepo/my-demo --version 0.1.0能正常pull下來說明倉庫OK。然后可以用helm install遠程安裝這個Chart確保index.yaml里記錄的URL是客戶端可達的否則會報“chart not found”或“failed to download”。4. Helm企業(yè)級實戰(zhàn)部署、升級與回滾4.1 多環(huán)境Value管理企業(yè)里一個應用至少要在dev、test、prod三個環(huán)境各跑一套。如果每個環(huán)境都復制一份values文件會有一堆重復和容易漏改的參數(shù)。我的建議是一個Chart多個values文件用層次化組合。比如目錄結構charts/my-app/ ├── Chart.yaml ├── values.yaml # 默認值 ├── values-dev.yaml # dev覆蓋 ├── values-test.yaml # test覆蓋 └── values-prod.yaml # prod覆蓋安裝的時候helm install my-app-dev ./charts/my-app -n dev -f values-dev.yaml helm install my-app-test ./charts/my-app -n test -f values-test.yaml helm install my-app-prod ./charts/my-app -n prod -f values-prod.yamlvalues.yaml里只放“所有環(huán)境都一樣的公共配置”比如鏡像倉庫前綴、對外端口。而每個環(huán)境只寫“跟別的環(huán)境不同”的那幾項比如副本數(shù)、數(shù)據(jù)庫地址、日志級別。這樣以后加一個環(huán)境只需要新增一個values文件改動范圍很小。還有一個細節(jié)我認為--set參數(shù)適合臨時調(diào)試不適合長期維護。因為--set寫在命令行里不留痕下次復制命令根本不知道上次設了哪些值。所以正式環(huán)境一定要用values文件并且把values文件納入Git管理。4.2 企業(yè)級Chart設計規(guī)范在多人協(xié)作的場景里Chart的結構和命名最好約定好否則沒過多久就會亂。我總結過幾條經(jīng)驗Chart目錄按業(yè)務域劃分比如apps/order-center/、apps/user-center/。Chart中的模板要盡量參數(shù)化凡是會變化的字段都放進values.yaml不要硬編碼在模板里。每個Chart都必須有README.md說明默認參數(shù)、端口、依賴、部署步驟。版本號遵守語義化Chart version: 0.1.0appVersion: 1.2.3升級應用時只改appVersion和version的patch位改動模板導致功能變化時升級version。依賴的外部資源數(shù)據(jù)庫、消息隊列不要直接內(nèi)嵌在業(yè)務Chart里最好單獨部署基礎設施Chart然后在業(yè)務Chart里通過values傳入地址。這里還要聊一下_helpers.tpl。這個文件用來生成通用模板片段比如資源名稱、標簽。我建議所有Chart都讓模板調(diào)用include來構造名字而不是直接硬寫。這樣統(tǒng)一改名時只要動一個地方就不會出現(xiàn)Deployment名叫AService名叫B這類低級錯誤。4.3 升級與回滾操作實戰(zhàn)升級一個Release到新版本最簡單的命令是helm upgrade my-app-prod ./charts/my-app -n prod -f values-prod.yaml升級時Helm會對比values.yaml和模板渲染結果然后對K8s資源做patch或create。你可以用--dry-run先模擬helm upgrade my-app-prod ./charts/my-app -n prod \ -f values-prod.yaml --dry-run --debug確定沒問題后去掉--dry-run執(zhí)行。假如你改了values里的鏡像tag那么Deployment期望的Pod模板變了k8s會自動滾動更新。但你改了ConfigMap內(nèi)容時Deployment本身沒變Pod不會自動重啟這會讓很多人迷惑。解決辦法很經(jīng)典在模板里給Deployment加一個基于ConfigMap內(nèi)容哈希的annotation比如annotations: checksum/config: {{ include (print $.Template.BasePath /configmap.yaml) . | sha256sum }}這樣只要ConfigMap內(nèi)容變了Deployment的annotation就變k8s就會滾動?;貪L操作也很直接helm history my-app-prod -n prod helm rollback my-app-prod 3 -n prod這里的3是REVISION序號不是Chart版本。回滾后Helm會生成一個新的REVISION第4個并盡可能恢復到第3版時的資源配置。要注意如果你在回滾后立即發(fā)現(xiàn)了問題還可以rollback回第4個版本所以別怕回滾它很安全。4.4 配合CI/CD的自動化發(fā)布流程企業(yè)里最爽的用法是把Helm集成進CI/CD流水線。我在某公司的流水線里大概是這樣工作的代碼提交觸發(fā)CI編譯并構建Docker鏡像推送到鏡像倉庫。CI生成一個帶Git commit hash的鏡像tag。更新對應環(huán)境的values文件里的image.tag提交到Git。CD階段調(diào)用helm upgrade把Chart和更新后的values一起安裝到集群。偽代碼差不多這樣# CD 階段 helm repo add corp http://charts.example.com --username $HELM_USER --password $HELM_PASS helm repo update helm upgrade --install my-app-prod corp/my-app \ --namespace prod \ -f values-prod.yaml \ --set image.tag${CI_COMMIT_SHA}這里用了--install參數(shù)意思是如果Release不存在就安裝存在就升級非常省事。如果用了GitOps那么你也可以把values文件和Chart倉庫都當成Git里的變量通過流水線自動執(zhí)行。但不管哪條路基本套路都一樣把鏡像版本作為主要變更輸入通過values文件或者--set傳給Helm。我們還做了發(fā)布權限管控CI使用的ServiceAccount只擁有部署相關命名空間的寫權限不能操作其他業(yè)務。這是Helm 3比Helm 2安全的地方不需要一個全局的Tiller所有權限都交給kube-apiserver來管。5. 常見問題與坑位實錄5.1 倉庫認證與Chart拉取失敗常見報錯是failed to fetch http://.../index.yaml : 401 Unauthorized。這種情況分兩種你repo add時用戶名密碼不對重新添加即可。認證信息被緩存了錯誤內(nèi)容特別是CI里環(huán)境變量沒傳對??梢杂胔elm repo remove再重新add或者檢查~/.config/helm/repositories.yaml里的配置。另一種情況是index.yaml里記錄的Chart包的URL指向了一個不存在的地址。如果你用Nginx方案最常見的是你生成index時用的--url參數(shù)寫錯了比如寫成了http://localhost:8080別人自然拉不到。解決辦法是重跑helm repo index用正確的對外訪問地址重新生成。5.2 升級后Pod不更新的詭異現(xiàn)象除了上面提到的ConfigMap變更不觸發(fā)滾動外還有一個容易踩的坑你在模板里用了semverCompare或者復雜條件導致某些字段沒有變化。用--dry-run能幫你看到最終渲染結果對比一下就能發(fā)現(xiàn)問題。我習慣用helm diff插件helm-diff來查看預期變更能在執(zhí)行升級前直觀列出哪些資源會被修改非常穩(wěn)。如果安裝的是別人寫的Chart里面模板用了random函數(shù)生成Secret會導致每次升級都生成一個新Secret而引用Secret的Pod卻因為引用值不變其實變了而不更新。這類問題只能靠排查模板邏輯或者指定helm.sh/resource-policy: keep等策略。5.3 Helm版本與K8s版本兼容性Helm 3本身要求K8s的API版本不能太老。比如老的k8s 1.14之前用Helm 3默認的apps/v1Deployment是沒問題的但如果你用了很老的集群一些內(nèi)置API可能不支持。我在某團隊的舊集群上就遇到過Ingress資源版本不兼容的問題需要修改Chart模板里的apiVersion。另外helm 2到helm 3遷移是一道大坑主要是模板變量和label的變化。如果你的團隊還在用Helm 2我還是建議盡早遷移Helm 2已經(jīng)非常老了而且安全性堪憂。5.4 Hook與資源清理的坑Helm支持pre-install、pre-upgrade、post-install等Hook用來執(zhí)行Job或者初始化動作。如果你定義了Hook要記得Hook完成的Pod在Release卸載時會被清理。但有時候我們想保留一些數(shù)據(jù)比如數(shù)據(jù)庫初始化結果那就不該用Hook而是用一個獨立Chart來管理初始化任務否則升級的時候Hook重跑可能把數(shù)據(jù)搞亂。還有一個坑如果Hook Job失敗了Helm會默認一直重試直到失敗次數(shù)達到backoffLimit然后才會標記Release失敗。這個地方可能導致一次發(fā)布卡死很久。我建議在Hook Job模板里顯式設置restartPolicy: Never和backoffLimit: 3避免死循環(huán)。結尾Helm這東西初期學起來簡單但真正在企業(yè)里用得順是需要一些沉淀的。我在實際使用過程中最深的體會是Helm的價值不在于“少寫幾個YAML”而在于它逼著你把應用部署這事兒做成一個標準化的交付物。把Chart倉庫建好、把values分層規(guī)范起來之后新項目上線的時間從原來的半天壓縮到了十幾分鐘而且再也不用擔心有人“不小心改錯了環(huán)境配置”。如果你還沒有接觸過Helm我建議先從一個小Chart跑通全流程再逐步推廣到核心應用。這個系列后面我還會繼續(xù)聊更細的實踐比如在Helm里管理數(shù)據(jù)庫遷移、多集群發(fā)布等咱們下次接著聊。