1. 简介与核心作用
Helm 是 K8S 的包管理器,用于定义、安装、升级复杂的 K8S 应用。
plaintext
┌─────────────────────────────────────────────────────────────────┐
│ Helm 核心概念 │
├─────────────────────────────────────────────────────────────────┤
│ Chart ──▶ 应用模板包(类比 RPM/Deb) │
│ Repository ──▶ Chart 仓库(类比 yum/apt 源) │
│ Release ──▶ Chart 的运行时实例(可多实例) │
│ values.yaml ──▶ 配置参数(模板变量) │
└─────────────────────────────────────────────────────────────────┘
表格
| 问题 | kubectl | Helm |
|---|---|---|
| 多文件管理 | 散落 YAML | Chart 统一打包 |
| 多环境部署 | 手动修改 | values 覆盖 |
| 版本回滚 | 手动操作 | helm rollback |
2. Chart 结构详解
plaintext
mychart/
├── Chart.yaml # 元信息
├── values.yaml # 默认配置
├── values.schema.json # 校验(可选)
├── templates/ # K8S 资源模板
│ ├── _helpers.tpl # 命名模板
│ ├── deployment.yaml
│ ├── service.yaml
│ └── NOTES.txt
└── .helmignore
Chart.yaml
yaml
apiVersion: v2
name: mywebapp
version: 1.0.0
kubeVersion: ">=1.24.0"
dependencies:
- name: redis
version: "18.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
values.yaml
yaml
replicaCount: 2
image:
repository: myregistry/mywebapp
tag: "v1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
hosts:
- host: myapp.example.com
paths:
- path: /
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
redis:
enabled: true
2.1 values 覆盖机制
plaintext
┌─────────────────────────────────────────────────────────────────┐
│ values 优先级(从高到低) │
├─────────────────────────────────────────────────────────────────┤
│ 1. --set / --set-file 命令行参数 │
│ 2. --values 指定文件 │
│ 3. Release revision(回滚时使用) │
│ 4. Chart values.yaml(最低) │
└─────────────────────────────────────────────────────────────────┘
bash
# 多层覆盖
helm install myapp ./mychart \
-f values.yaml \
-f values-prod.yaml \
--set replicaCount=5
# 嵌套覆盖
helm upgrade myapp ./mychart \
--set service.type=LoadBalancer \
--set 'ingress.hosts[0].host=prod.example.com'
2.2 依赖管理
bash
# 下载依赖
helm dependency build ./mychart
# 查看依赖
helm dependency list ./mychart
# NAME VERSION REPOSITORY STATUS
# redis 18.x.x https://charts.bitnami.com/bitnami installed
2.3 Helm Hooks
plaintext
┌─────────────────────────────────────────────────────────────────┐
│ Hook 生命周期 │
├─────────────────────────────────────────────────────────────────┤
│ install/upgrade: │
│ pre-install → post-install │
│ pre-upgrade → post-upgrade │
│ uninstall: │
│ pre-delete → post-delete │
└─────────────────────────────────────────────────────────────────┘
Hook 示例:数据库迁移 Job
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: "-1"
helm.sh/hook-delete-policy: before-hook-creation,hook-failed
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: myapp/migrate:latest
command: ["/migrate.sh"]
3. Helm 3 架构
plaintext
┌───────────────────────────────────────────────────────────────────┐
│ Helm 3 架构(无 Tiller) │
├───────────────────────────────────────────────────────────────────┤
│ │
│ helm CLI ───────────┐ │
│ (本地客户端) │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ K8S API Server │ │
│ │ │ │
│ │ Secrets │ Release 存储在 kube-system │
│ │ (Release 信息) │ │
│ └─────────────────┘ │
│ │
│ helm template ──▶ 本地渲染(无需 K8S) │
│ │
└───────────────────────────────────────────────────────────────────┘
Helm 3 vs Helm 2
表格
| 特性 | Helm 2 | Helm 3 |
|---|---|---|
| 服务端 | Tiller | 无 |
| Release 存储 | Tiller 内存 | K8S Secrets |
| 依赖声明 | chart: | dependencies: |
Chart 渲染流程
plaintext
┌───────────────────────────────────────────────────────────────────┐
│ Chart 渲染流程 │
├───────────────────────────────────────────────────────────────────┤
│ │
│ 1. 合并 values (values.yaml + 覆盖文件 + --set) │
│ │ │
│ ▼ │
│ 2. Go Template 渲染 templates/*.yaml │
│ - _helpers.tpl 命名模板 │
│ - range/if/include 等函数 │
│ │ │
│ ▼ │
│ 3. K8S API 创建资源 │
│ │ │
│ ▼ │
│ 4. Secret 存储 Release 信息,revision++ │
│ │
└───────────────────────────────────────────────────────────────────┘
4. 实战配置
场景 1:完整 Chart 开发
templates/deployment.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mywebapp.fullname" . }}
labels:
{{- include "mywebapp.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mywebapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "mywebapp.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "mywebapp.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
livenessProbe:
httpGet:
path: /healthz
port: http
readinessProbe:
httpGet:
path: /ready
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
templates/service.yaml
yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "mywebapp.fullname" . }}
labels:
{{- include "mywebapp.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "mywebapp.selectorLabels" . | nindent 4 }}
templates/ingress.yaml
yaml
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "mywebapp.fullname" . }}
annotations:
{{- with .Values.ingress.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }} - {{ . | quote }}{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ include "mywebapp.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
templates/_helpers.tpl
yaml
{{- define "mywebapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "mywebapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "mywebapp.labels" -}}
helm.sh/chart: {{ include "mywebapp.chart" . }}
{{ include "mywebapp.selectorLabels" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "mywebapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mywebapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "mywebapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
场景 2:多环境部署
plaintext
mywebapp/
├── values.yaml # 默认值
├── values.dev.yaml # 开发环境
├── values.staging.yaml
└── values.prod.yaml # 生产环境
values.prod.yaml
yaml
replicaCount: 3
image:
pullPolicy: Always
service:
type: LoadBalancer
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
- secretName: myapp-tls-prod
hosts:
- myapp.example.com
resources:
limits:
cpu: 2000m
memory: 2Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
部署命令
bash
# 开发环境
helm install myapp-dev ./mywebapp -f values.yaml -f values.dev.yaml
# 生产环境
helm install myapp-prod ./mywebapp -f values.yaml -f values.prod.yaml
场景 3:Subchart 依赖
Chart.yaml
yaml
apiVersion: v2
name: mywebapp
version: 1.0.0
dependencies:
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "18.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
values.yaml(传递子 Chart 配置)
yaml
replicaCount: 2
image:
repository: myregistry/mywebapp
postgresql:
enabled: true
auth:
username: myapp
database: myappdb
primary:
persistence:
size: 10Gi
redis:
enabled: true
architecture: replication
auth:
enabled: true
场景 4:Hooks 实战
pre-install + post-install 组合
yaml
# templates/hooks/db-init.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mywebapp.fullname" . }}-db-init
annotations:
helm.sh/hook: pre-install
helm.sh/hook-weight: "-1"
helm.sh/hook-delete-policy: before-hook-creation,hook-failed
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: "{{ .Values.image.repository }}/migrate"
command: ["/migrate.sh", "up"]
---
# templates/hooks/verify.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mywebapp.fullname" . }}-verify
annotations:
helm.sh/hook: post-install
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: verify
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
sleep 10
curl -f http://{{ include "mywebapp.fullname" . }}:{{ .Values.service.port }}/healthz
场景 5:模板函数与管道
templates/configmap.yaml
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mywebapp.fullname" . }}-config
data:
# 基础变量
APP_NAME: {{ .Chart.Name | quote }}
APP_VERSION: {{ .Chart.Version | quote }}
RELEASE_NAME: {{ .Release.Name | quote }}
RELEASE_TIME: {{ .Release.Time | date "2006-01-02" | quote }}
# 字符串处理
APP_NAME_UPPER: {{ .Chart.Name | upper | quote }}
# 默认值
REPLICA_COUNT: {{ .Values.replicaCount | default 1 | toString | quote }}
LOG_LEVEL: {{ .Values.config.logLevel | default "info" | quote }}
# 条件渲染
{{- if .Values.features.debug }}
DEBUG_MODE: "true"
{{- end }}
# 多行配置
CUSTOM_CONFIG: |
server:
port: {{ .Values.service.port }}
logging:
level: {{ .Values.config.logLevel | default "info" }}
场景 6:生产级 values
yaml
# values.yaml
replicaCount: 2
environment: production
image:
repository: myregistry/mywebapp
tag: "v1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: myapp.example.com
paths:
- path: /
tls:
- secretName: myapp-tls
hosts:
- myapp.example.com
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
5. 常用命令
Chart 开发
bash
# 创建 Chart
helm create mychart
# 验证 Chart
helm lint ./mychart
# 本地渲染
helm template myrelease ./mychart
helm template myrelease ./mychart --debug
# 渲染 + values 覆盖
helm template myrelease ./mychart -f values.yaml -f values.prod.yaml
# 打包
helm package ./mychart
Release 管理
bash
# 安装
helm install myapp ./mychart
helm install myapp ./mychart -f values.prod.yaml --namespace myns
# 升级
helm upgrade myapp ./mychart
helm upgrade myapp ./mychart --atomic # 失败自动回滚
helm upgrade --install myapp ./mychart # 不存在则安装
# 回滚
helm rollback myapp 1
# 卸载
helm uninstall myapp
# 查看列表
helm list --all-namespaces
helm history myapp
# REVISION STATUS CHART DESCRIPTION
# 1 superseded myapp-1.0.0 Install complete
# 2 deployed myapp-1.1.0 Upgrade complete
依赖管理
bash
helm dependency build ./mychart
helm dependency update ./mychart
helm dependency list ./mychart
Repository
bash
# 添加仓库
helm repo add bitnami https://charts.bitnami.com/bitnami
# 更新
helm repo update
# 搜索
helm search repo nginx
helm search hub postgresql
# 索引
helm repo index ./myrepo --url https://charts.example.com
6. 常见问题与排查
问题 1:upgrade 失败与回滚
bash
# 查看历史
helm history myapp
# 查看失败原因
helm get values myapp --revision 2
# 回滚
helm rollback myapp 1
# --atomic 自动回滚
helm upgrade myapp ./mychart --atomic
问题 2:模板渲染错误
bash
# 本地调试
helm template myapp ./mychart --debug
# Dry-run
helm install myapp ./mychart --dry-run
# 常见错误修复
# 未定义变量:
{{ .Values.missing | default "default" }}
# 类型转换:
replicas: {{ .Values.replicaCount | int }}
# range 空列表:
{{- range .Values.items }}
- {{ .name }}
{{- else }}
- []
{{- end }}
问题 3:依赖冲突
bash
# 查看依赖状态
helm dependency list ./mychart
# 重新下载
helm dependency build ./mychart --verify
# 本地依赖(离线)
ls ./mychart/charts/
7. 最佳实践
Chart 设计
表格
| 规范 | 说明 |
|---|---|
| 版本命名 | SemVer 2 |
| Chart 名称 | lowercase + hyphen |
| 敏感信息 | 使用 Secret,不在 values 明文 |
| 默认值 | 生产级,非最小配置 |
生产配置
yaml
# Pod 安全上下文
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
# NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "mywebapp.fullname" . }}
spec:
podSelector:
matchLabels:
{{- include "mywebapp.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
CI/CD 集成
bash
# 1. 打包验证
helm lint ./mychart && helm package ./mychart
# 2. 测试部署
helm upgrade --install myapp ./mychart --namespace test --dry-run
# 3. 测试
helm test myapp --namespace test
# 4. 清理
helm uninstall myapp --namespace test
OCI 仓库(Helm 3.8+)
bash
helm registry login registry.example.com
helm push mychart-1.0.0.tgz oci://registry.example.com/charts
helm pull oci://registry.example.com/charts/mychart --version 1.0.0
总结
- Chart = 应用模板包,Release = 运行实例
- Helm 3 无 Tiller,直接 K8S API
- values 优先级:命令行 > 文件 > 默认值
- Hooks 实现生命周期管理
- 调试 :优先
helm template --debug