一、概述
1.1 背景介紹
生產(chǎn)環(huán)境中一個微服務(wù)體系動輒幾十個 Deployment、Service、ConfigMap、Secret、Ingress,如果全部用裸 YAML 手工維護(hù),版本迭代時改錯一個 label selector 就能導(dǎo)致滾動更新斷流。Helm 作為 Kubernetes 的包管理器,把一組關(guān)聯(lián)資源打包成 Chart,通過模板變量實(shí)現(xiàn)環(huán)境差異化渲染,通過 Release 管理實(shí)現(xiàn)一鍵部署、升級、回滾。從 Helm v3 開始去掉了集群端的 Tiller 組件,直接使用 kubeconfig 鑒權(quán),安全模型大幅簡化。v3.17+ 進(jìn)一步將 OCI Registry 作為原生存儲后端,Chart 的分發(fā)方式與容器鏡像完全對齊。
本文面向一線運(yùn)維和 SRE,以排障手冊的形式覆蓋 Helm Chart 從設(shè)計(jì)、開發(fā)、測試到部署上線的全鏈路,重點(diǎn)放在企業(yè)級場景下的 Chart 模板化設(shè)計(jì)模式、多環(huán)境分層覆蓋策略、OCI Registry 集成、Helmfile 編排以及常見部署故障的根因定位。
1.2 核心語義
| 概念 | 語義 | 排障關(guān)聯(lián) |
|---|---|---|
| Chart | 一組 Kubernetes 資源模板的打包單元 | Chart 結(jié)構(gòu)錯誤直接導(dǎo)致 helm install 失敗 |
| Release | Chart 的一次安裝實(shí)例,帶版本歷史 | Release 狀態(tài)異常是回滾和升級故障的入口 |
| Values | 模板渲染參數(shù),支持多層覆蓋 | Values 層級錯誤導(dǎo)致渲染結(jié)果不符合預(yù)期 |
| Repository / OCI Registry | Chart 的存儲和分發(fā)通道 | 認(rèn)證、網(wǎng)絡(luò)問題導(dǎo)致 chart pull 失敗 |
| Hook | Release 生命周期中的自定義動作 | Hook 執(zhí)行失敗會阻塞整個 install/upgrade 流程 |
1.3 適用場景
多環(huán)境(dev/staging/prod)同一套應(yīng)用需要差異化配置部署
微服務(wù)體系需要統(tǒng)一模板、批量發(fā)布、版本化管理
需要在 CI/CD 流水線中實(shí)現(xiàn)聲明式部署并支持自動回滾
跨團(tuán)隊(duì)共享基礎(chǔ)設(shè)施組件(中間件、監(jiān)控棧、日志采集器)的標(biāo)準(zhǔn)化交付
需要將內(nèi)部 Chart 推送到私有 OCI Registry 進(jìn)行版本管理
1.4 環(huán)境要求
| 組件 | 版本要求 | 說明 |
|---|---|---|
| Kubernetes | 1.32+ | 目標(biāo)集群版本,需與 Chart apiVersion 兼容 |
| Helm | v3.17+ | OCI registry 默認(rèn)支持,無需額外 feature gate |
| kubectl | 匹配集群版本 | helm 底層依賴 kubectl 的 kubeconfig |
| Docker Engine | 27.x | 用于構(gòu)建鏡像和本地 OCI registry 測試 |
| Container Registry | Harbor 2.x / ECR / ACR / GCR | OCI 兼容的 Chart 存儲后端 |
| Helmfile | v0.169+ | 多 Release 批量編排(可選) |
1.5 排障坐標(biāo)系
Helm 部署故障的排查可以沿兩個軸展開:
時間軸(Release 生命周期):
helm install → pre-install hooks → 資源創(chuàng)建 → post-install hooks → Ready 檢查 helm upgrade → pre-upgrade hooks → 資源更新 → post-upgrade hooks → Ready 檢查 helm rollback → pre-rollback hooks → 資源回退 → post-rollback hooks helm uninstall → pre-delete hooks → 資源刪除 → post-delete hooks
空間軸(問題域分層):
Layer 0: Chart 結(jié)構(gòu) / 語法錯誤 → helm lint / helm template 可發(fā)現(xiàn) Layer 1: Values 渲染結(jié)果異常 → helm template + diff 可發(fā)現(xiàn) Layer 2: Kubernetes API 拒絕 → kubectl apply --dry-run=server 級別 Layer 3: 資源運(yùn)行時異常 → Pod/Service/Ingress 層面的運(yùn)行時排查 Layer 4: Hook 執(zhí)行失敗 → Job/Pod 日志層面
二、詳細(xì)步驟
2.1 觀測面:Chart 結(jié)構(gòu)與 Release 狀態(tài)
2.1.1 Chart 目錄結(jié)構(gòu)
一個標(biāo)準(zhǔn)的 Helm Chart 目錄結(jié)構(gòu):
mychart/ ├── Chart.yaml # Chart 元數(shù)據(jù):名稱、版本、依賴 ├── Chart.lock # 依賴鎖定文件 ├── values.yaml # 默認(rèn)參數(shù) ├── values-dev.yaml # 開發(fā)環(huán)境覆蓋(可選,推薦放 Chart 外部) ├── values-prod.yaml # 生產(chǎn)環(huán)境覆蓋 ├── charts/ # 子 Chart(依賴) ├── crds/ # CRD 定義(install 時自動應(yīng)用) ├── templates/ # 模板文件 │ ├── _helpers.tpl # 命名模板(partial templates) │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── configmap.yaml │ ├── secret.yaml │ ├── hpa.yaml │ ├── serviceaccount.yaml │ ├── NOTES.txt # install 后的提示信息 │ └── tests/ │ └──test-connection.yaml └── .helmignore # 打包時忽略的文件
2.1.2 Chart.yaml 詳解
apiVersion:v2 # Helm v3 固定為 v2 name:myapp description:Aproduction-gradewebapplicationchart type:application # application 或 library version:1.4.2 # Chart 版本,遵循 SemVer appVersion:"3.8.1" # 應(yīng)用程序版本,僅展示用途 kubeVersion:">=1.28.0-0" # 兼容的 K8s 版本范圍 home:https://github.com/myorg/myapp maintainers: -name:sre-team email:sre@myorg.com dependencies: -name:postgresql version:"15.5.x" repository:"oci://registry.myorg.com/charts" condition:postgresql.enabled tags: -database -name:redis version:"19.x.x" repository:"oci://registry.myorg.com/charts" condition:redis.enabled tags: -cache -name:common version:"2.x.x" repository:"oci://registry.myorg.com/charts" tags: -infra
version和appVersion的區(qū)別經(jīng)常混淆:version是 Chart 自身的版本號,每次模板變更都應(yīng)遞增;appVersion是 Chart 部署的應(yīng)用版本,變更應(yīng)用鏡像 tag 時更新。CI/CD 中應(yīng)該自動同步這兩個版本號。
2.1.3 Release 狀態(tài)查看
# 列出所有 namespace 的 release helm list -A # 查看特定 release 的詳細(xì)信息 helm status myapp -n production # 查看 release 歷史版本 helmhistorymyapp -n production # 輸出示例 # REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION # 1 2026-02-10 1000 superseded myapp-1.2.0 3.6.0 Install complete # 2 2026-02-15 1400 superseded myapp-1.3.0 3.7.0 Upgrade complete # 3 2026-03-01 0900 deployed myapp-1.4.2 3.8.1 Upgrade complete
Release 的 STATUS 字段是排障第一入口:
| STATUS | 含義 | 下一步動作 |
|---|---|---|
| deployed | 當(dāng)前活躍版本 | 正常,無需處理 |
| superseded | 已被新版本取代 | 保留用于回滾參考 |
| failed | 安裝或升級失敗 | 查看 helm status 中的 NOTES 和 events |
| pending-install | 安裝中 | 檢查 hook Job 是否卡住 |
| pending-upgrade | 升級中 | 檢查 hook Job 和資源創(chuàng)建狀態(tài) |
| pending-rollback | 回滾中 | 檢查回滾目標(biāo)版本的資源兼容性 |
| uninstalling | 刪除中 | 檢查 finalizer 和 pre-delete hook |
2.2 第一輪判斷:模板渲染與語法檢查
2.2.1 helm lint — 靜態(tài)檢查
# 基礎(chǔ) lint helm lint ./mychart # 帶 values 文件的 lint helm lint ./mychart -f values-prod.yaml # 嚴(yán)格模式 helm lint ./mychart --strict # 輸出示例(有問題時) # ==> Linting ./mychart # [ERROR] Chart.yaml: version is required # [WARNING] templates/deployment.yaml: object name does not conform to Kubernetes naming requirements # [INFO] Chart.yaml: icon is recommended # # Error: 1 chart(s) linted, 1 chart(s) failed
helm lint能捕獲的問題:Chart.yaml 必填字段缺失、模板語法錯誤(括號未閉合、函數(shù)不存在)、資源名稱不符合 K8s 命名規(guī)范。它不能捕獲的問題:Values 值類型不匹配、運(yùn)行時資源沖突、鏡像不存在。
2.2.2 helm template — 本地渲染
# 完整渲染輸出 helm template myapp ./mychart -f values-prod.yaml -n production # 只渲染特定模板 helm template myapp ./mychart -s templates/deployment.yaml -f values-prod.yaml # 渲染并校驗(yàn) API 版本(連接集群) helm template myapp ./mychart -f values-prod.yaml --validate # 渲染結(jié)果重定向到文件,方便 diff helm template myapp ./mychart -f values-dev.yaml > rendered-dev.yaml helm template myapp ./mychart -f values-prod.yaml > rendered-prod.yaml diff rendered-dev.yaml rendered-prod.yaml
helm template是排查 Values 渲染問題的核心工具。在 CI/CD 中,建議每次 merge request 都執(zhí)行helm template并與主分支的渲染結(jié)果做 diff,任何意外的資源變更都應(yīng)觸發(fā)人工審核。
2.2.3 helm diff 插件
# 安裝 diff 插件 helm plugin install https://github.com/databus23/helm-diff # 查看升級將產(chǎn)生的差異(不實(shí)際執(zhí)行) helm diff upgrade myapp ./mychart -f values-prod.yaml -n production # 輸出示例 # production, myapp-deployment, Deployment (apps) has changed: # spec: # template: # spec: # containers: # - name: myapp # - image: registry.myorg.com/myapp:3.7.0 # + image: registry.myorg.com/myapp:3.8.1 # - resources: # - limits: # - memory: 512Mi # + resources: # + limits: # + memory: 1Gi
helm diff在執(zhí)行helm upgrade之前展示所有即將變更的資源,是生產(chǎn)環(huán)境變更審批的必備環(huán)節(jié)。
2.3 第二輪下鉆:Go 模板語法與 Values 設(shè)計(jì)
2.3.1 Go 模板語法核心
變量引用與內(nèi)置對象:
# templates/deployment.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:{{include"myapp.fullname".}}
labels:
{{-include"myapp.labels".|nindent4}}
annotations:
helm.sh/chart:{{include"myapp.chart".}}
app.kubernetes.io/managed-by:{{.Release.Service}}
spec:
{{-ifnot.Values.autoscaling.enabled}}
replicas:{{.Values.replicaCount}}
{{-end}}
selector:
matchLabels:
{{-include"myapp.selectorLabels".|nindent6}}
template:
metadata:
annotations:
checksum/config:{{include(print$.Template.BasePath"/configmap.yaml").|sha256sum}}
labels:
{{-include"myapp.selectorLabels".|nindent8}}
spec:
{{-with.Values.imagePullSecrets}}
imagePullSecrets:
{{-toYaml.|nindent8}}
{{-end}}
serviceAccountName:{{include"myapp.serviceAccountName".}}
containers:
-name:{{.Chart.Name}}
image:"{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy:{{.Values.image.pullPolicy}}
ports:
-name:http
containerPort:{{.Values.service.port}}
protocol:TCP
{{-if.Values.probes.liveness.enabled}}
livenessProbe:
httpGet:
path:{{.Values.probes.liveness.path}}
port:http
initialDelaySeconds:{{.Values.probes.liveness.initialDelaySeconds}}
periodSeconds:{{.Values.probes.liveness.periodSeconds}}
failureThreshold:{{.Values.probes.liveness.failureThreshold}}
{{-end}}
{{-if.Values.probes.readiness.enabled}}
readinessProbe:
httpGet:
path:{{.Values.probes.readiness.path}}
port:http
initialDelaySeconds:{{.Values.probes.readiness.initialDelaySeconds}}
periodSeconds:{{.Values.probes.readiness.periodSeconds}}
{{-end}}
resources:
{{-toYaml.Values.resources|nindent12}}
{{-if.Values.extraEnv}}
env:
{{-range.Values.extraEnv}}
-name:{{.name}}
value:{{.value|quote}}
{{-end}}
{{-end}}
volumeMounts:
-name:config
mountPath:/etc/myapp
readOnly:true
volumes:
-name:config
configMap:
name:{{include"myapp.fullname".}}-config
{{-with.Values.nodeSelector}}
nodeSelector:
{{-toYaml.|nindent8}}
{{-end}}
{{-with.Values.tolerations}}
tolerations:
{{-toYaml.|nindent8}}
{{-end}}
{{-with.Values.affinity}}
affinity:
{{-toYaml.|nindent8}}
{{-end}}
關(guān)鍵語法點(diǎn):
| 語法 | 作用 | 常見錯誤 |
|---|---|---|
| {{ .Values.xxx }} | 引用 values 值 | 路徑寫錯不報(bào)錯,渲染為空 |
| {{- ... -}} | 去除左/右空白 | 過度使用導(dǎo)致 YAML 縮進(jìn)錯誤 |
| {{ include "tpl" . }} | 調(diào)用命名模板 | 忘記傳遞.上下文 |
| {{ toYaml . | nindent N }} | 轉(zhuǎn) YAML 并縮進(jìn) N 格 | nindent 值錯誤導(dǎo)致 YAML 解析失敗 |
| {{ with .Values.xxx }} | 條件塊并切換上下文 | 塊內(nèi)無法訪問.Release等頂級對象 |
| {{ range .Values.list }} | 遍歷列表 | 塊內(nèi).指向當(dāng)前元素,需$.Values訪問頂級 |
| {{ default "val" .Values.xxx }} | 默認(rèn)值 | 空字符串不觸發(fā) default |
| {{ required "msg" .Values.xxx }} | 必填校驗(yàn) | 僅在渲染時觸發(fā),lint 不檢查 |
| {{ quote .Values.xxx }} | 加引號 | 數(shù)字類型意外變字符串 |
2.3.2 _helpers.tpl 命名模板
# templates/_helpers.tpl
{{/*
Chart完整名稱,帶releasename前綴
*/}}
{{-define"myapp.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標(biāo)識
*/}}
{{-define"myapp.chart"-}}
{{-printf"%s-%s".Chart.Name.Chart.Version|replace"+""_"|trunc63|trimSuffix"-"}}
{{-end}}
{{/*
通用標(biāo)簽
*/}}
{{-define"myapp.labels"-}}
helm.sh/chart:{{include"myapp.chart".}}
{{include"myapp.selectorLabels".}}
{{-if.Chart.AppVersion}}
app.kubernetes.io/version:{{.Chart.AppVersion|quote}}
{{-end}}
app.kubernetes.io/managed-by:{{.Release.Service}}
{{-end}}
{{/*
Selector標(biāo)簽—不可變,用于Deploymentselector
*/}}
{{-define"myapp.selectorLabels"-}}
app.kubernetes.io/name:{{include"myapp.name".}}
app.kubernetes.io/instance:{{.Release.Name}}
{{-end}}
{{/*
ServiceAccount名稱
*/}}
{{-define"myapp.serviceAccountName"-}}
{{-if.Values.serviceAccount.create}}
{{-default(include"myapp.fullname".).Values.serviceAccount.name}}
{{-else}}
{{-default"default".Values.serviceAccount.name}}
{{-end}}
{{-end}}
{{/*
名稱截?cái)?*/}}
{{-define"myapp.name"-}}
{{-default.Chart.Name.Values.nameOverride|trunc63|trimSuffix"-"}}
{{-end}}
63 字符截?cái)嗍?Kubernetes 對 label value 的硬限制,在命名模板中必須始終保留這個約束。
2.3.3 Values 分層覆蓋設(shè)計(jì)
Helm Values 的合并順序(后者覆蓋前者):
Chart 內(nèi) values.yaml(默認(rèn)值) ↓ 被覆蓋 父 Chart 的 values.yaml(子 Chart 依賴場景) ↓ 被覆蓋 -f / --values 指定的文件(可多個,后指定的優(yōu)先) ↓ 被覆蓋 --set/ --set-string / --set-json(命令行直接設(shè)置)
values.yaml 設(shè)計(jì)原則:
# values.yaml — 默認(rèn)值,適配 dev 環(huán)境
replicaCount:1
image:
repository:registry.myorg.com/myapp
pullPolicy:IfNotPresent
tag:""# 留空,默認(rèn)使用 Chart.AppVersion
imagePullSecrets:
-name:registry-credentials
serviceAccount:
create:true
name:""
annotations:{}
service:
type:ClusterIP
port:8080
ingress:
enabled:false
className:nginx
annotations:{}
hosts:
-host:myapp.dev.myorg.com
paths:
-path:/
pathType:Prefix
tls:[]
resources:
limits:
cpu:500m
memory:512Mi
requests:
cpu:100m
memory:128Mi
autoscaling:
enabled:false
minReplicas:2
maxReplicas:10
targetCPUUtilizationPercentage:70
targetMemoryUtilizationPercentage:80
probes:
liveness:
enabled:true
path:/healthz
initialDelaySeconds:10
periodSeconds:15
failureThreshold:3
readiness:
enabled:true
path:/readyz
initialDelaySeconds:5
periodSeconds:10
extraEnv:[]
nodeSelector:{}
tolerations:[]
affinity:{}
# 子 Chart 開關(guān)
postgresql:
enabled:true
auth:
database:myapp
username:myapp
primary:
persistence:
size:10Gi
redis:
enabled:false
values-prod.yaml — 生產(chǎn)環(huán)境覆蓋:
# values-prod.yaml
replicaCount:3
image:
tag:"3.8.1"
ingress:
enabled:true
className:nginx
annotations:
cert-manager.io/cluster-issuer:letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit:"100"
nginx.ingress.kubernetes.io/ssl-redirect:"true"
hosts:
-host:myapp.myorg.com
paths:
-path:/
pathType:Prefix
tls:
-secretName:myapp-tls
hosts:
-myapp.myorg.com
resources:
limits:
cpu:"2"
memory:2Gi
requests:
cpu:500m
memory:512Mi
autoscaling:
enabled:true
minReplicas:3
maxReplicas:20
targetCPUUtilizationPercentage:65
probes:
liveness:
initialDelaySeconds:30
failureThreshold:5
nodeSelector:
node-role:app
tolerations:
-key:dedicated
operator:Equal
value:app
effect:NoSchedule
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
-weight:100
podAffinityTerm:
labelSelector:
matchExpressions:
-key:app.kubernetes.io/name
operator:In
values:
-myapp
topologyKey:kubernetes.io/hostname
postgresql:
primary:
persistence:
size:100Gi
resources:
limits:
cpu:"4"
memory:8Gi
requests:
cpu:"1"
memory:2Gi
敏感值處理:
生產(chǎn)環(huán)境的 Secret 不應(yīng)寫在 values 文件中。推薦方案:
# 方案一:通過 --set 在 CI/CD 中注入
helm upgrade myapp ./mychart
-f values-prod.yaml
--setsecrets.dbPassword="${DB_PASSWORD}"
--setsecrets.apiKey="${API_KEY}"
-n production
# 方案二:External Secrets Operator(推薦)
# 在 Chart 模板中引用 ExternalSecret 資源,從 AWS Secrets Manager / Vault 同步
# templates/external-secret.yaml
{{-if.Values.externalSecrets.enabled}}
apiVersion:external-secrets.io/v1beta1
kind:ExternalSecret
metadata:
name:{{include"myapp.fullname".}}-secrets
labels:
{{-include"myapp.labels".|nindent4}}
spec:
refreshInterval:1h
secretStoreRef:
name:{{.Values.externalSecrets.storeRef}}
kind:ClusterSecretStore
target:
name:{{include"myapp.fullname".}}-secrets
data:
{{-range.Values.externalSecrets.keys}}
-secretKey:{{.secretKey}}
remoteRef:
key:{{.remoteKey}}
property:{{.property}}
{{-end}}
{{-end}}
2.4 根因矩陣:Helm 部署常見故障
| 故障現(xiàn)象 | 層級 | 可能原因 | 診斷命令 | 修復(fù)方向 |
|---|---|---|---|---|
| helm install 報(bào) YAML 解析錯誤 | L0 | 模板縮進(jìn)錯誤、nindent 值不對 | helm template 查看渲染結(jié)果 | 修正模板縮進(jìn) |
| helm install 報(bào) "cannot re-use a name" | L2 | 同名 Release 已存在(可能是 failed 狀態(tài)) | helm list -A --all | helm uninstall 舊 Release 或換名 |
| upgrade 報(bào) "has no deployed releases" | L2 | 首次 install 失敗,Release 停在 pending-install | helm list --pending -A | helm uninstall --no-hooks 清理后重新 install |
| upgrade 后 Pod 未更新 | L1 | image.tag 未變更,Deployment spec 無變化 | helm diff upgrade 檢查差異 | 在 annotation 中加 checksum/config 觸發(fā)滾動 |
| hook Job 卡在 Pending | L4 | 資源配額不足、nodeSelector 不匹配 | kubectl describe job | 調(diào)整 Job 資源或調(diào)度約束 |
| "lookup function not supported" | L0 | 使用lookup函數(shù)但在helm template中運(yùn)行 | 改用helm install --dry-run | lookup 只在連接集群時可用 |
| "chart requires kubeVersion >=1.28" | L0 | 集群版本低于 Chart 要求 | kubectl version | 升級集群或調(diào)整 Chart.yaml |
| OCI pull 401 Unauthorized | L2 | Registry 認(rèn)證失敗 | helm registry login 測試 | 檢查憑證配置 |
| values 中的 list 被 --set 覆蓋而非追加 | L1 | Helm --set 對 list 是替換行為 | helm template 對比渲染結(jié)果 | 使用 --set-json 或 -f 文件 |
| CRD 升級未生效 | L0 | Helm 不管理 CRD 的升級(只負(fù)責(zé)首次安裝) | kubectl get crd -o yaml | 手動 kubectl apply CRD |
2.5 處理與驗(yàn)證
2.5.1 Release 部署操作
# 首次安裝 helm install myapp ./mychart -f values-prod.yaml -n production --create-namespace --wait --timeout 10m # 升級(帶原子操作:失敗自動回滾) helm upgrade myapp ./mychart -f values-prod.yaml -n production --atomic --timeout 10m # 手動回滾到上一個版本 helm rollback myapp 0 -n production --wait # 回滾到指定版本 helm rollback myapp 2 -n production --wait # 卸載 helm uninstall myapp -n production --keep-history
關(guān)鍵參數(shù)說明:
--wait:等待所有 Pod 就緒后才標(biāo)記 Release 為 deployed,否則即使 Pod 還沒啟動完就返回成功
--atomic:如果升級失?。?-wait 超時、hook 失?。?,自動回滾到上一個成功版本
--timeout:與 --wait 配合,設(shè)置等待超時。默認(rèn) 5m,復(fù)雜應(yīng)用建議調(diào)到 10-15m
--keep-history:uninstall 時保留歷史,后續(xù)可以 rollback
2.5.2 部署后驗(yàn)證清單
# 1. 確認(rèn) Release 狀態(tài) helm status myapp -n production # 2. 確認(rèn)渲染結(jié)果符合預(yù)期 helm get values myapp -n production # 查看當(dāng)前生效的 values helm get manifest myapp -n production # 查看當(dāng)前生效的完整 manifest # 3. 確認(rèn) Pod 狀態(tài) kubectl get pods -n production -l app.kubernetes.io/instance=myapp # 4. 確認(rèn) Service 端點(diǎn) kubectl get endpoints -n production -l app.kubernetes.io/instance=myapp # 5. 確認(rèn) Ingress kubectl get ingress -n production -l app.kubernetes.io/instance=myapp # 6. 運(yùn)行 Chart 測試 helmtestmyapp -n production
2.5.3 Helm Hooks 詳解
# templates/hooks/db-migration.yaml
apiVersion:batch/v1
kind:Job
metadata:
name:{{include"myapp.fullname".}}-db-migrate
labels:
{{-include"myapp.labels".|nindent4}}
annotations:
"helm.sh/hook":pre-upgrade,pre-install
"helm.sh/hook-weight":"-5" # 權(quán)重越小越先執(zhí)行
"helm.sh/hook-delete-policy":before-hook-creation,hook-succeeded
spec:
backoffLimit:3
activeDeadlineSeconds:600
template:
metadata:
labels:
{{-include"myapp.selectorLabels".|nindent8}}
spec:
restartPolicy:Never
containers:
-name:migrate
image:"{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command:["./migrate","--direction","up"]
env:
-name:DATABASE_URL
valueFrom:
secretKeyRef:
name:{{include"myapp.fullname".}}-secrets
key:database-url
{{-with.Values.imagePullSecrets}}
imagePullSecrets:
{{-toYaml.|nindent8}}
{{-end}}
Hook 類型與執(zhí)行時機(jī):
| Hook | 觸發(fā)時機(jī) | 典型用途 |
|---|---|---|
| pre-install | install 前,資源創(chuàng)建前 | 數(shù)據(jù)庫初始化、前置檢查 |
| post-install | install 后,所有資源創(chuàng)建后 | 注冊服務(wù)、發(fā)送通知 |
| pre-upgrade | upgrade 前 | 數(shù)據(jù)庫遷移、數(shù)據(jù)備份 |
| post-upgrade | upgrade 后 | 緩存預(yù)熱、健康檢查 |
| pre-rollback | rollback 前 | 數(shù)據(jù)庫回退遷移 |
| post-rollback | rollback 后 | 狀態(tài)清理 |
| pre-delete | uninstall 前 | 資源清理、數(shù)據(jù)導(dǎo)出 |
| post-delete | uninstall 后 | 外部資源清理 |
| test | helm test 執(zhí)行時 | 連通性測試、功能驗(yàn)證 |
Hook 刪除策略:
| 策略 | 含義 |
|---|---|
| before-hook-creation | 執(zhí)行新 hook 前刪除舊的同名資源 |
| hook-succeeded | hook 成功后刪除 |
| hook-failed | hook 失敗后刪除 |
生產(chǎn)建議:始終設(shè)置before-hook-creation,否則第二次 upgrade 時會因?yàn)橥?Job 已存在而失敗。
三、示例代碼和配置
3.1 OCI Registry 操作
Helm v3.17+ 中 OCI 是默認(rèn)支持的 Chart 存儲格式,不再需要設(shè)置環(huán)境變量HELM_EXPERIMENTAL_OCI=1。
# 登錄 OCI Registry
helm registry login registry.myorg.com
--username admin
--password-stdin <<"${REGISTRY_PASSWORD}"
# 打包 Chart
helm package ./mychart
# 輸出: Successfully packaged chart and saved it to: /path/to/myapp-1.4.2.tgz
# 推送 Chart 到 OCI Registry
helm push myapp-1.4.2.tgz oci://registry.myorg.com/charts
# 從 OCI Registry 拉取 Chart
helm pull oci://registry.myorg.com/charts/myapp --version 1.4.2
# 查看遠(yuǎn)程 Chart 信息
helm show chart oci://registry.myorg.com/charts/myapp --version 1.4.2
helm show values oci://registry.myorg.com/charts/myapp --version 1.4.2
# 直接從 OCI Registry 安裝
helm install myapp oci://registry.myorg.com/charts/myapp
? --version 1.4.2
? -f values-prod.yaml
? -n production
3.2 完整 CI/CD 集成示例
# .gitlab-ci.yml — Helm Chart CI/CD 流水線
stages:
-lint
-package
-deploy-staging
-deploy-prod
variables:
CHART_DIR:"./charts/myapp"
REGISTRY:"registry.myorg.com"
CHART_REPO:"oci://${REGISTRY}/charts"
lint:
stage:lint
image:alpine/helm:3.17.0
script:
-helmlint${CHART_DIR}--strict
-helmtemplatetest-release${CHART_DIR}-f${CHART_DIR}/values.yaml>/dev/null
-helmtemplatetest-release${CHART_DIR}-fvalues-staging.yaml>/dev/null
-helmtemplatetest-release${CHART_DIR}-fvalues-prod.yaml>/dev/null
rules:
-changes:
-"charts/**/*"
package-and-push:
stage:package
image:alpine/helm:3.17.0
script:
-helmdependencyupdate${CHART_DIR}
-helmpackage${CHART_DIR}
-helmregistrylogin${REGISTRY}--username${REGISTRY_USER}--password${REGISTRY_PASSWORD}
-helmpushmyapp-*.tgz${CHART_REPO}
rules:
-if:'$CI_COMMIT_BRANCH == "main"'
changes:
-"charts/**/*"
deploy-staging:
stage:deploy-staging
image:alpine/helm:3.17.0
script:
-helmregistrylogin${REGISTRY}--username${REGISTRY_USER}--password${REGISTRY_PASSWORD}
-helmdiffupgrademyapp${CHART_REPO}/myapp
--version${CHART_VERSION}
-fvalues-staging.yaml
-nstaging||true
-helmupgrade--installmyapp${CHART_REPO}/myapp
--version${CHART_VERSION}
-fvalues-staging.yaml
-nstaging
--create-namespace
--atomic
--timeout10m
-helmtestmyapp-nstaging
environment:
name:staging
rules:
-if:'$CI_COMMIT_BRANCH == "main"'
deploy-prod:
stage:deploy-prod
image:alpine/helm:3.17.0
script:
-helmregistrylogin${REGISTRY}--username${REGISTRY_USER}--password${REGISTRY_PASSWORD}
-helmdiffupgrademyapp${CHART_REPO}/myapp
--version${CHART_VERSION}
-fvalues-prod.yaml
-nproduction
-helmupgrade--installmyapp${CHART_REPO}/myapp
--version${CHART_VERSION}
-fvalues-prod.yaml
-nproduction
--atomic
--timeout15m
--setsecrets.dbPassword="${DB_PASSWORD_PROD}"
-helmtestmyapp-nproduction
environment:
name:production
rules:
-if:'$CI_COMMIT_BRANCH == "main"'
when:manual
3.3 Helmfile 多環(huán)境批量編排
# helmfile.yaml
---
repositories:[]# OCI registry 不需要 repositories 聲明
environments:
dev:
values:
-environments/dev/defaults.yaml
staging:
values:
-environments/staging/defaults.yaml
production:
values:
-environments/production/defaults.yaml
secrets:
-environments/production/secrets.yaml# sops 加密
---
helmDefaults:
wait:true
timeout:600
atomic:true
createNamespace:true
releases:
-name:myapp
namespace:{{.Environment.Name}}
chart:oci://registry.myorg.com/charts/myapp
version:1.4.2
values:
-values-{{.Environment.Name}}.yaml
-image:
tag:{{requiredEnv"APP_VERSION"}}
hooks:
-events:["presync"]
showlogs:true
command:"kubectl"
args:
-"get"
-"ns"
-"{{ .Environment.Name }}"
-name:postgresql
namespace:{{.Environment.Name}}
chart:oci://registry.myorg.com/charts/postgresql
version:15.5.38
condition:postgresql.enabled
values:
-postgresql-values-{{.Environment.Name}}.yaml
-name:redis
namespace:{{.Environment.Name}}
chart:oci://registry.myorg.com/charts/redis
version:19.6.4
condition:redis.enabled
values:
-redis-values-{{.Environment.Name}}.yaml
-name:prometheus-stack
namespace:monitoring
chart:oci://registry.myorg.com/charts/kube-prometheus-stack
version:65.8.1
values:
-monitoring-values.yaml
# Helmfile 操作命令 # 查看 diff helmfile -e production diff # 部署到指定環(huán)境 helmfile -e production apply # 只部署特定 release helmfile -e production -l name=myapp apply # 銷毀環(huán)境 helmfile -e staging destroy
3.4 Library Chart 設(shè)計(jì)
Library Chart 不直接生成 Kubernetes 資源,而是提供可復(fù)用的模板片段給其他 Chart 引用。
# common-library/Chart.yaml apiVersion:v2 name:common-library version:2.1.0 type:library # 關(guān)鍵:聲明為 library 類型 description:Commontemplatesforallapplicationcharts
# common-library/templates/_deployment.tpl
{{-define"common.deployment"-}}
apiVersion:apps/v1
kind:Deployment
metadata:
name:{{include"common.fullname".}}
labels:
{{-include"common.labels".|nindent4}}
spec:
{{-ifnot.Values.autoscaling.enabled}}
replicas:{{.Values.replicaCount|default1}}
{{-end}}
strategy:
type:RollingUpdate
rollingUpdate:
maxSurge:25%
maxUnavailable:0
selector:
matchLabels:
{{-include"common.selectorLabels".|nindent6}}
template:
metadata:
annotations:
{{-if.Values.configHash}}
checksum/config:{{.Values.configHash}}
{{-end}}
labels:
{{-include"common.selectorLabels".|nindent8}}
spec:
serviceAccountName:{{include"common.serviceAccountName".}}
{{-with.Values.imagePullSecrets}}
imagePullSecrets:
{{-toYaml.|nindent8}}
{{-end}}
securityContext:
runAsNonRoot:true
seccompProfile:
type:RuntimeDefault
containers:
-name:{{.Chart.Name}}
image:"{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy:{{.Values.image.pullPolicy|default"IfNotPresent"}}
securityContext:
allowPrivilegeEscalation:false
capabilities:
drop:
-ALL
readOnlyRootFilesystem:true
{{-with.Values.ports}}
ports:
{{-toYaml.|nindent12}}
{{-end}}
{{-with.Values.resources}}
resources:
{{-toYaml.|nindent12}}
{{-end}}
{{-with.Values.nodeSelector}}
nodeSelector:
{{-toYaml.|nindent8}}
{{-end}}
{{-end-}}
在應(yīng)用 Chart 中引用 Library Chart:
# myapp/Chart.yaml dependencies: -name:common-library version:"2.x.x" repository:"oci://registry.myorg.com/charts"
# myapp/templates/deployment.yaml
{{-include"common.deployment".}}
這樣組織內(nèi)所有微服務(wù) Chart 只需維護(hù) values 差異,Deployment 的基礎(chǔ)結(jié)構(gòu)由 Library Chart 統(tǒng)一管理。
3.5 Chart 測試模板
# templates/tests/test-connection.yaml
apiVersion:v1
kind:Pod
metadata:
name:"{{ include "myapp.fullname" . }}-test-connection"
labels:
{{-include"myapp.labels".|nindent4}}
annotations:
"helm.sh/hook":test
"helm.sh/hook-delete-policy":before-hook-creation,hook-succeeded
spec:
restartPolicy:Never
containers:
-name:wget
image:busybox:1.37
command:['wget']
args:['{{include"myapp.fullname".}}:{{.Values.service.port}}/healthz','-q','-O-','-T','10']
-name:curl-api
image:curlimages/curl:8.10.1
command:['curl']
args:['-sf','--max-time','10','http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/api/v1/status']
# 運(yùn)行測試 helmtestmyapp -n production --timeout 5m # 查看測試 Pod 日志 kubectl logs myapp-test-connection -n production -c wget kubectl logs myapp-test-connection -n production -c curl-api
3.6 自動化診斷腳本
#!/usr/bin/env bash set-euo pipefail # 文件名:helm-release-diagnose.sh # 作用:對指定 Helm Release 進(jìn)行全面健康檢查,輸出診斷報(bào)告 # 適用場景:Release 部署后狀態(tài)異常、Pod 未就緒、Service 無端點(diǎn)時使用 # 使用方法:./helm-release-diagnose.sh# 輸入?yún)?shù):$1=Release名稱 $2=命名空間 # 輸出結(jié)果:終端輸出診斷報(bào)告,包含 Release 狀態(tài)、Pod 狀態(tài)、事件、日志摘要 # 風(fēng)險提示:只讀操作,不修改任何資源。需要 helm 和 kubectl 權(quán)限 RELEASE="${1:?Usage: $0 }" NAMESPACE="${2:?Usage: $0 }" echo"==========================================" echo"Helm Release 診斷報(bào)告" echo"Release:${RELEASE}" echo"Namespace:${NAMESPACE}" echo"Time:$(date -u +%Y-%m-%dT%H:%M:%SZ)" echo"==========================================" echo"" echo"--- 1. Release 狀態(tài) ---" helm status"${RELEASE}"-n"${NAMESPACE}"2>&1 ||echo"ERROR: Release 不存在或無法訪問" echo"" echo"--- 2. Release 歷史 ---" helmhistory"${RELEASE}"-n"${NAMESPACE}"--max 5 2>&1 ||echo"ERROR: 無法獲取歷史" echo"" echo"--- 3. 當(dāng)前生效 Values(非默認(rèn)值) ---" helm get values"${RELEASE}"-n"${NAMESPACE}"2>&1 ||echo"ERROR: 無法獲取 values" echo"" echo"--- 4. Release 關(guān)聯(lián)的 Pod ---" PODS=$(kubectl get pods -n"${NAMESPACE}"-l"app.kubernetes.io/instance=${RELEASE}"-o wide 2>&1) echo"${PODS}" # 檢查是否有異常 Pod echo"" echo"--- 5. 異常 Pod 詳情 ---" ABNORMAL_PODS=$(kubectl get pods -n"${NAMESPACE}"-l"app.kubernetes.io/instance=${RELEASE}" --field-selector='status.phase!=Running,status.phase!=Succeeded'-o name 2>/dev/null ||true) if[[ -z"${ABNORMAL_PODS}"]];then echo"所有 Pod 狀態(tài)正常" else forpodin${ABNORMAL_PODS};do echo"---${pod}---" kubectl describe"${pod}"-n"${NAMESPACE}"| tail -30 echo"" kubectl logs"${pod}"-n"${NAMESPACE}"--tail=20 2>/dev/null ||echo"無法獲取日志" echo"" done fi echo"" echo"--- 6. 近期事件(Warning) ---" kubectl get events -n"${NAMESPACE}" --field-selector="type=Warning" --sort-by='.lastTimestamp'2>&1 | tail -20 echo"" echo"--- 7. Service 端點(diǎn) ---" kubectl get endpoints -n"${NAMESPACE}"-l"app.kubernetes.io/instance=${RELEASE}"2>&1 echo"" echo"--- 8. Ingress 狀態(tài) ---" kubectl get ingress -n"${NAMESPACE}"-l"app.kubernetes.io/instance=${RELEASE}"2>&1 ||echo"無 Ingress" echo"" echo"==========================================" echo"診斷完成" echo"=========================================="
#!/usr/bin/env bash set-euo pipefail # 文件名:helm-chart-ci-validate.sh # 作用:在 CI 流水線中對 Chart 執(zhí)行完整的靜態(tài)驗(yàn)證 # 適用場景:MR/PR 提交時自動觸發(fā),防止有語法錯誤或渲染異常的 Chart 合入主分支 # 使用方法:./helm-chart-ci-validate.sh[values-file1] [values-file2] ... # 輸入?yún)?shù):$1=Chart目錄 $2+=可選的 values 文件列表 # 輸出結(jié)果:通過則退出碼 0,失敗則退出碼 1 并輸出錯誤詳情 # 風(fēng)險提示:只讀操作。需要 helm 3.17+ CHART_DIR="${1:?Usage: $0 [values-files...]}" shift VALUES_FILES=("$@") ERRORS=0 echo"=== Helm Chart CI Validation ===" echo"Chart:${CHART_DIR}" echo"Values files:${VALUES_FILES[*]:-none}" echo"" # Step 1: lint echo"--- Step 1: helm lint (strict) ---" if! helm lint"${CHART_DIR}"--strict;then echo"FAIL: helm lint failed" ((ERRORS++)) fi # Step 2: dependency check echo"" echo"--- Step 2: dependency update ---" if! helm dependency update"${CHART_DIR}";then echo"FAIL: dependency update failed" ((ERRORS++)) fi # Step 3: template render with default values echo"" echo"--- Step 3: template render (default values) ---" if! helm template ci-test"${CHART_DIR}"> /dev/null;then echo"FAIL: template render with default values failed" ((ERRORS++)) fi # Step 4: template render with each values file forvfin"${VALUES_FILES[@]}";do echo"" echo"--- Step 4: template render with${vf}---" if! helm template ci-test"${CHART_DIR}"-f"${vf}"> /dev/null;then echo"FAIL: template render with${vf}failed" ((ERRORS++)) fi done # Step 5: check for deprecated API versions echo"" echo"--- Step 5: deprecated API check ---" RENDERED=$(helm template ci-test"${CHART_DIR}"2>/dev/null ||true) DEPRECATED_APIS=("extensions/v1beta1""apps/v1beta1""apps/v1beta2""networking.k8s.io/v1beta1") forapiin"${DEPRECATED_APIS[@]}";do ifecho"${RENDERED}"| grep -q"apiVersion:${api}";then echo"WARN: Deprecated API found:${api}" fi done echo"" if[[${ERRORS}-gt 0 ]];then echo"RESULT: FAILED (${ERRORS}errors)" exit1 else echo"RESULT: PASSED" exit0 fi
#!/usr/bin/env bash set-euo pipefail # 文件名:helm-bulk-upgrade.sh # 作用:批量升級多個 Helm Release,支持 dry-run 和回滾 # 適用場景:基礎(chǔ)組件(如 common-library)升級后需要批量更新所有引用該 library 的應(yīng)用 Chart # 使用方法:./helm-bulk-upgrade.sh --env production --chart-version 1.4.2 [--dry-run] # 輸入?yún)?shù):--env=目標(biāo)環(huán)境 --chart-version=Chart版本 --dry-run=僅模擬不執(zhí)行 # 輸出結(jié)果:每個 Release 的升級結(jié)果日志 # 風(fēng)險提示:生產(chǎn)環(huán)境務(wù)必先 --dry-run。批量操作前確認(rèn)變更窗口。升級順序:基礎(chǔ)設(shè)施 → 中間件 → 應(yīng)用 ENV="" CHART_VERSION="" DRY_RUN=false while[[$#-gt 0 ]];do case$1in --env) ENV="$2";shift2 ;; --chart-version) CHART_VERSION="$2";shift2 ;; --dry-run) DRY_RUN=true;shift;; *)echo"Unknown option:$1";exit1 ;; esac done [[ -z"${ENV}"]] && {echo"Error: --env is required";exit1; } [[ -z"${CHART_VERSION}"]] && {echo"Error: --chart-version is required";exit1; } REGISTRY="oci://registry.myorg.com/charts" RELEASES=("myapp-api""myapp-web""myapp-worker""myapp-scheduler") NAMESPACE="${ENV}" echo"Bulk upgrade started" echo"Environment:${ENV}" echo"Chart version:${CHART_VERSION}" echo"Dry run:${DRY_RUN}" echo"" FAILED=() SUCCEEDED=() forreleasein"${RELEASES[@]}";do echo"--- Upgrading${release}---" VALUES_FILE="values/${release}-${ENV}.yaml" if[[ ! -f"${VALUES_FILE}"]];then echo"SKIP:${VALUES_FILE}not found" continue fi CMD="helm upgrade --install${release}${REGISTRY}/myapp --version${CHART_VERSION} -f${VALUES_FILE} -n${NAMESPACE} --atomic --timeout 10m" if[["${DRY_RUN}"=="true"]];then CMD="${CMD}--dry-run" fi ifeval"${CMD}";then SUCCEEDED+=("${release}") echo"OK:${release}upgraded successfully" else FAILED+=("${release}") echo"FAIL:${release}upgrade failed" if[["${DRY_RUN}"=="false"]];then echo"Note: --atomic flag should have triggered auto-rollback" fi fi echo"" done echo"==========================================" echo"Summary" echo"Succeeded:${SUCCEEDED[*]:-none}" echo"Failed:${FAILED[*]:-none}" echo"==========================================" [[${#FAILED[@]}-eq 0 ]] ||exit1
#!/usr/bin/env bash set-euo pipefail # 文件名:helm-values-diff.sh # 作用:對比兩個環(huán)境的 values 渲染差異,輸出人類可讀的 diff 報(bào)告 # 適用場景:新環(huán)境上線前確認(rèn)與已有環(huán)境的配置差異,防止遺漏關(guān)鍵配置 # 使用方法:./helm-values-diff.sh# 輸入?yún)?shù):$1=Chart目錄 $2=環(huán)境A的values文件 $3=環(huán)境B的values文件 # 輸出結(jié)果:兩個環(huán)境渲染后的資源 diff(類似 kubectl diff 格式) # 風(fēng)險提示:只讀操作。大型 Chart 渲染可能消耗較多內(nèi)存 CHART_DIR="${1:?Usage: $0 }" VALUES_A="${2:?Usage: $0 }" VALUES_B="${3:?Usage: $0 }" TMPDIR=$(mktemp -d) trap'rm -rf "${TMPDIR}"'EXIT echo"Rendering${VALUES_A}..." helm template compare"${CHART_DIR}"-f"${VALUES_A}">"${TMPDIR}/a.yaml" echo"Rendering${VALUES_B}..." helm template compare"${CHART_DIR}"-f"${VALUES_B}">"${TMPDIR}/b.yaml" echo"" echo"=== Diff:${VALUES_A}vs${VALUES_B}===" diff -u"${TMPDIR}/a.yaml""${TMPDIR}/b.yaml" --label"${VALUES_A}" --label"${VALUES_B}"||true echo"" echo"=== End of diff ==="
四、實(shí)際應(yīng)用案例
4.1 案例一:helm upgrade 后 Pod 未滾動更新
現(xiàn)場現(xiàn)象:執(zhí)行helm upgrade myapp ./mychart -f values-prod.yaml -n production返回成功,但 Pod 仍在運(yùn)行舊版本鏡像。helm history顯示新 Revision 為 deployed 狀態(tài)。
第一輪判斷:
# 查看當(dāng)前渲染的 manifest 中的鏡像
helm get manifest myapp -n production | grep"image:"
# 輸出:image: "registry.myorg.com/myapp:3.8.1" — 鏡像 tag 確實(shí)已更新
# 查看 Deployment 是否有變更
kubectl get deployment myapp -n production -o jsonpath='{.spec.template.spec.containers[0].image}'
# 輸出:registry.myorg.com/myapp:3.8.1 — Deployment spec 已更新
# 查看 Pod 的鏡像
kubectl get pods -n production -l app.kubernetes.io/instance=myapp -o jsonpath='{.items[*].spec.containers[0].image}'
# 輸出:registry.myorg.com/myapp:3.7.0 registry.myorg.com/myapp:3.7.0 — Pod 仍是舊版本
第二輪下鉆:
# 查看 Deployment 的 rollout 狀態(tài) kubectl rollout status deployment/myapp -n production # 輸出:Waiting for deployment "myapp" rollout to finish: 0 of 3 updated replicas are available... # 查看 ReplicaSet kubectl get rs -n production -l app.kubernetes.io/instance=myapp # NAME DESIRED CURRENT READY AGE # myapp-6d4f8b7c9 3 3 3 10d ← 舊 RS # myapp-7a5e9c3d1 3 3 0 2m ← 新 RS,Pod 未 Ready # 查看新 RS 的 Pod 為什么未 Ready kubectl describe pod myapp-7a5e9c3d1-xxxxx -n production # Events: # Warning Failed 2m kubelet Failed to pull image "registry.myorg.com/myapp:3.8.1": # rpc error: code = NotFound desc = failed to pull and unpack image: not found
關(guān)鍵證據(jù):鏡像3.8.1在 Registry 中不存在。CI/CD 流水線在鏡像構(gòu)建完成前就觸發(fā)了 Helm upgrade。
根因:流水線中 build 和 deploy stage 的依賴關(guān)系配置錯誤,deploy 未等待 build 完成。
修復(fù)動作:
先推送正確的鏡像到 Registry
新 Pod 會自動拉取成功并變?yōu)?Ready
修復(fù) CI/CD 流水線中 stage 之間的依賴關(guān)系
修復(fù)后驗(yàn)證:
kubectl rollout status deployment/myapp -n production --timeout=5m
# deployment "myapp" successfully rolled out
kubectl get pods -n production -l app.kubernetes.io/instance=myapp -o jsonpath='{.items[*].spec.containers[0].image}'
# registry.myorg.com/myapp:3.8.1 registry.myorg.com/myapp:3.8.1 registry.myorg.com/myapp:3.8.1
防再發(fā)建議:
CI/CD 中在 deploy 之前加鏡像存在性檢查:docker manifest inspect registry.myorg.com/myapp:3.8.1
Helm upgrade 使用--atomic,鏡像拉取失敗會自動回滾
4.2 案例二:pre-upgrade Hook Job 卡住導(dǎo)致 upgrade 超時
現(xiàn)場現(xiàn)象:helm upgrade myapp ./mychart -f values-prod.yaml -n production --timeout 10m --atomic執(zhí)行 10 分鐘后超時,Release 狀態(tài)變?yōu)閒ailed,然后自動回滾。但數(shù)據(jù)庫遷移 Job 一直卡在 Pending。
第一輪判斷:
helmhistorymyapp -n production # REVISION STATUS DESCRIPTION # 5 deployed Upgrade complete # 6 failed pre-upgrade hook "myapp-db-migrate" timed out kubectl getjobs-n production | grep migrate # myapp-db-migrate 0/1 10m 10m
第二輪下鉆:
kubectl describe job myapp-db-migrate -n production # Events: # Warning FailedCreate 10m job-controller Error creating: pods "myapp-db-migrate-xxxxx" # is forbidden: exceeded quota: compute-quota, requested: cpu=2, used: cpu=14, limited: cpu=16 kubectl get resourcequota -n production # NAME AGE REQUEST LIMIT # compute-quota 30d requests.cpu: 14/16, ... limits.cpu: 28/32, ...
關(guān)鍵證據(jù):數(shù)據(jù)庫遷移 Job 請求 2 CPU,但命名空間的 ResourceQuota 已經(jīng)用了 14/16,剩余 2 CPU 正好不夠(因?yàn)檫€有其他 Pending Pod 占用請求)。
根因:遷移 Job 的資源請求過高,且在 upgrade 期間新舊 Pod 同時存在導(dǎo)致 CPU 請求總量超限。
修復(fù)動作:
# 降低遷移 Job 的資源請求
# templates/hooks/db-migration.yaml
spec:
template:
spec:
containers:
-name:migrate
resources:
requests:
cpu:200m # 從 2 降到 200m
memory:256Mi
limits:
cpu:"1"
memory:512Mi
或者調(diào)整升級策略,確保新舊 Pod 不會同時大量并存:
# Deployment 使用 Recreate 策略或更保守的 RollingUpdate spec: strategy: type:RollingUpdate rollingUpdate: maxSurge:0 # 不額外創(chuàng)建新 Pod maxUnavailable:1 # 一次只替換一個
修復(fù)后驗(yàn)證:
helm upgrade myapp ./mychart -f values-prod.yaml -n production --atomic --timeout 10m # Release "myapp" has been upgraded. Happy Helming! kubectl getjobs-n production | grep migrate # myapp-db-migrate 1/1 45s 1m
防再發(fā)建議:
Hook Job 的資源請求應(yīng)與 ResourceQuota 預(yù)留空間匹配
在 CI 中加入 ResourceQuota 余量檢查
考慮將數(shù)據(jù)庫遷移從 Helm Hook 移到獨(dú)立的 Job,在 upgrade 之前手動執(zhí)行
4.3 案例三:Helmfile 多 Release 部署順序?qū)е乱蕾囄淳途w
現(xiàn)場現(xiàn)象:使用 Helmfile 部署三個組件(postgresql、redis、myapp),myapp 部署后 Pod 反復(fù) CrashLoopBackOff,日志顯示 "connection refused" 無法連接 PostgreSQL。
第一輪判斷:
helmfile -e production status # NAME NAMESPACE REVISION STATUS # postgresql production 1 deployed # redis production 1 deployed # myapp production 1 deployed kubectl get pods -n production # postgresql-0 1/1 Running 0 2m # redis-master-0 1/1 Running 0 2m # myapp-xxx 0/1 CrashLoopBackOff 3 2m kubectl logs myapp-xxx -n production # level=fatal msg="failed to connect to database" error="dial tcp 10.96.45.123 connection refused"
第二輪下鉆:
# PostgreSQL Pod 雖然 Running,但實(shí)際還在初始化 kubectl logs postgresql-0 -n production | tail -5 # 2026-03-01 0930 UTC [1] LOG: database system is ready to accept connections # 上面這行出現(xiàn)在 myapp 啟動之后 # 查看時間線 kubectl get events -n production --sort-by='.firstTimestamp'| grep -E"(postgresql|myapp)" # 0900 postgresql-0 Created # 0905 myapp-xxx Created ← myapp 在 postgresql 初始化完成前就啟動了 # 0920 myapp-xxx BackOff # 0930 postgresql-0 Ready
關(guān)鍵證據(jù):Helmfile 默認(rèn)并發(fā)安裝所有 Release,myapp 在 PostgreSQL 完成初始化之前就開始連接數(shù)據(jù)庫。
根因:Helmfile 缺少 Release 之間的依賴和順序聲明。
修復(fù)動作:
# helmfile.yaml — 添加 needs 聲明 releases: -name:postgresql namespace:production chart:oci://registry.myorg.com/charts/postgresql version:15.5.38 values: -postgresql-values-production.yaml -name:redis namespace:production chart:oci://registry.myorg.com/charts/redis version:19.6.4 values: -redis-values-production.yaml -name:myapp namespace:production chart:oci://registry.myorg.com/charts/myapp version:1.4.2 needs: # 聲明依賴 -production/postgresql -production/redis values: -values-production.yaml
同時在 myapp 的 Chart 中增加 initContainer 做連接等待:
# values-production.yaml initContainers: -name:wait-for-db image:busybox:1.37 command:['sh','-c','until nc -z postgresql 5432; do echo waiting for db; sleep 2; done']
修復(fù)后驗(yàn)證:
helmfile -e production apply # postgresql deployed first, then redis, then myapp kubectl get pods -n production # postgresql-0 1/1 Running 0 3m # redis-master-0 1/1 Running 0 2m # myapp-xxx 1/1 Running 0 1m
防再發(fā)建議:
始終在 Helmfile 中用needs聲明 Release 之間的啟動依賴
應(yīng)用端使用 initContainer 或連接重試機(jī)制,不依賴部署順序保證可用性
設(shè)置合理的 readinessProbe,讓 K8s 在應(yīng)用真正就緒后才接入流量
4.4 案例四:OCI Registry 推送 Chart 時 401 Unauthorized
現(xiàn)場現(xiàn)象:CI/CD 流水線中執(zhí)行helm push myapp-1.4.2.tgz oci://registry.myorg.com/charts報(bào)錯Error: unexpected status from HEAD request to https://registry.myorg.com/v2/charts/myapp/blobs/sha256 401 Unauthorized。
第一輪判斷:
# 測試 registry 登錄
helm registry login registry.myorg.com --username ci-bot --password"${REGISTRY_PASSWORD}"
# Login Succeeded — 登錄沒問題
# 手動推送
helm push myapp-1.4.2.tgz oci://registry.myorg.com/charts
# Error: unexpected status from HEAD request... 401 Unauthorized
第二輪下鉆:
# 檢查 OCI 兼容性
curl -u"ci-bot:${REGISTRY_PASSWORD}"https://registry.myorg.com/v2/
# {"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}
# 檢查是否需要 Bearer token
curl -v https://registry.myorg.com/v2/ 2>&1 | grep -i www-authenticate
# Www-Authenticate: Bearer realm="https://auth.myorg.com/service/token",service="registry.myorg.com"
# 檢查 helm 的認(rèn)證配置
cat ~/.config/helm/registry/config.json
# 發(fā)現(xiàn) auth 字段存在但對應(yīng)的 credsStore 配置指向了 docker-credential-desktop
關(guān)鍵證據(jù):Helm 的 registry 認(rèn)證走的是 Docker credential store,在 CI 環(huán)境中docker-credential-desktop不存在,導(dǎo)致雖然helm registry login顯示成功(寫入了 config.json),但實(shí)際推送時 credential helper 找不到憑證。
根因:CI 環(huán)境缺少 Docker credential helper,Helm 的 registry 認(rèn)證依賴 Docker 的憑證存儲體系。
修復(fù)動作:
# 方案一:在 CI 中禁用 credential helper,使用純文件認(rèn)證
exportDOCKER_CONFIG="${HOME}/.docker-helm"
mkdir -p"${DOCKER_CONFIG}"
# 直接寫入認(rèn)證信息
echo'{"auths":{"registry.myorg.com":{"auth":"'$(echo-n"ci-bot:${REGISTRY_PASSWORD}"| base64)'"}}}'
>"${DOCKER_CONFIG}/config.json"
# 重新執(zhí)行 helm push
helm push myapp-1.4.2.tgz oci://registry.myorg.com/charts
# Pushed: registry.myorg.com/charts/myapp:1.4.2
# 方案二:使用 HELM_REGISTRY_CONFIG 環(huán)境變量
exportHELM_REGISTRY_CONFIG="/tmp/helm-registry-config.json"
helm registry login registry.myorg.com --username ci-bot --password"${REGISTRY_PASSWORD}"
helm push myapp-1.4.2.tgz oci://registry.myorg.com/charts
修復(fù)后驗(yàn)證:
# 確認(rèn)推送成功 helm pull oci://registry.myorg.com/charts/myapp --version 1.4.2 # Pulled: registry.myorg.com/charts/myapp:1.4.2 helm show chart oci://registry.myorg.com/charts/myapp --version 1.4.2 # apiVersion: v2 # name: myapp # version: 1.4.2
防再發(fā)建議:
CI 環(huán)境中統(tǒng)一使用HELM_REGISTRY_CONFIG或DOCKER_CONFIG環(huán)境變量指定認(rèn)證配置文件
不依賴系統(tǒng)級的 credential helper
在流水線開始時加helm registry login的連通性驗(yàn)證步驟
五、最佳實(shí)踐和注意事項(xiàng)
5.1 Chart 設(shè)計(jì)最佳實(shí)踐
5.1.1 版本管理
Chart version 遵循 SemVer:Breaking change 升主版本,新功能升次版本,Bug 修復(fù)升補(bǔ)丁版本
appVersion 與應(yīng)用鏡像 tag 保持同步
使用 Git tag 觸發(fā) Chart 打包和推送
Chart.lock 提交到版本庫,保證依賴版本可復(fù)現(xiàn)
5.1.2 Values 設(shè)計(jì)規(guī)范
所有可配置項(xiàng)都應(yīng)有默認(rèn)值,helm install裸裝必須能跑起來(至少在 dev 環(huán)境)
使用required函數(shù)標(biāo)記生產(chǎn)環(huán)境必填項(xiàng)
敏感值不寫入 values 文件,使用 External Secrets Operator 或 --set 注入
布爾開關(guān)命名統(tǒng)一:xxx.enabled
資源配置使用嵌套結(jié)構(gòu):resources.requests.cpu,不要拍平成requestsCpu
# values.yaml 中用 required 標(biāo)記必填項(xiàng)的模板用法
# templates/deployment.yaml
env:
-name:DATABASE_URL
value:{{required"database.url is required for production".Values.database.url}}
5.1.3 模板組織
公共邏輯提取到_helpers.tpl
每個 Kubernetes 資源一個模板文件,命名與資源類型對應(yīng)
復(fù)雜的條件渲染用命名模板封裝,而不是在資源模板中嵌套大量 if-else
使用{{- include ... | nindent N }}而非{{ template ... }}(template 不能管道傳遞)
5.1.4 Umbrella Chart 架構(gòu)
platform-chart/ ├── Chart.yaml # type: application ├── values.yaml # 全局默認(rèn)值 ├── charts/ │ ├── api/ # 子 Chart: API 服務(wù) │ ├── web/ # 子 Chart: Web 前端 │ ├── worker/ # 子 Chart: 后臺任務(wù) │ └── common/ # Library Chart └── templates/ ├── _helpers.tpl └── shared-configmap.yaml # 跨子 Chart 共享的資源
Umbrella Chart 的 values.yaml 中通過子 Chart 名稱作為 key 傳遞參數(shù):
# platform-chart/values.yaml global: imageRegistry:registry.myorg.com environment:production api: replicaCount:3 image: repository:registry.myorg.com/myapp-api tag:"2.1.0" web: replicaCount:2 image: repository:registry.myorg.com/myapp-web tag:"1.8.0" worker: replicaCount:5 image: repository:registry.myorg.com/myapp-worker tag:"2.1.0"
5.2 安全加固
5.2.1 RBAC 最小權(quán)限
# 為 Helm 操作創(chuàng)建專用 ServiceAccount(CI/CD 場景) apiVersion:v1 kind:ServiceAccount metadata: name:helm-deployer namespace:kube-system --- apiVersion:rbac.authorization.k8s.io/v1 kind:ClusterRole metadata: name:helm-deployer rules: -apiGroups:["","apps","batch","networking.k8s.io","autoscaling"] resources:["*"] verbs:["get","list","watch","create","update","patch","delete"] -apiGroups:[""] resources:["secrets"] verbs:["get","list","watch","create","update","patch","delete"] # Helm 存儲 Release 信息到 Secret --- apiVersion:rbac.authorization.k8s.io/v1 kind:ClusterRoleBinding metadata: name:helm-deployer roleRef: apiGroup:rbac.authorization.k8s.io kind:ClusterRole name:helm-deployer subjects: -kind:ServiceAccount name:helm-deployer namespace:kube-system
5.2.2 Chart 安全掃描
# 使用 kubeaudit 掃描渲染后的 manifest helm template myapp ./mychart -f values-prod.yaml | kubeaudit all -f - # 使用 checkov 做合規(guī)掃描 helm template myapp ./mychart -f values-prod.yaml > rendered.yaml checkov -f rendered.yaml --framework kubernetes # 使用 trivy 掃描 Chart 中的配置問題 trivy config ./mychart
5.3 注意事項(xiàng)
5.3.1 CRD 管理陷阱
Helm 對 CRD 的處理有特殊限制:
CRD 放在crds/目錄下,僅在首次helm install時安裝
helm upgrade不會更新 CRD
helm uninstall不會刪除 CRD
因此 CRD 的生命周期管理需要額外處理:
# 手動更新 CRD kubectl apply -f mychart/crds/ # 或者將 CRD 從 crds/ 移到 templates/ 中,但需要加注解防止意外刪除 # templates/crd.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: myresources.myorg.com annotations: "helm.sh/resource-policy": keep # uninstall 時保留
5.3.2 常見配置錯誤
| 錯誤模式 | 表現(xiàn) | 修正方法 |
|---|---|---|
| nindent 值錯誤 | YAML 渲染后縮進(jìn)不對,K8s API 拒絕 | 用helm template檢查輸出格式 |
| with 塊內(nèi)訪問頂級對象 | {{ .Values.xxx }} 變成空 | 使用{{ $.Values.xxx }} |
| range 塊內(nèi)的. | . 指向當(dāng)前迭代元素而非根 | 使用$引用根上下文 |
| 數(shù)字值未加 quote | YAML 解析為科學(xué)計(jì)數(shù)法 | 用{{ .Values.xxx | quote }} |
| list 值用 --set 覆蓋 | 整個 list 被替換而非追加 | 使用--set-json或 -f 文件 |
| Release 歷史膨脹 | etcd 存儲壓力增大 | 設(shè)置--history-max 10 |
5.3.3 Release 存儲與 etcd 壓力
Helm v3 將 Release 信息存儲在 Kubernetes Secret 中(Base64 編碼 + gzip 壓縮)。每次 upgrade/rollback 都會創(chuàng)建新的 Secret。對于頻繁發(fā)布的應(yīng)用,需要限制歷史版本數(shù):
# 全局設(shè)置最大歷史版本 helm upgrade myapp ./mychart -f values-prod.yaml -n production --history-max 10 # 清理舊 Release Secret kubectl get secrets -n production -l owner=helm -l name=myapp --sort-by=.metadata.creationTimestamp
六、故障排查和監(jiān)控
6.1 故障排查決策樹
helm install/upgrade 失敗 ├── 報(bào)錯"YAML parse error"/"template render error" │ ├── 檢查 helm template 輸出 │ ├── 定位到具體模板文件和行號 │ └── 常見:nindent 錯誤、括號未閉合、函數(shù)名拼寫 ├── 報(bào)錯"cannot re-use a name"/"has no deployed releases" │ ├── helm list --all -n查看現(xiàn)有 Release │ ├── 如果是 failed 狀態(tài):helm uninstall --no-hooks │ └── 如果是 pending-* 狀態(tài):等待或強(qiáng)制清理 ├── 報(bào)錯"timed out waiting for the condition" │ ├── 檢查 Pod 狀態(tài):kubectl get pods │ ├── Hook Job 卡?。簁ubectl describe job │ ├── Pod 未 Ready:kubectl describe pod / kubectl logs │ └── 資源配額不足:kubectl get resourcequota ├── 報(bào)錯"admission webhook denied" │ ├── 檢查 ValidatingWebhookConfiguration │ ├── 查看 webhook 服務(wù)是否可用 │ └── 臨時繞過:刪除 webhook 或加 exclude annotation ├── 部署成功但功能異常 │ ├── helm get values 確認(rèn) values 生效 │ ├── helm get manifest 確認(rèn)渲染結(jié)果 │ ├── kubectl describe / logs 查看運(yùn)行時狀態(tài) │ └── Service/Endpoints/Ingress 鏈路檢查 └── OCI Registry 相關(guān) ├── 401 Unauthorized:檢查 helm registry login ├── not found:確認(rèn) Chart 名稱和版本號 └── timeout:檢查網(wǎng)絡(luò)和 DNS
6.2 Helm 操作監(jiān)控
6.2.1 Release 狀態(tài)監(jiān)控
# Prometheus 告警規(guī)則:檢測 Helm Release 異常狀態(tài)
# 需要 helm-exporter(https://github.com/sstarber/helm-exporter)
groups:
-name:helm-release-alerts
rules:
-alert:HelmReleaseFailed
expr:helm_chart_info{status="failed"}==1
for:5m
labels:
severity:critical
annotations:
summary:"Helm Release{{ $labels.release }}處于 failed 狀態(tài)"
description:"Release{{ $labels.release }}in namespace{{ $labels.namespace }}has been in failed state for 5 minutes. Chart:{{ $labels.chart }}"
-alert:HelmReleasePending
expr:helm_chart_info{status=~"pending-.*"}==1
for:15m
labels:
severity:warning
annotations:
summary:"Helm Release{{ $labels.release }}處于 pending 狀態(tài)"
description:"Release{{ $labels.release }}in namespace{{ $labels.namespace }}has been pending for 15 minutes."
6.2.2 CI/CD 部署指標(biāo)
在 CI/CD 流水線中記錄 Helm 部署的關(guān)鍵指標(biāo):
# 部署耗時統(tǒng)計(jì) START_TIME=$(date +%s) helm upgrade --install myapp ./mychart -f values-prod.yaml -n production --atomic --timeout 10m END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) # 推送指標(biāo)到 Prometheus Pushgateway cat <
6.3 日志排查路徑
# Helm 自身調(diào)試日志 helm upgrade myapp ./mychart -f values-prod.yaml -n production --debug 2>&1 | tee helm-debug.log # 從 debug 日志中提取關(guān)鍵信息 grep -E"(error|Error|FAIL|WARNING)"helm-debug.log # 查看 Helm 發(fā)送給 K8s API 的請求 helm upgrade myapp ./mychart -f values-prod.yaml -n production --debug --dry-run 2>&1 | head -100
6.4 常見問題速查
問題 快速診斷 快速修復(fù) "release: already exists" helm list --all -n helm uninstall -n "UPGRADE FAILED: has no deployed releases" helm list --pending -A helm uninstall --no-hooks -n 后重新 install Secret 超過 1MB(etcd 限制) helm get manifest | wc -c 拆分 Chart 或減少嵌入數(shù)據(jù) lookup 在 CI 中失敗 helm template 模式不支持 lookup 改用--dry-run=server或移除 lookup 子 Chart values 未生效 helm get values 檢查層級 確認(rèn) values 中用子 Chart 名稱作為 key 七、總結(jié)
7.1 技術(shù)要點(diǎn)回顧
Helm v3.17+ 去除 Tiller,直接使用 kubeconfig 鑒權(quán),OCI Registry 作為原生 Chart 存儲后端
Chart 結(jié)構(gòu)的核心三要素:Chart.yaml(元數(shù)據(jù))、values.yaml(參數(shù)化)、templates/(模板渲染)
Go 模板語法中最常見的坑:with塊上下文切換、range塊中.的含義變化、nindent縮進(jìn)值
Values 分層覆蓋遵循「默認(rèn)值 → 父 Chart → -f 文件 → --set」的優(yōu)先級鏈
Hook 的before-hook-creation刪除策略是防止 Job 沖突的關(guān)鍵配置
--atomic參數(shù)保證升級失敗時自動回滾,是生產(chǎn)環(huán)境部署的必選項(xiàng)
Library Chart + Umbrella Chart 是企業(yè)級多微服務(wù) Chart 管理的標(biāo)準(zhǔn)架構(gòu)模式
Release 歷史存儲在 etcd 中,必須通過--history-max限制版本數(shù)量
7.2 排障鏈路總結(jié)
完整的 Helm 部署排障鏈路:
helm lint → helm template → helm diff → helm upgrade --dry-run → helm upgrade(實(shí)際執(zhí)行) → 失敗時:helm status → helmhistory→ kubectl describe/logs → 定位到具體層級(Chart 語法 / Values 渲染 / K8s API / 運(yùn)行時 / Hook) → 修復(fù)后:helm upgrade --atomic → helmtest→ 驗(yàn)證
7.3 進(jìn)階學(xué)習(xí)方向
Helm SDK 集成:在 Go 程序中直接調(diào)用 Helm SDK 實(shí)現(xiàn)自定義部署控制器
Chart Testing(ct)工具:在 CI 中自動檢測 Chart 變更并運(yùn)行集成測試
ArgoCD + Helm:GitOps 模式下的 Helm Chart 自動同步和漂移檢測
Sigstore 簽名驗(yàn)證:對 Chart 進(jìn)行數(shù)字簽名,在部署前驗(yàn)證完整性
7.3 參考資料
Helm 官方文檔— Chart 開發(fā)、最佳實(shí)踐、命令參考
Helm GitHub— 源碼、Issue 跟蹤
Artifact Hub— 公共 Chart 搜索
Helmfile 文檔— 多 Release 編排
附錄
A. Helm 命令速查表
# Chart 開發(fā) helm create mychart # 創(chuàng)建 Chart 腳手架 helm lint ./mychart --strict # 靜態(tài)檢查 helm template myapp ./mychart -f values.yaml # 本地渲染 helm package ./mychart # 打包為 .tgz helm dependency update ./mychart # 更新依賴 helm dependency list ./mychart # 查看依賴狀態(tài) helm show chart ./mychart # 查看 Chart.yaml helm show values ./mychart # 查看默認(rèn) values # OCI Registry helm registry login# 登錄 helm push oci:// / # 推送 helm pull oci:// / --version X # 拉取 helm show chart oci:// / # 查看遠(yuǎn)程 Chart # Release 管理 helm install -f -n # 安裝 helm upgrade --install -f -n # 升級(不存在則安裝) helm rollback -n # 回滾 helm uninstall -n # 卸載 helm list -A # 列出所有 Release helm status -n # 查看狀態(tài) helmhistory -n # 查看歷史 # 調(diào)試 helm upgrade --dry-run --debug # 模擬升級 helm get values -n # 查看當(dāng)前 values helm get manifest -n # 查看當(dāng)前 manifest helm get hooks -n # 查看 hooks helmtest -n # 運(yùn)行測試
-
容器
+關(guān)注
關(guān)注
0文章
533瀏覽量
22981 -
kubernetes
+關(guān)注
關(guān)注
0文章
266瀏覽量
9500
原文標(biāo)題:Helm包管理實(shí)戰(zhàn):企業(yè)級應(yīng)用模板化部署
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Kubernetes Helm入門指南
企業(yè)級應(yīng)用模板化部署與Helm包管理實(shí)戰(zhàn)
評論