一、概述
1.1 背景介绍
管理Kubernetes的YAML文件一直是个令人头疼的难题。对于一个中等规模的微服务项目,至少会有几十个Deployment、Service、ConfigMap和Ingress资源。如果再乘以开发(dev)、预发(staging)和生产(prod)三套环境,YAML文件的数量轻轻松松就能超过一百个。手动维护这些文件,不仅效率低下,还容易出错——比如修改一个镜像版本,可能需要改动三个地方,一旦遗漏就会导致线上事故。
为此,社区发展出了两条主流技术路线。Helm走的是“模板引擎加包管理”的道路,它将YAML参数化,通过一个values.yaml文件来控制不同环境间的差异。而Kustomize则采用了“纯声明式叠加层”的哲学,不引入任何模板语法,仅仅通过patch和overlay机制在基础配置上叠加环境特定的改动。
这两种方案各自拥有坚定的支持者,相关的争论从2019年Kustomize被集成进kubectl命令行工具后就从未停止。到了2026年,随着GitOps成为主流的部署范式、Helm迭代到v3.17版本、以及Kustomize在ArgoCD和Flux这类工具中深度集成,这个问题有了更清晰的答案——或者说,更现实的答案是:“这取决于你的具体使用场景”。
1.2 技术特点
- Helm:一个完整的包管理器。它支持模板渲染、依赖管理、版本控制和回滚,生态极为丰富,其Chart仓库覆盖了几乎所有主流中间件。
- Kustomize:原生集成于
kubectl中。它坚持零模板语法,完全基于纯YAML进行overlay操作,学习曲线平缓,并且与GitOps理念天然契合。
- 混合方案:结合两者优势,用Helm生成基础模板,再用Kustomize处理环境差异化配置,成为越来越流行的选择。
- 新势力:诸如Timoni和CUE语言等新工具正在挑战传统方案,它们致力于提供类型更安全的配置管理体验。
1.3 适用场景
- 场景一:需要管理多环境(dev/staging/prod)的K8s应用配置,并为此选择合适的配置管理工具。
- 场景二:搭建GitOps流水线(如使用ArgoCD或Flux),需要确定底层的配置管理方案。
- 场景三:维护内部平台的基础设施组件(如数据库、消息队列、监控栈),需要一种标准化的部署方式。
- 场景四:评估是否应该从单一的Helm或Kustomize方案迁移到两者结合的混合方案。
1.4 环境要求
| 组件 |
版本要求 |
说明 |
| Kubernetes |
1.32+ |
2026年主流的生产环境版本 |
| Helm |
3.17+ |
支持OCI Registry、JSON Schema校验 |
| Kustomize |
5.6+ |
独立的二进制版本,功能比kubectl内置版更完整 |
| kubectl |
1.32+ |
内置Kustomize 5.x版本 |
| ArgoCD |
2.14+ |
GitOps持续交付工具,原生支持Helm和Kustomize |
| Flux |
2.5+ |
GitOps工具套件,提供HelmRelease和Kustomization CRD |
二、详细步骤
2.1 Helm核心概念与模板语法
2.1.1 三大核心概念
Helm的设计哲学是成为“Kubernetes的apt或yum”,其核心围绕着三个概念展开:
Chart(包):一个Chart就是一组Kubernetes资源定义的模板集合,其中包含了模板文件、默认配置值、依赖声明和元数据。一个典型的Chart目录结构如下:
mychart/
├── Chart.yaml # Chart元数据(名称、版本、依赖)
├── values.yaml # 默认参数值
├── values.schema.json # 参数JSON Schema校验(强烈建议加上)
├── templates/ # Go模板文件目录
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── _helpers.tpl # 可复用的模板片段
│ └── NOTES.txt # 安装后的提示信息
├── charts/ # 子Chart依赖
└── crds/ # CRD定义(安装时自动应用)
Release(实例):Chart的一次安装就称为一个Release。同一个Chart可以被多次安装,每次都会生成一个独立的Release,它们之间互不干扰。例如,你可以使用同一个Redis Chart,分别安装为redis-cache和redis-session,各自拥有独立的配置和生命周期。
Repository(仓库):存放Chart包的地方。Helm v3.17已经全面拥抱OCI Registry,这意味着你可以直接将Chart推送到Harbor、ECR、GCR等容器镜像仓库,不再需要单独维护ChartMuseum这样的传统仓库。
# OCI方式推送和拉取Chart(推荐)
helm push mychart-1.0.0.tgz oci://harbor.example.com/charts
helm install myapp oci://harbor.example.com/charts/mychart --version 1.0.0
# 传统HTTP仓库方式(仍然支持)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install redis bitnami/redis --version 20.6.0
2.1.2 模板语法实战
Helm使用Go template引擎,语法上手不难,但实际操作中可能会遇到一些“坑”。下面是一个生产级Deployment模板的示例:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
# 配置变更时自动触发滚动更新
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
ports:
- containerPort: {{ .Values.service.targetPort | default 8080 }}
{{- if .Values.resources }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.probes.enabled }}
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path | default "/healthz" }}
port: {{ .Values.service.targetPort | default 8080 }}
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds | default 10 }}
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path | default "/readyz" }}
port: {{ .Values.service.targetPort | default 8080 }}
{{- end }}
对应的values.yaml文件:
# values.yaml - 默认值,生产环境通过 -f 覆盖
replicaCount: 2
image:
repository: registry.example.com/myapp
tag: "" # 留空则使用Chart.AppVersion
service:
type: ClusterIP
port: 80
targetPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
probes:
enabled: true
liveness:
path: /healthz
initialDelaySeconds: 10
readiness:
path: /readyz
nodeSelector: {}
2.1.3 Helm的几个关键“坑”
坑一:模板调试困难。 Go template报错信息非常晦涩,有时一个缩进错误会导致完全不相干的错误提示。必须养成使用helm template命令预渲染模板的习惯:
# 渲染模板但不安装,检查输出是否正确
helm template myapp ./mychart -f values-prod.yaml > rendered.yaml
# 加 --debug 看更详细的错误信息
helm template myapp ./mychart -f values-prod.yaml --debug
# 用 --dry-run=server 做服务端校验(会检查CRD是否存在等)
helm install myapp ./mychart -f values-prod.yaml --dry-run=server
坑二:values.yaml缺乏类型校验。 如果你不小心传递了一个字符串"true"给期望布尔值的字段,模板不会报错,但行为会完全不符合预期。解决方案是添加values.schema.json文件:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["replicaCount", "image"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 50
},
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": { "type": "string", "minLength": 1 },
"tag": { "type": "string" }
}
}
}
}
坑三:Chart依赖地狱。 大型Chart(如kube-prometheus-stack)可能嵌套三四层子Chart,values的覆盖路径极深,要修改一个参数可能得翻半天文档。建议使用helm show values命令导出完整的默认值,再在其基础上进行修改:
# 导出Chart的完整默认values
helm show values bitnami/redis > redis-defaults.yaml
# 只覆盖需要改的部分
helm install redis bitnami/redis -f redis-custom.yaml
2.2 Kustomize核心概念与实战
2.2.1 设计哲学:不写模板,只做叠加
Kustomize的核心理念是“保持原始YAML的可读性”。它不引入任何模板语法,所有Kubernetes资源文件都是合法的、可以直接被kubectl apply的YAML。环境差异通过overlay(叠加层)和patch(补丁)机制来处理,基础配置始终保持干净。
这意味着:任何时候打开base目录下的YAML文件,你看到的都是一个完整的、可运行的Kubernetes资源定义,不需要在脑子里做变量替换。
2.2.2 目录结构:base + overlay
标准的Kustomize项目结构:
k8s/
├── base/ # 基础配置,所有环境共享
│ ├── kustomization.yaml # 资源清单和公共配置
│ ├── deployment.yaml # 原始Deployment
│ ├── service.yaml # 原始Service
│ └── configmap.yaml # 原始ConfigMap
├── overlays/
│ ├── dev/ # 开发环境叠加层
│ │ ├── kustomization.yaml
│ │ ├── replica-patch.yaml # 副本数补丁
│ │ └── env-config.env # 环境变量
│ ├── staging/ # 预发环境叠加层
│ │ ├── kustomization.yaml
│ │ └── resource-patch.yaml
│ └── prod/ # 生产环境叠加层
│ ├── kustomization.yaml
│ ├── replica-patch.yaml
│ ├── resource-patch.yaml
│ └── hpa.yaml # 生产独有的HPA
└── components/ # 可复用组件(Kustomize 5.x)
├── monitoring/ # 监控sidecar注入
│ └── kustomization.yaml
└── istio/ # Istio注解注入
└── kustomization.yaml
2.2.3 base层配置
base层存放的是“裸”YAML,不含任何环境特定信息:
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
commonLabels:
app.kubernetes.io/managed-by: kustomize
# 所有资源加统一前缀(可选)
# namePrefix: myapp-
# base/deployment.yaml - 干净的原始YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: registry.example.com/myapp:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
2.2.4 overlay层实战
生产环境的overlay在base基础上叠加差异:
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- hpa.yaml # 生产环境额外资源
namespace: production # 强制设置namespace
images:
- name: registry.example.com/myapp
newTag: v2.3.1 # 锁定生产镜像版本
patches:
# Strategic Merge Patch:按字段路径合并
- path: replica-patch.yaml
# JSON 6902 Patch:精确操作(增删改)
- target:
kind: Deployment
name: myapp
patch: |-
- op: replace
path: /spec/template/spec/containers/0/resources/requests/cpu
value: "500m"
- op: replace
path: /spec/template/spec/containers/0/resources/requests/memory
value: "512Mi"
- op: replace
path: /spec/template/spec/containers/0/resources/limits/cpu
value: "2000m"
- op: replace
path: /spec/template/spec/containers/0/resources/limits/memory
value: "2Gi"
# 引入可复用组件
components:
- ../../components/monitoring
configMapGenerator:
- name: myapp-config
behavior: merge
literals:
- LOG_LEVEL=warn
- CACHE_TTL=3600
# overlays/prod/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
构建和应用:
# 预览渲染结果(不实际应用)
kubectl kustomize overlays/prod
# 直接应用到集群
kubectl apply -k overlays/prod
# 或者用独立的kustomize二进制(功能更完整)
kustomize build overlays/prod | kubectl apply -f -
# 查看差异(先看再改,养成习惯)
kubectl diff -k overlays/prod
2.2.5 Kustomize的几个关键能力
configMapGenerator / secretGenerator:自动生成带hash后缀的ConfigMap/Secret,当配置变更时,名称的hash值会改变,从而自动触发引用它的Pod滚动更新。这就不需要像Helm那样手动添加checksum annotation。
configMapGenerator:
- name: app-config
files:
- config.properties
# 生成的名称类似:app-config-k5m8h2d9
# 引用该ConfigMap的Deployment会自动更新引用名
components:Kustomize 5.x引入的可复用模块,解决了不同overlay之间共享配置的问题。例如,“注入Prometheus监控sidecar”这个操作,dev和prod环境都需要,把它抽成component就避免了重复编写。
# components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- target:
kind: Deployment
patch: |-
- op: add
path: /spec/template/metadata/annotations/prometheus.io~1scrape
value: "true"
- op: add
path: /spec/template/metadata/annotations/prometheus.io~1port
value: "9090"
2.3 Helm vs Kustomize详细对比
2.3.1 核心维度对比
| 维度 |
Helm |
Kustomize |
| 配置方式 |
Go模板 + values.yaml参数化 |
纯YAML overlay + patch |
| 学习曲线 |
中等偏高(Go template语法、Chart结构) |
低(会写YAML就能上手) |
| 可读性 |
模板文件可读性差,需要渲染后才能看到最终结果 |
base层始终是合法可读的YAML |
| 包管理 |
完整的包管理能力(版本、依赖、仓库) |
无包管理概念,依赖Git管理 |
| 生态 |
丰富,ArtifactHub上万个社区Chart |
较少,主要靠自己写base |
| 回滚 |
helm rollback原生支持Release级回滚 |
依赖Git revert或GitOps工具 |
| Hooks |
支持pre-install/post-install等生命周期钩子 |
不支持,需要外部工具配合 |
| 类型校验 |
values.schema.json(可选) |
无内置校验机制 |
| GitOps兼容 |
需要额外处理(Helm Release状态管理) |
天然兼容(纯声明式) |
| 调试体验 |
helm template + --debug,报错信息晦涩 |
kubectl kustomize,报错直观 |
2.3.2 什么时候该用Helm
- 部署第三方中间件:像Redis、PostgreSQL、Kafka、Prometheus这类基础设施组件,社区的Chart经过了大量生产环境的验证,自己从零开始写YAML既费时又容易遗漏细节。直接
helm install省心省力。
- 需要完整的Release生命周期管理:安装、升级、回滚、卸载,Helm提供了完整的工作流。特别是它的回滚能力,
helm rollback myapp 3一条命令就能回到第3个版本,比手动kubectl apply旧版本YAML要可靠得多。
- 跨团队分发内部应用:把应用打包成Chart推送到内部的OCI Registry,其他团队
helm install就能使用,values.yaml提供了一个清晰的配置接口。
- 需要安装时执行初始化逻辑:例如数据库迁移、证书生成、配置校验等,Helm hooks可以在安装/升级的不同阶段执行特定的Job。
2.3.3 什么时候该用Kustomize
- 管理自研应用的多环境配置:对于自研应用,YAML结构完全可控,用Kustomize的overlay管理dev/staging/prod之间的差异,比维护一套Helm模板要简单直观得多。
- GitOps优先的团队:Kustomize的纯声明式特性和Git天然契合。ArgoCD的Application可以直接指向Kustomize的overlay目录,配置变更就等于Git commit,审计追踪清清楚楚。
- 团队Go template经验不足:Kustomize没有模板语法,不会出现“缩进错一个空格整个模板炸掉”的问题。新人上手快,Code Review也更容易。
- 需要对第三方Helm Chart做微调:先用
helm template渲染出YAML,然后放进Kustomize的base目录,最后再用overlay做定制化修改。这种方式比在values.yaml里翻找深层次的参数更直接。
三、混合使用方案与GitOps场景
3.1 Helm + Kustomize混合方案
3.1.1 为什么要混合用
在实际的生产环境中,单一的Helm或Kustomize方案有时会显得力不从心。典型场景是:你想用Helm部署Prometheus Stack,但需要针对不同集群注入特定的relabel配置、调整资源限制、或者添加自定义的PrometheusRule。这些定制化需求,要么在values.yaml里找不到对应参数,要么嵌套层级太深,修改起来非常头疼。
混合方案的思路很直接:让Helm负责生成基础的、标准化的YAML,让Kustomize负责处理环境和集群的差异化定制。
3.1.2 方案一:helm template + Kustomize overlay
这是最简单的混合方式,即把Helm渲染后的结果作为Kustomize的base:
# 渲染Helm Chart为静态YAML
helm template prometheus prometheus-community/kube-prometheus-stack \
-f values-base.yaml \
--namespace monitoring \
--output-dir ./k8s/base/prometheus-stack
# 目录结构
k8s/
├── base/
│ └── prometheus-stack/ # helm template渲染结果
│ └── kube-prometheus-stack/
│ └── templates/
│ ├── prometheus/
│ ├── alertmanager/
│ └── grafana/
├── overlays/
│ ├── cluster-a/ # 集群A的定制
│ │ ├── kustomization.yaml
│ │ └── alertmanager-patch.yaml
│ └── cluster-b/ # 集群B的定制
│ ├── kustomization.yaml
│ └── resource-patch.yaml
这种方式的缺点是,当Helm Chart升级时,需要重新渲染base,操作繁琐且容易遗漏。可以使用Makefile或CI脚本将其自动化:
# Makefile
HELM_CHART := prometheus-community/kube-prometheus-stack
HELM_VERSION := 68.4.0
BASE_DIR := k8s/base/prometheus-stack
.PHONY: render-base
render-base:
rm -rf $(BASE_DIR)
helm template prometheus $(HELM_CHART) \
--version $(HELM_VERSION) \
-f values-base.yaml \
--namespace monitoring \
--output-dir $(BASE_DIR)
@echo "Base rendered from Chart version $(HELM_VERSION)"
3.1.3 方案二:ArgoCD原生Helm + Kustomize
ArgoCD 2.14+ 支持在同一个Application中同时使用Helm和Kustomize,不需要手动渲染中间产物:
# ArgoCD Application:Helm渲染 + Kustomize后处理
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: prometheus-stack
namespace: argocd
spec:
project: infrastructure
source:
repoURL: https://prometheus-community.github.io/helm-charts
chart: kube-prometheus-stack
targetRevision: 68.4.0
helm:
releaseName: prometheus
valuesObject:
prometheus:
prometheusSpec:
retention: 30d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
resources:
requests:
storage: 100Gi
# Kustomize后处理:在Helm渲染结果上叠加修改
kustomize:
patches:
- target:
kind: Prometheus
name: prometheus-prometheus
patch: |-
- op: add
path: /spec/additionalScrapeConfigs
value:
name: additional-scrape-configs
key: prometheus-additional.yaml
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
3.1.4 方案三:Flux HelmRelease + Kustomization
Flux的方案设计上更优雅,HelmRelease和Kustomization是两个独立的CRD,天生支持组合:
# flux-system/helmrelease-redis.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: redis
namespace: data
spec:
interval: 30m
chart:
spec:
chart: redis
version: "20.x" # 允许patch版本自动升级
sourceRef:
kind: HelmRepository
name: bitnami
values:
architecture: replication
auth:
enabled: true
existingSecret: redis-credentials
replica:
replicaCount: 3
resources:
requests:
cpu: 250m
memory: 256Mi
# 安装后用Kustomize做后处理
postRenderers:
- kustomize:
patches:
- target:
kind: StatefulSet
name: redis-replicas
patch: |-
- op: add
path: /spec/template/spec/topologySpreadConstraints
value:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: redis
3.2 GitOps场景下的选择
3.2.1 ArgoCD场景
ArgoCD对Helm和Kustomize都有一流的支持,但两者的使用方式有本质区别。
Kustomize方式:ArgoCD直接读取Git仓库中的Kustomize overlay目录,通过kubectl kustomize渲染后,与集群的当前状态进行diff。配置变更就是Git commit,审计链路完整,Code Review流程可以自然地融入其中。
Helm方式:ArgoCD可以直接从Helm Repository拉取Chart,也可以从Git仓库读取Chart源码。但Helm Release的状态(revision history)存储在集群的Secret里,这与ArgoCD自身对应用状态的管理存在重叠,偶尔会出现状态不一致的问题。
# ArgoCD ApplicationSet:多集群 + Kustomize overlay
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
env: production
template:
metadata:
name: "myapp-{{name}}"
spec:
project: default
source:
repoURL: https://git.example.com/platform/k8s-configs.git
targetRevision: main
path: "apps/myapp/overlays/{{metadata.labels.region}}"
destination:
server: "{{server}}"
namespace: myapp
3.2.2 Flux场景
Flux的设计理念天然偏向Kustomize——它的核心CRD就叫Kustomization。所有的资源(包括HelmRelease)都通过Kustomization来编排和管理:
# flux-system/kustomization.yaml - 编排所有基础设施组件
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: infra-repo
path: ./infrastructure/production
prune: true
dependsOn:
- name: cert-manager # 先装cert-manager
- name: external-secrets # 再装external-secrets
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: prometheus-operator
namespace: monitoring
3.2.3 GitOps场景选型建议
| 场景 |
推荐方案 |
理由 |
| 自研应用多环境部署 |
Kustomize + ArgoCD/Flux |
纯声明式,Git diff即配置diff |
| 第三方中间件部署 |
Helm + postRenderers |
复用社区Chart,Kustomize做微调 |
| 多集群统一管理 |
ApplicationSet + Kustomize |
overlay按集群/区域组织,结构清晰 |
| 平台工程内部分发 |
Helm OCI + values overlay |
Chart作为标准交付单元,values覆盖实现定制 |
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 Helm最佳实践
- 始终添加values.schema.json:类型校验能在CI/CD阶段拦截大量低级错误,比如不小心把字符串
"3"传给replicaCount。Helm 3.17版本的schema校验性能已大幅优化,不会拖慢安装速度。
- 用OCI Registry替代ChartMuseum:到了2026年,还在使用ChartMuseum的团队应该考虑迁移了。Harbor 3.x、ECR、GCR等主流镜像仓库都已原生支持OCI Artifact,Chart和镜像统一管理,权限模型也保持一致。
# 登录OCI Registry
helm registry login harbor.example.com
# 打包并推送
helm package ./mychart
helm push mychart-1.0.0.tgz oci://harbor.example.com/charts
# 安装时直接引用OCI地址
helm install myapp oci://harbor.example.com/charts/mychart --version 1.0.0
- Chart版本和AppVersion分离:
Chart.yaml中的version是Chart自身的版本(当模板结构变更时递增),appVersion是所部署应用的版本(当镜像tag变更时递增)。两者应独立管理,不要混为一谈。
- 利用Helm test做部署验证:在templates目录下放置一个test Pod,使用
helm test命令会在安装后运行它,以验证服务是否正常工作:
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include \"mychart.fullname\" . }}-test"
annotations:
"helm.sh/hook": test
spec:
restartPolicy: Never
containers:
- name: curl-test
image: curlimages/curl:8.11.0
command: ['curl', '--fail', 'http://{{ include \"mychart.fullname\" . }}:{{ .Values.service.port }}/healthz']
4.1.2 Kustomize最佳实践
- 用独立二进制而非kubectl内置版:
kubectl内置的Kustomize版本通常落后独立版本1-2个大版本,会缺少components、helmCharts等新功能。在CI/CD流水线中,建议安装独立的kustomize二进制。
- 善用configMapGenerator的hash后缀:这是Kustomize最实用的功能之一。ConfigMap名称带有hash后缀,当内容变更时名称也随之改变,这会自动触发引用它的Deployment滚动更新,彻底解决了“改了ConfigMap但Pod没重启”的问题。
- overlay之间不要互相引用:例如,dev环境的overlay不应该引用staging环境的overlay。每个overlay应该只引用base和components,保持独立性。否则,一旦形成依赖链,修改一个overlay可能会无意间影响其他环境。
- 用replacements替代已废弃的vars:Kustomize 5.x已经废弃了
vars字段,应使用replacements来实现跨资源的值引用:
# kustomization.yaml
replacements:
- source:
kind: Service
name: myapp
fieldPath: metadata.name
targets:
- select:
kind: Ingress
name: myapp
fieldPaths:
- spec.rules.0.http.paths.0.backend.service.name
4.1.3 通用最佳实践
- CI流水线中做dry-run校验:无论使用Helm还是Kustomize,在代码合并到主分支前,必须在CI流水线中运行一次渲染和校验:
# Helm:渲染 + 服务端校验
helm template myapp ./mychart -f values-prod.yaml | kubectl apply --dry-run=server -f -
# Kustomize:渲染 + 服务端校验
kustomize build overlays/prod | kubectl apply --dry-run=server -f -
# 用kubeconform做离线Schema校验(不需要连接集群)
kustomize build overlays/prod | kubeconform -strict -kubernetes-version 1.32.0
- 配置和代码分仓管理:将应用代码仓库和Kubernetes配置仓库分开。代码仓库的CI流水线负责构建镜像,并更新配置仓库中的镜像tag;配置仓库的变更则触发GitOps工具进行部署。这样做的好处是配置变更有独立的审计日志,回滚配置时不需要回滚代码。
4.2 注意事项
4.2.1 Helm常见陷阱
| 陷阱 |
现象 |
解决方案 |
| Release状态残留 |
helm install报“already exists”但helm list看不到 |
kubectl get secret -l owner=helm找到残留Secret并清理 |
| CRD升级不生效 |
Chart升级后CRD没有更新 |
Helm不会自动升级crds/目录下的CRD,需要手动kubectl apply |
| 模板渲染顺序问题 |
依赖的资源还没创建就被引用 |
使用helm.sh/hook-weight控制Hook的执行顺序 |
| values合并行为不符预期 |
数组类型的values被整体替换而非合并 |
Helm对数组执行的是替换操作,而不是合并。这是设计行为,需要在values结构设计上规避。 |
4.2.2 Kustomize常见陷阱
| 陷阱 |
现象 |
解决方案 |
| commonLabels污染selector |
加了commonLabels后Deployment升级失败 |
selector.matchLabels是不可变字段,对已有资源不要后加commonLabels |
| patch路径错误静默失败 |
JSON patch指定了不存在的路径,不报错也不生效 |
使用kubectl kustomize预览输出,对比base确认patch是否生效 |
| 资源名称冲突 |
多个base中有同名资源导致合并异常 |
使用namePrefix或nameSuffix区分,或将资源拆分到不同namespace |
| 远程base版本漂移 |
引用GitHub上的base没有锁定版本 |
始终使用?ref=v1.2.3锁定Git tag,不要引用main等移动分支 |
五、故障排查和监控
5.1 Helm故障排查
5.1.1 安装/升级失败排查
# 查看Release历史,定位失败的revision
helm history myapp -n production
# 查看指定revision的详细信息
helm get all myapp --revision 3 -n production
# 查看渲染后的manifest(对比预期)
helm get manifest myapp -n production
# 查看实际传入的values(排查参数覆盖问题)
helm get values myapp -n production
# 查看Helm操作日志(存在Secret中)
kubectl get secret -n production -l owner=helm,name=myapp --sort-by=.metadata.creationTimestamp
常见失败场景一:upgrade卡在pending-upgrade状态
这是Helm用户最头疼的问题之一。通常是上一次upgrade操作超时或被中断,导致Release状态卡住了。
# 查看当前Release状态
helm status myapp -n production
# 如果状态是pending-upgrade / pending-install / pending-rollback
# 方案一:回滚到上一个成功的版本
helm rollback myapp 0 -n production # 0表示回滚到上一个成功版本
# 方案二:如果回滚也失败,手动修复Release状态(最后手段,操作前务必备份)
# 找到最新的Release Secret
SECRET_NAME=$(kubectl get secret -n production -l owner=helm,name=myapp \
--sort-by=.metadata.creationTimestamp -o name | tail -1)
# 修改Secret中的状态字段(将pending-upgrade改为deployed)
kubectl get secret $SECRET_NAME -n production -o jsonpath='{.data.release}' | \
base64 -d | base64 -d | gzip -d | jq '.info.status = "deployed"' | \
gzip | base64 | base64 | \
kubectl patch secret $SECRET_NAME -n production \
-p "{\"data\":{\"release\":\"$(cat)\"}}"
常见失败场景二:资源冲突 “rendered manifests contain a resource that already exists”
# 找出冲突的资源是谁创建的
kubectl get <resource-type> <resource-name> -n <namespace> -o yaml | \
grep -A5 "annotations:" | grep "app.kubernetes.io/managed-by"
# 如果是之前手动kubectl apply创建的,需要给它打上Helm标签
kubectl annotate <resource-type> <resource-name> \
meta.helm.sh/release-name=myapp \
meta.helm.sh/release-namespace=production
kubectl label <resource-type> <resource-name> \
app.kubernetes.io/managed-by=Helm
5.1.2 Helm性能问题排查
在大规模集群中Helm操作变慢,通常与Release历史记录堆积有关:
# 查看Release历史数量
helm history myapp -n production --max 999 | wc -l
# 清理旧版本(保留最近5个)
# 在helm install/upgrade时设置
helm upgrade myapp ./mychart --history-max 5
5.2 Kustomize故障排查
5.2.1 渲染问题排查
# 查看完整渲染结果
kubectl kustomize overlays/prod 2>&1
# 常见错误:accumulating resources
# 原因:kustomization.yaml中引用了不存在的文件
# 排查:逐个检查resources列表中的文件路径
kustomize build overlays/prod --stack-trace 2>&1
# 对比两个环境的渲染差异
diff <(kubectl kustomize overlays/staging) <(kubectl kustomize overlays/prod)
# 验证渲染结果的YAML合法性
kubectl kustomize overlays/prod | kubectl apply --dry-run=client -f - 2>&1
5.2.2 Patch不生效排查
Kustomize patch不生效是最常见的问题,排查思路:
# 1. 确认target资源存在于base中
kubectl kustomize base/ | grep "kind: Deployment" -A3
# 2. 确认patch的metadata.name和base中的资源名称完全匹配
# Strategic Merge Patch靠name匹配,名称不一致就静默跳过
# 3. 对于JSON 6902 Patch,确认target selector能匹配到资源
# 在kustomization.yaml中加--enable-alpha-plugins看详细日志
kustomize build overlays/prod --enable-alpha-plugins 2>&1
# 4. 验证JSON path是否正确
# 先渲染base,用yq检查目标路径是否存在
kubectl kustomize base/ | yq eval '.spec.template.spec.containers[0].resources' -
六、总结
6.1 技术要点回顾
- Helm的核心价值是包管理和生态:对于部署第三方中间件、跨团队分发应用、管理完整的Release生命周期这些场景,Helm依然是最优解。Go template的学习成本和调试体验是其主要的痛点,但通过合理使用
values.schema.json和helm template命令可以有效缓解。
- Kustomize的核心价值是简洁和GitOps亲和:在管理自研应用的多环境配置、坚持纯声明式理念、以及需要与
kubectl原生集成的场景下,Kustomize比Helm更轻量、更直观。其configMapGenerator的hash后缀机制和components复用能力是两个杀手级功能。
- 混合方案是2026年的主流实践:使用Helm来管理第三方Chart,再结合Kustomize进行环境差异化定制,这种混合模式正被越来越多的团队采纳。主流的GitOps工具如ArgoCD和Flux都已原生支持这种组合。我们无需在Helm和Kustomize之间做非此即彼的选择。
- GitOps工具的选择会影响配置管理方案:ArgoCD对Helm和Kustomize提供了同等的良好支持,而Flux的设计则天然偏向Kustomize。在选定GitOps工具后,配置管理方案也需要进行相应的适配。
6.2 2026年趋势:Timoni和CUE的崛起
Helm和Kustomize在Kubernetes配置管理领域统治多年,但它们的一些根本局限性一直存在:Helm的Go template缺乏类型系统,复杂模板的可维护性令人担忧;Kustomize的patch机制在大规模场景下可能变得碎片化,overlay数量膨胀后管理成本不低。
Timoni是Flux团队推出的下一代包管理器,基于CUE语言构建。它试图同时解决Helm和Kustomize的痛点:
// timoni.cue - Timoni Module定义示例
package main
import (
"timoni.sh/core"
)
// 类型安全的values定义,编译期就能发现错误
#Values: {
image: {
repository: string & =~"^[a-z0-9./-]+$"
tag: string | *"latest"
}
replicas: int & >=1 & <=100
resources: {
requests: {
cpu: string & =~"^[0-9]+m$"
memory: string & =~"^[0-9]+(Mi|Gi)$"
}
}
}
// 默认值
values: #Values & {
image: {
repository: "registry.example.com/myapp"
tag: "v2.3.1"
}
replicas: 2
resources: requests: {
cpu: "100m"
memory: "128Mi"
}
}
CUE语言的核心优势:
- 类型安全:值的约束在定义时就声明,不合法的配置在编译期就会报错,不需要等到
kubectl apply时才暴露问题。
- 统一定义和约束:Schema(类型定义)和Value(具体值)用同一种语法表达,不像Helm需要分别维护
values.yaml和values.schema.json。
- 无副作用的合并:CUE的值合并是幂等的、可交换的,不存在Helm values中数组合并那种“顺序敏感”的问题。
- 原生支持大规模配置:CUE的包系统和导入机制比Helm的子Chart和Kustomize的base/overlay模型更适合管理数百个微服务的配置。
当前状态(2026年初):Timoni仍处于快速迭代阶段,其API尚未完全稳定,社区生态与Helm相比差距明显。CUE语言本身的学习曲线比Go template更陡峭——它不仅仅是一门模板语言,而是一门完整的配置语言,需要理解格(lattice)理论和类型统一等概念。
务实的建议:
- 现有项目:继续使用Helm + Kustomize混合方案,不需要急于迁移。
- 新项目评估:如果团队有能力和意愿投入学习CUE,可以在非关键业务的新项目上试点Timoni,积累实践经验。
- 关注但不押注:Timoni/CUE代表了配置管理向类型安全、声明式、可组合方向的演进,但其生态成熟度目前还不足以支撑生产环境的大规模使用。作为开发者,我们可以持续在像云栈社区这样的技术论坛关注其进展。
6.3 选型决策树
需要部署第三方中间件?
├── 是 → 用Helm(复用社区Chart)
│ └── 需要环境定制?→ Helm + Kustomize postRenderers
└── 否(自研应用)
├── 团队熟悉Go template?
│ ├── 是 → Helm Chart(跨团队分发)或Kustomize(单团队管理)
│ └── 否 → Kustomize(学习成本低)
└── 使用GitOps?
├── ArgoCD → Helm或Kustomize均可
└── Flux → 优先Kustomize + HelmRelease