Kubernetes原生的YAML资源文件虽然灵活,但直接用一堆散落的清单文件交付应用,很难做到版本管理、差异化配置和规模化分发。Helm作为Kubernetes生态中最主流的应用打包工具,通过一套约定的目录结构,把应用的所有资源模板、默认参数和元信息组织成一个完整的制品,这就是Chart。本文将从目录结构、参数设计讲到应用市场交付的完整实践,帮助你把应用从“能跑”升级到“能卖”。

一、Helm Chart标准目录结构详解
一个规范的Chart首先体现在目录组织上。Helm对目录结构有明确约定,社区应用市场中的绝大多数Chart都遵循这一约定。看一个典型的多组件应用目录:
myapp/ ├── Chart.yaml # Chart的元信息:名称、版本、依赖 ├── values.yaml # 默认配置参数 ├── values-prod.yaml # 生产环境覆盖参数(可选) ├── charts/ # 依赖的子Chart存放目录 │ └── redis-17.5.0.tgz ├── templates/ # Kubernetes资源模板目录 │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── configmap.yaml │ ├── secret.yaml │ ├── _helpers.tpl # 公共模板片段与命名辅助函数 │ ├── NOTES.txt # 安装成功后的提示输出 │ └── tests/ # 部署后验证用的测试Pod │ └── test-connection.yaml ├── .helmignore # 打包时需要排除的文件 └── README.md # 使用说明
其中Chart.yaml是整个Chart的身份证明,它声明了应用名称、描述、维护者以及依赖关系。apiVersion目前推荐使用v2,依赖信息直接写在这个文件里而不是单独的requirements.yaml:
apiVersion: v2
name: myapp
description: 一个演示用的Web应用
type: application
version: 0.3.2 # Chart自身版本
appVersion: "1.18.0" # 应用镜像版本
maintainers:
- name: devops-team
email: team@ipipp.com
dependencies:
- name: redis
version: "17.5.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
这里有一个容易混淆的概念需要厘清:version指的是Chart打包模板本身的版本,每次修改模板都必须递增它;而appVersion指的是容器镜像对应的业务版本,两者独立演进。很多团队初期把这两个版本混为一谈,导致模板改了但版本没变,应用市场缓存的旧制品被反复安装,排查起来非常痛苦。
templates/_helpers.tpl虽然不生成任何实际资源,却是保证命名一致性的关键。把名称、标签这类多处引用的值抽取成命名模板:
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "myapp.labels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
所有资源模板统一引用include "myapp.labels",既减少了重复代码,也保证了标签体系的完整,便于后续通过标签选择器做运维管理。
二、values参数分层:一份Chart适配多环境多租户
市场化交付的核心诉求是:同一份Chart制品,不同客户、不同环境安装出不同形态。这完全依赖values.yaml的参数化设计。values.yaml中只放最通用的默认值:
replicaCount: 2
image:
repository: registry.ipipp.com/myapp
tag: ""
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
redis:
enabled: true
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 8
安装时通过-f传入环境覆盖文件,或用--set做点状覆盖。推荐生产环境使用独立的覆盖文件而不是大量--set,因为覆盖文件本身可以纳入Git管理,变更可追溯:
# 测试环境:使用默认值,2副本 helm install demo ./myapp -n test # 生产环境:覆盖副本数、资源与镜像仓库 helm install demo ./myapp -n prod \ -f values-prod.yaml \ --set image.tag=1.18.0
values-prod.yaml只写差异项,Helm会与默认值做深度合并:
replicaCount: 4
image:
repository: registry-customer-a.ipipp.com/myapp
service:
type: LoadBalancer
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
autoscaling:
enabled: true
参数设计还有几条实践建议值得遵守。第一,把可选功能做成开关,比如上面的autoscaling.enabled和redis.enabled,在模板中用{{- if .Values.autoscaling.enabled }}控制整块资源的渲染,关闭时不产生任何多余对象。第二,所有资源的创建优先添加metadata.labels和matchLabels,保证helm uninstall能干净回收。第三,敏感信息不要写在values文件里,而是引导用户通过existingSecret参数引用集群中已有的Secret,把密钥管理交还给平台侧。
三、应用市场交付:Chart仓库、版本管理与OCI分发
Chart打包完成后,下一步是让用户能方便地获取和安装,这就进入市场化交付环节。传统方式是搭建HTTP协议的Chart仓库,任何能托管静态文件的对象存储或Nginx都可以充当。打包并生成索引:
helm package myapp # 生成 myapp-0.3.2.tgz helm repo index . --url https://charts.ipipp.com # 客户侧使用 helm repo add market https://charts.ipipp.com helm repo update helm search repo market/myapp helm install myapp market/myapp -f my-values.yaml
索引文件index.yaml记录了每个版本的校验和与下载地址,客户端每次helm repo update都会重新拉取索引,因此版本发布只需把新的tgz包和更新后的索引上传到仓库即可。这里务必坚持语义化版本:破坏性变更递增主版本号,新增功能递增次版本号,修复问题递增修订号。市场用户依赖版本号判断升级风险,混乱的版本号会直接摧毁用户信任。
更现代的方案是OCI方式分发。Helm 3.8以后支持把Chart直接推送到符合OCI规范的镜像仓库,与容器镜像统一存储、统一认证、统一漏洞扫描:
helm package myapp helm push myapp-0.3.2.tgz oci://registry.ipipp.com/charts # 拉取安装 helm install myapp oci://registry.ipipp.com/charts/myapp \ --version 0.3.2 -f my-values.yaml
相比HTTP仓库,OCI分发省去了维护索引文件的环节,版本即Tag,天然不可变,而且可以复用镜像仓库的RBAC权限体系控制谁能发布、谁能下载。对于企业内部的应用市场来说,OCI几乎已经是当前的最佳选择。
最后是交付质量的闭环。在templates/tests/目录中放置连接测试Pod,配合helm test可以在安装后自动验证应用是否真正可用;在CI流水线中加入helm lint和helm template | kubectl apply --dry-run=server做渲染校验,能在发布前拦截模板错误。一个经过目录规范化、参数分层化、分发制品化三重打磨的Kubernetes应用,才能称得上真正具备市场化交付的能力。
KubernetesHelm Chart应用市场交付修改时间:2026-09-01 04:40:54