0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

企业级应用模板化部署与Helm包管理实战

马哥Linux运维 来源:马哥Linux运维 2026-03-17 14:32 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

一、概述

1.1 背景介绍

生产环境中一个微服务体系动辄几十个 Deployment、Service、ConfigMap、Secret、Ingress,如果全部用裸 YAML 手工维护,版本迭代时改错一个 label selector 就能导致滚动更新断流。Helm 作为 Kubernetes 的包管理器,把一组关联资源打包成 Chart,通过模板变量实现环境差异化渲染,通过 Release 管理实现一键部署、升级、回滚。从 Helm v3 开始去掉了集群端的 Tiller 组件,直接使用 kubeconfig 鉴权,安全模型大幅简化。v3.17+ 进一步将 OCI Registry 作为原生存储后端,Chart 的分发方式与容器镜像完全对齐。

本文面向一线运维和 SRE,以排障手册的形式覆盖 Helm Chart 从设计、开发、测试到部署上线的全链路,重点放在企业级场景下的 Chart 模板化设计模式、多环境分层覆盖策略、OCI Registry 集成、Helmfile 编排以及常见部署故障的根因定位。

1.2 核心语义

概念 语义 排障关联
Chart 一组 Kubernetes 资源模板的打包单元 Chart 结构错误直接导致 helm install 失败
Release Chart 的一次安装实例,带版本历史 Release 状态异常是回滚和升级故障的入口
Values 模板渲染参数,支持多层覆盖 Values 层级错误导致渲染结果不符合预期
Repository / OCI Registry Chart 的存储和分发通道 认证、网络问题导致 chart pull 失败
Hook Release 生命周期中的自定义动作 Hook 执行失败会阻塞整个 install/upgrade 流程

1.3 适用场景

多环境(dev/staging/prod)同一套应用需要差异化配置部署

微服务体系需要统一模板、批量发布、版本化管理

需要在 CI/CD 流水线中实现声明式部署并支持自动回滚

跨团队共享基础设施组件(中间件、监控栈、日志采集器)的标准化交付

需要将内部 Chart 推送到私有 OCI Registry 进行版本管理

1.4 环境要求

组件 版本要求 说明
Kubernetes 1.32+ 目标集群版本,需与 Chart apiVersion 兼容
Helm v3.17+ OCI registry 默认支持,无需额外 feature gate
kubectl 匹配集群版本 helm 底层依赖 kubectl 的 kubeconfig
Docker Engine 27.x 用于构建镜像和本地 OCI registry 测试
Container Registry Harbor 2.x / ECR / ACR / GCR OCI 兼容的 Chart 存储后端
Helmfile v0.169+ 多 Release 批量编排(可选)

1.5 排障坐标系

Helm 部署故障的排查可以沿两个轴展开:

时间轴(Release 生命周期):

helm install → pre-install hooks → 资源创建 → 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 结构 / 语法错误   → helm lint / helm template 可发现
Layer 1: Values 渲染结果异常    → helm template + diff 可发现
Layer 2: Kubernetes API 拒绝    → kubectl apply --dry-run=server 级别
Layer 3: 资源运行时异常      → Pod/Service/Ingress 层面的运行时排查
Layer 4: Hook 执行失败       → Job/Pod 日志层面

二、详细步骤

2.1 观测面:Chart 结构与 Release 状态

2.1.1 Chart 目录结构

一个标准的 Helm Chart 目录结构:

mychart/
├── Chart.yaml       # Chart 元数据:名称、版本、依赖
├── Chart.lock       # 依赖锁定文件
├── values.yaml      # 默认参数
├── values-dev.yaml    # 开发环境覆盖(可选,推荐放 Chart 外部)
├── values-prod.yaml    # 生产环境覆盖
├── charts/        # 子 Chart(依赖)
├── crds/         # CRD 定义(install 时自动应用)
├── 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"       # 应用程序版本,仅展示用途
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的区别经常混淆:version是 Chart 自身的版本号,每次模板变更都应递增;appVersion是 Chart 部署的应用版本,变更应用镜像 tag 时更新。CI/CD 中应该自动同步这两个版本号。

2.1.3 Release 状态查看

# 列出所有 namespace 的 release
helm list -A

# 查看特定 release 的详细信息
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 当前活跃版本 正常,无需处理
superseded 已被新版本取代 保留用于回滚参考
failed 安装或升级失败 查看 helm status 中的 NOTES 和 events
pending-install 安装中 检查 hook Job 是否卡住
pending-upgrade 升级中 检查 hook Job 和资源创建状态
pending-rollback 回滚中 检查回滚目标版本的资源兼容性
uninstalling 删除中 检查 finalizer 和 pre-delete hook

2.2 第一轮判断:模板渲染与语法检查

2.2.1 helm lint — 静态检查

# 基础 lint
helm lint ./mychart

# 带 values 文件的 lint
helm lint ./mychart -f values-prod.yaml

# 严格模式
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 必填字段缺失、模板语法错误(括号未闭合、函数不存在)、资源名称不符合 K8s 命名规范。它不能捕获的问题:Values 值类型不匹配、运行时资源冲突、镜像不存在。

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

# 渲染并校验 API 版本(连接集群)
helm template myapp ./mychart -f values-prod.yaml --validate

# 渲染结果重定向到文件,方便 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 都执行helm template并与主分支的渲染结果做 diff,任何意外的资源变更都应触发人工审核。

2.2.3 helm diff 插件

# 安装 diff 插件
helm plugin install https://github.com/databus23/helm-diff

# 查看升级将产生的差异(不实际执行)
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在执行helm upgrade之前展示所有即将变更的资源,是生产环境变更审批的必备环节。

2.3 第二轮下钻:Go 模板语法与 Values 设计

2.3.1 Go 模板语法核心

变量引用与内置对象

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

关键语法点

语法 作用 常见错误
{{ .Values.xxx }} 引用 values 值 路径写错不报错,渲染为空
{{- ... -}} 去除左/右空白 过度使用导致 YAML 缩进错误
{{ include "tpl" . }} 调用命名模板 忘记传递.上下文
{{ toYaml . | nindent N }} 转 YAML 并缩进 N 格 nindent 值错误导致 YAML 解析失败
{{ with .Values.xxx }} 条件块并切换上下文 块内无法访问.Release等顶级对象
{{ range .Values.list }} 遍历列表 块内.指向当前元素,需$.Values访问顶级
{{ default "val" .Values.xxx }} 默认值 空字符串不触发 default
{{ required "msg" .Values.xxx }} 必填校验 仅在渲染时触发,lint 不检查
{{ quote .Values.xxx }} 加引号 数字类型意外变字符串

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标识
*/}}
{{-define"myapp.chart"-}}
{{-printf"%s-%s".Chart.Name.Chart.Version|replace"+""_"|trunc63|trimSuffix"-"}}
{{-end}}

{{/*
通用标签
*/}}
{{-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标签—不可变,用于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}}

{{/*
名称截断
*/}}
{{-define"myapp.name"-}}
{{-default.Chart.Name.Values.nameOverride|trunc63|trimSuffix"-"}}
{{-end}}

63 字符截断是 Kubernetes 对 label value 的硬限制,在命名模板中必须始终保留这个约束。

2.3.3 Values 分层覆盖设计

Helm Values 的合并顺序(后者覆盖前者):

Chart 内 values.yaml(默认值)
↓ 被覆盖
父 Chart 的 values.yaml(子 Chart 依赖场景)
↓ 被覆盖
-f / --values 指定的文件(可多个,后指定的优先)
↓ 被覆盖
--set/ --set-string / --set-json(命令行直接设置)

values.yaml 设计原则

# values.yaml — 默认值,适配 dev 环境
replicaCount:1

image:
repository:registry.myorg.com/myapp
pullPolicy:IfNotPresent
tag:""# 留空,默认使用 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 开关
postgresql:
enabled:true
auth:
 database:myapp
 username:myapp
primary:
 persistence:
  size:10Gi

redis:
enabled:false

values-prod.yaml — 生产环境覆盖

# 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

敏感值处理

生产环境的 Secret 不应写在 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 部署常见故障

故障现象 层级 可能原因 诊断命令 修复方向
helm install 报 YAML 解析错误 L0 模板缩进错误、nindent 值不对 helm template 查看渲染结果 修正模板缩进
helm install 报 "cannot re-use a name" L2 同名 Release 已存在(可能是 failed 状态) helm list -A --all helm uninstall 旧 Release 或换名
upgrade 报 "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 触发滚动
hook Job 卡在 Pending L4 资源配额不足、nodeSelector 不匹配 kubectl describe job 调整 Job 资源或调度约束
"lookup function not supported" L0 使用lookup函数但在helm template中运行 改用helm install --dry-run lookup 只在连接集群时可用
"chart requires kubeVersion >=1.28" L0 集群版本低于 Chart 要求 kubectl version 升级集群或调整 Chart.yaml
OCI pull 401 Unauthorized L2 Registry 认证失败 helm registry login 测试 检查凭证配置
values 中的 list 被 --set 覆盖而非追加 L1 Helm --set 对 list 是替换行为 helm template 对比渲染结果 使用 --set-json 或 -f 文件
CRD 升级未生效 L0 Helm 不管理 CRD 的升级(只负责首次安装) kubectl get crd -o yaml 手动 kubectl apply CRD

2.5 处理与验证

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

关键参数说明:

--wait:等待所有 Pod 就绪后才标记 Release 为 deployed,否则即使 Pod 还没启动完就返回成功

--atomic:如果升级失败(--wait 超时、hook 失败),自动回滚到上一个成功版本

--timeout:与 --wait 配合,设置等待超时。默认 5m,复杂应用建议调到 10-15m

--keep-history:uninstall 时保留历史,后续可以 rollback

2.5.2 部署后验证清单

# 1. 确认 Release 状态
helm status myapp -n production

# 2. 确认渲染结果符合预期
helm get values myapp -n production    # 查看当前生效的 values
helm get manifest myapp -n production   # 查看当前生效的完整 manifest

# 3. 确认 Pod 状态
kubectl get pods -n production -l app.kubernetes.io/instance=myapp

# 4. 确认 Service 端点
kubectl get endpoints -n production -l app.kubernetes.io/instance=myapp

# 5. 确认 Ingress
kubectl get ingress -n production -l app.kubernetes.io/instance=myapp

# 6. 运行 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"     # 权重越小越先执行
 "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 类型与执行时机:

Hook 触发时机 典型用途
pre-install install 前,资源创建前 数据库初始化、前置检查
post-install install 后,所有资源创建后 注册服务、发送通知
pre-upgrade upgrade 前 数据库迁移、数据备份
post-upgrade upgrade 后 缓存预热、健康检查
pre-rollback rollback 前 数据库回退迁移
post-rollback rollback 后 状态清理
pre-delete uninstall 前 资源清理、数据导出
post-delete uninstall 后 外部资源清理
test helm test 执行时 连通性测试、功能验证

Hook 删除策略:

策略 含义
before-hook-creation 执行新 hook 前删除旧的同名资源
hook-succeeded hook 成功后删除
hook-failed hook 失败后删除

生产建议:始终设置before-hook-creation,否则第二次 upgrade 时会因为同名 Job 已存在而失败。

三、示例代码和配置

3.1 OCI Registry 操作

Helm v3.17+ 中 OCI 是默认支持的 Chart 存储格式,不再需要设置环境变量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

# 查看远程 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 多环境批量编排

# 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

# 部署到指定环境
helmfile -e production apply

# 只部署特定 release
helmfile -e production -l name=myapp apply

# 销毁环境
helmfile -e staging destroy

3.4 Library Chart 设计

Library Chart 不直接生成 Kubernetes 资源,而是提供可复用的模板片段给其他 Chart 引用。

# common-library/Chart.yaml
apiVersion:v2
name:common-library
version:2.1.0
type:library # 关键:声明为 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-}}

在应用 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".}}

这样组织内所有微服务 Chart 只需维护 values 差异,Deployment 的基础结构由 Library Chart 统一管理。

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']
# 运行测试
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 进行全面健康检查,输出诊断报告
# 适用场景:Release 部署后状态异常、Pod 未就绪、Service 无端点时使用
# 使用方法:./helm-release-diagnose.sh  
# 输入参数:$1=Release名称 $2=命名空间
# 输出结果:终端输出诊断报告,包含 Release 状态、Pod 状态、事件、日志摘要
# 风险提示:只读操作,不修改任何资源。需要 helm 和 kubectl 权限

RELEASE="${1:?Usage: $0  }"
NAMESPACE="${2:?Usage: $0  }"

echo"=========================================="
echo"Helm Release 诊断报告"
echo"Release:${RELEASE}"
echo"Namespace:${NAMESPACE}"
echo"Time:$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo"=========================================="

echo""
echo"--- 1. Release 状态 ---"
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. 当前生效 Values(非默认值) ---"
helm get values"${RELEASE}"-n"${NAMESPACE}"2>&1 ||echo"ERROR: 无法获取 values"

echo""
echo"--- 4. Release 关联的 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 状态正常"
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 端点 ---"
kubectl get endpoints -n"${NAMESPACE}"-l"app.kubernetes.io/instance=${RELEASE}"2>&1

echo""
echo"--- 8. Ingress 状态 ---"
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 执行完整的静态验证
# 适用场景:MR/PR 提交时自动触发,防止有语法错误或渲染异常的 Chart 合入主分支
# 使用方法:./helm-chart-ci-validate.sh  [values-file1] [values-file2] ...
# 输入参数:$1=Chart目录 $2+=可选的 values 文件列表
# 输出结果:通过则退出码 0,失败则退出码 1 并输出错误详情
# 风险提示:只读操作。需要 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 和回滚
# 适用场景:基础组件(如 common-library)升级后需要批量更新所有引用该 library 的应用 Chart
# 使用方法:./helm-bulk-upgrade.sh --env production --chart-version 1.4.2 [--dry-run]
# 输入参数:--env=目标环境 --chart-version=Chart版本 --dry-run=仅模拟不执行
# 输出结果:每个 Release 的升级结果日志
# 风险提示:生产环境务必先 --dry-run。批量操作前确认变更窗口。升级顺序:基础设施 → 中间件 → 应用

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
# 作用:对比两个环境的 values 渲染差异,输出人类可读的 diff 报告
# 适用场景:新环境上线前确认与已有环境的配置差异,防止遗漏关键配置
# 使用方法:./helm-values-diff.sh   
# 输入参数:$1=Chart目录 $2=环境A的values文件 $3=环境B的values文件
# 输出结果:两个环境渲染后的资源 diff(类似 kubectl diff 格式)
# 风险提示:只读操作。大型 Chart 渲染可能消耗较多内存

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

四、实际应用案例

4.1 案例一:helm upgrade 后 Pod 未滚动更新

现场现象:执行helm upgrade myapp ./mychart -f values-prod.yaml -n production返回成功,但 Pod 仍在运行旧版本镜像。helm history显示新 Revision 为 deployed 状态。

第一轮判断

# 查看当前渲染的 manifest 中的镜像
helm get manifest myapp -n production | grep"image:"
# 输出:image: "registry.myorg.com/myapp:3.8.1" — 镜像 tag 确实已更新

# 查看 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 状态
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

关键证据:镜像3.8.1在 Registry 中不存在。CI/CD 流水线在镜像构建完成前就触发了 Helm upgrade。

根因:流水线中 build 和 deploy stage 的依赖关系配置错误,deploy 未等待 build 完成。

修复动作

先推送正确的镜像到 Registry

新 Pod 会自动拉取成功并变为 Ready

修复 CI/CD 流水线中 stage 之间的依赖关系

修复后验证

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

防再发建议

CI/CD 中在 deploy 之前加镜像存在性检查:docker manifest inspect registry.myorg.com/myapp:3.8.1

Helm upgrade 使用--atomic,镜像拉取失败会自动回滚

4.2 案例二:pre-upgrade Hook Job 卡住导致 upgrade 超时

现场现象:helm upgrade myapp ./mychart -f values-prod.yaml -n production --timeout 10m --atomic执行 10 分钟后超时,Release 状态变为failed,然后自动回滚。但数据库迁移 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, ...

关键证据:数据库迁移 Job 请求 2 CPU,但命名空间的 ResourceQuota 已经用了 14/16,剩余 2 CPU 正好不够(因为还有其他 Pending Pod 占用请求)。

根因:迁移 Job 的资源请求过高,且在 upgrade 期间新旧 Pod 同时存在导致 CPU 请求总量超限。

修复动作

# 降低迁移 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

或者调整升级策略,确保新旧 Pod 不会同时大量并存:

# Deployment 使用 Recreate 策略或更保守的 RollingUpdate
spec:
strategy:
 type:RollingUpdate
 rollingUpdate:
  maxSurge:0    # 不额外创建新 Pod
  maxUnavailable:1 # 一次只替换一个

修复后验证

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

防再发建议

Hook Job 的资源请求应与 ResourceQuota 预留空间匹配

在 CI 中加入 ResourceQuota 余量检查

考虑将数据库迁移从 Helm Hook 移到独立的 Job,在 upgrade 之前手动执行

4.3 案例三:Helmfile 多 Release 部署顺序导致依赖未就绪

现场现象:使用 Helmfile 部署三个组件(postgresql、redis、myapp),myapp 部署后 Pod 反复 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,但实际还在初始化
kubectl logs postgresql-0 -n production | tail -5
# 2026-03-01 0930 UTC [1] LOG: database system is ready to accept connections
# 上面这行出现在 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

关键证据:Helmfile 默认并发安装所有 Release,myapp 在 PostgreSQL 完成初始化之前就开始连接数据库。

根因:Helmfile 缺少 Release 之间的依赖和顺序声明。

修复动作

# 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']

修复后验证

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

防再发建议

始终在 Helmfile 中用needs声明 Release 之间的启动依赖

应用端使用 initContainer 或连接重试机制,不依赖部署顺序保证可用性

设置合理的 readinessProbe,让 K8s 在应用真正就绪后才接入流量

4.4 案例四:OCI Registry 推送 Chart 时 401 Unauthorized

现场现象:CI/CD 流水线中执行helm push myapp-1.4.2.tgz oci://registry.myorg.com/charts报错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 的认证配置
cat ~/.config/helm/registry/config.json
# 发现 auth 字段存在但对应的 credsStore 配置指向了 docker-credential-desktop

关键证据:Helm 的 registry 认证走的是 Docker credential store,在 CI 环境中docker-credential-desktop不存在,导致虽然helm registry login显示成功(写入了 config.json),但实际推送时 credential helper 找不到凭证。

根因:CI 环境缺少 Docker credential helper,Helm 的 registry 认证依赖 Docker 的凭证存储体系。

修复动作

# 方案一:在 CI 中禁用 credential helper,使用纯文件认证
exportDOCKER_CONFIG="${HOME}/.docker-helm"
mkdir -p"${DOCKER_CONFIG}"

# 直接写入认证信息
echo'{"auths":{"registry.myorg.com":{"auth":"'$(echo-n"ci-bot:${REGISTRY_PASSWORD}"| base64)'"}}}'
 >"${DOCKER_CONFIG}/config.json"

# 重新执行 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 环境变量
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

修复后验证

# 确认推送成功
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

防再发建议

CI 环境中统一使用HELM_REGISTRY_CONFIG或DOCKER_CONFIG环境变量指定认证配置文件

不依赖系统级的 credential helper

在流水线开始时加helm registry login的连通性验证步骤

五、最佳实践和注意事项

5.1 Chart 设计最佳实践

5.1.1 版本管理

Chart version 遵循 SemVer:Breaking change 升主版本,新功能升次版本,Bug 修复升补丁版本

appVersion 与应用镜像 tag 保持同步

使用 Git tag 触发 Chart 打包和推送

Chart.lock 提交到版本库,保证依赖版本可复现

5.1.2 Values 设计规范

所有可配置项都应有默认值,helm install裸装必须能跑起来(至少在 dev 环境)

使用required函数标记生产环境必填项

敏感值不写入 values 文件,使用 External Secrets Operator 或 --set 注入

布尔开关命名统一:xxx.enabled

资源配置使用嵌套结构:resources.requests.cpu,不要拍平成requestsCpu

# values.yaml 中用 required 标记必填项的模板用法
# templates/deployment.yaml
env:
-name:DATABASE_URL
 value:{{required"database.url is required for production".Values.database.url}}

5.1.3 模板组织

公共逻辑提取到_helpers.tpl

每个 Kubernetes 资源一个模板文件,命名与资源类型对应

复杂的条件渲染用命名模板封装,而不是在资源模板中嵌套大量 if-else

使用{{- include ... | nindent N }}而非{{ template ... }}(template 不能管道传递)

5.1.4 Umbrella Chart 架构

platform-chart/
├── Chart.yaml     # type: application
├── values.yaml    # 全局默认值
├── charts/
│  ├── api/      # 子 Chart: API 服务
│  ├── web/      # 子 Chart: Web 前端
│  ├── worker/    # 子 Chart: 后台任务
│  └── common/    # Library Chart
└── templates/
  ├── _helpers.tpl
  └── shared-configmap.yaml # 跨子 Chart 共享的资源

Umbrella Chart 的 values.yaml 中通过子 Chart 名称作为 key 传递参数:

# 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 最小权限

# 为 Helm 操作创建专用 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 做合规扫描
helm template myapp ./mychart -f values-prod.yaml > rendered.yaml
checkov -f rendered.yaml --framework kubernetes

# 使用 trivy 扫描 Chart 中的配置问题
trivy config ./mychart

5.3 注意事项

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 常见配置错误

错误模式 表现 修正方法
nindent 值错误 YAML 渲染后缩进不对,K8s API 拒绝 用helm template检查输出格式
with 块内访问顶级对象 {{ .Values.xxx }} 变成空 使用{{ $.Values.xxx }}
range 块内的. . 指向当前迭代元素而非根 使用$引用根上下文
数字值未加 quote YAML 解析为科学计数法 用{{ .Values.xxx | quote }}
list 值用 --set 覆盖 整个 list 被替换而非追加 使用--set-json或 -f 文件
Release 历史膨胀 etcd 存储压力增大 设置--history-max 10

5.3.3 Release 存储与 etcd 压力

Helm v3 将 Release 信息存储在 Kubernetes Secret 中(Base64 编码 + gzip 压缩)。每次 upgrade/rollback 都会创建新的 Secret。对于频繁发布的应用,需要限制历史版本数:

# 全局设置最大历史版本
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

六、故障排查和监控

6.1 故障排查决策树

helm install/upgrade 失败
├── 报错"YAML parse error"/"template render error"
│  ├── 检查 helm template 输出
│  ├── 定位到具体模板文件和行号
│  └── 常见:nindent 错误、括号未闭合、函数名拼写
├── 报错"cannot re-use a name"/"has no deployed releases"
│  ├── helm list --all -n  查看现有 Release
│  ├── 如果是 failed 状态:helm uninstall --no-hooks
│  └── 如果是 pending-* 状态:等待或强制清理
├── 报错"timed out waiting for the condition"
│  ├── 检查 Pod 状态:kubectl get pods
│  ├── Hook Job 卡住:kubectl describe job
│  ├── Pod 未 Ready:kubectl describe pod / kubectl logs
│  └── 资源配额不足:kubectl get resourcequota
├── 报错"admission webhook denied"
│  ├── 检查 ValidatingWebhookConfiguration
│  ├── 查看 webhook 服务是否可用
│  └── 临时绕过:删除 webhook 或加 exclude annotation
├── 部署成功但功能异常
│  ├── helm get values 确认 values 生效
│  ├── helm get manifest 确认渲染结果
│  ├── kubectl describe / logs 查看运行时状态
│  └── Service/Endpoints/Ingress 链路检查
└── OCI Registry 相关
  ├── 401 Unauthorized:检查 helm registry login
  ├── not found:确认 Chart 名称和版本号
  └── timeout:检查网络和 DNS

6.2 Helm 操作监控

6.2.1 Release 状态监控

# Prometheus 告警规则:检测 Helm Release 异常状态
# 需要 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 状态"
    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 状态"
    description:"Release{{ $labels.release }}in namespace{{ $labels.namespace }}has been pending for 15 minutes."

6.2.2 CI/CD 部署指标

在 CI/CD 流水线中记录 Helm 部署的关键指标:

# 部署耗时统计
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))

# 推送指标到 Prometheus Pushgateway
cat <

6.3 日志排查路径

# Helm 自身调试日志
helm upgrade myapp ./mychart -f values-prod.yaml -n production --debug 2>&1 | tee helm-debug.log

# 从 debug 日志中提取关键信息
grep -E"(error|Error|FAIL|WARNING)"helm-debug.log

# 查看 Helm 发送给 K8s API 的请求
helm upgrade myapp ./mychart -f values-prod.yaml -n production --debug --dry-run 2>&1 | head -100

6.4 常见问题速查

问题 快速诊断 快速修复
"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 或减少嵌入数据
lookup 在 CI 中失败 helm template 模式不支持 lookup 改用--dry-run=server或移除 lookup
子 Chart values 未生效 helm get values 检查层级 确认 values 中用子 Chart 名称作为 key

七、总结

7.1 技术要点回顾

Helm v3.17+ 去除 Tiller,直接使用 kubeconfig 鉴权,OCI Registry 作为原生 Chart 存储后端

Chart 结构的核心三要素:Chart.yaml(元数据)、values.yaml(参数化)、templates/(模板渲染)

Go 模板语法中最常见的坑:with块上下文切换、range块中.的含义变化、nindent缩进值

Values 分层覆盖遵循「默认值 → 父 Chart → -f 文件 → --set」的优先级链

Hook 的before-hook-creation删除策略是防止 Job 冲突的关键配置

--atomic参数保证升级失败时自动回滚,是生产环境部署的必选项

Library Chart + Umbrella Chart 是企业级多微服务 Chart 管理的标准架构模式

Release 历史存储在 etcd 中,必须通过--history-max限制版本数量

7.2 排障链路总结

完整的 Helm 部署排障链路:

helm lint → helm template → helm diff → helm upgrade --dry-run
→ helm upgrade(实际执行)
→ 失败时:helm status → helmhistory→ kubectl describe/logs
→ 定位到具体层级(Chart 语法 / Values 渲染 / K8s API / 运行时 / Hook)
→ 修复后:helm upgrade --atomic → helmtest→ 验证

7.3 进阶学习方向

Helm SDK 集成:在 Go 程序中直接调用 Helm SDK 实现自定义部署控制器

Chart Testing(ct)工具:在 CI 中自动检测 Chart 变更并运行集成测试

ArgoCD + Helm:GitOps 模式下的 Helm Chart 自动同步和漂移检测

Sigstore 签名验证:对 Chart 进行数字签名,在部署前验证完整性

7.3 参考资料

Helm 官方文档— Chart 开发、最佳实践、命令参考

Helm GitHub— 源码、Issue 跟踪

Artifact Hub— 公共 Chart 搜索

Helmfile 文档— 多 Release 编排

附录

A. Helm 命令速查表

# Chart 开发
helm create mychart             # 创建 Chart 脚手架
helm lint ./mychart --strict         # 静态检查
helm template myapp ./mychart -f values.yaml # 本地渲染
helm package ./mychart            # 打包为 .tgz
helm dependency update ./mychart       # 更新依赖
helm dependency list ./mychart        # 查看依赖状态
helm show chart ./mychart          # 查看 Chart.yaml
helm show values ./mychart          # 查看默认 values

# OCI Registry
helm registry login         # 登录
helm push  oci:///# 推送
helm pull oci:/// --version X # 拉取
helm show chart oci:///    # 查看远程 Chart

# Release 管理
helm install   -f  -n       # 安装
helm upgrade --install   -f  -n  # 升级(不存在则安装)
helm rollback   -n           # 回滚
helm uninstall  -n                # 卸载
helm list -A                        # 列出所有 Release
helm status  -n                  # 查看状态
helmhistory -n                 # 查看历史

# 调试
helm upgrade   --dry-run --debug       # 模拟升级
helm get values  -n                # 查看当前 values
helm get manifest  -n               # 查看当前 manifest
helm get hooks  -n                # 查看 hooks
helmtest -n                   # 运行测试

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 容器
    +关注

    关注

    0

    文章

    535

    浏览量

    23024
  • kubernetes
    +关注

    关注

    0

    文章

    273

    浏览量

    9530

原文标题:Helm包管理实战:企业级应用模板化部署

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Kubernetes Helm入门指南

    Helm 是 Kubernetes 的包管理工具,它允许开发者和系统管理员通过定义、打包和部署应用程序来简化 Kubernetes 应用的管理
    的头像 发表于 04-30 13:42 3207次阅读
    Kubernetes <b class='flag-5'>Helm</b>入门指南

    2017年企业级SaaS服务发展趋势?

    域创业项目数量有近400家,涉及20余个领域,包括CRM、ERP、HR、OA、企业报销、企业商旅、及协同办公、收银支付、考勤管理等。 在经过两三年的市场洗礼后,2017企业级服务的市场
    发表于 07-17 10:22

    使用Helm 在容器服务k8s集群一键部署wordpress

    摘要: Helm 是啥? 微服务和容器给复杂应用部署管理带来了极大的挑战。Helm是目前Kubernetes服务编排领域的唯一开源子项目
    发表于 03-29 13:38

    企业级的LInux系统日志管理

    企业级LInux系统日志管理
    发表于 05-29 11:33

    iPhone OS企业级部署指南

    iPhone OS企业级部署指南
    发表于 12-10 14:51 57次下载

    库神企业级包管理系统介绍

    针对目前的现状,为了更好的服务于区块链领域企业,包括加密资产交易平台、托管平台、支付平台、金融衍生业务平台等,库神公司自主研发出一套综合性加密资产管理系统,即库神企业级包管理系统(C
    发表于 04-25 11:00 2172次阅读

    Helm Kubernetes包管理

    helm.zip
    发表于 04-27 14:25 2次下载
    <b class='flag-5'>Helm</b> Kubernetes<b class='flag-5'>包管理</b>器

    Helm常用命令(chart安装、升级、回滚、卸载等操作)

    Helm 针对 Kubernetes 的 Helm 包管理器。
    的头像 发表于 09-13 14:54 9318次阅读

    Helm部署MinIO集群

    Helm部署MinIO集群
    的头像 发表于 12-03 09:44 1691次阅读
    <b class='flag-5'>Helm</b><b class='flag-5'>部署</b>MinIO集群

    DeepSeek企业级部署实战指南:以Raksmart企业服务器为例

    随着人工智能技术的快速发展,DeepSeek作为一款强大的AI工具,正在成为企业智能转型的重要驱动力。本文将结合Raksmart企业服务器的实际案例,详细解析DeepSeek的企业级
    的头像 发表于 03-12 11:33 1217次阅读

    Kubernetes包管理工具Helm的安装和使用

    Helm 可以帮助我们管理 Kubernetes 应用程序 - Helm Charts 可以定义、安装和升级复杂的 Kubernetes 应用程序,Charts 包很容易创建、版本管理
    的头像 发表于 03-13 16:06 2352次阅读

    Helm仓库管理常用配置

    Helm 仓库(Repository)是存储 Helm 图表(Chart)的地方,类似于软件包管理器的仓库(如 apt、yum 仓库)。
    的头像 发表于 06-07 09:27 1488次阅读

    Helm详细介绍和使用

    Helm是Kubernetes 应用的包管理工具,主要用来管理 Charts,类似Linux系统的yum。
    的头像 发表于 06-17 13:56 1530次阅读

    企业级MySQL数据库管理指南

    在当今数字化时代,MySQL作为全球最受欢迎的开源关系型数据库,承载着企业核心业务数据的存储与处理。作为数据库管理员(DBA),掌握MySQL的企业级部署、优化、维护技能至关重要。本文
    的头像 发表于 07-09 09:50 866次阅读

    Helm包管理模板部署实战

    直接用kubectl管理K8s资源,10个微服务就要维护几十个YAML文件,版本管理靠文件夹命名,回滚靠手动替换文件。Helm把一组相关的K8s资源打包成Chart,支持模板
    的头像 发表于 02-26 16:37 422次阅读