Kubernetes-09 Helm 包管理:从入门到实战
目录
Helm 包管理:从入门到实战
系列第九篇。Helm 是 K8s 的包管理器,让你可以将复杂的 K8s 应用打包、版本化、分发和管理。
目录
1. Helm 是什么
Helm 是 K8s 的包管理器,类比:
- Linux 的
apt/yum - Node.js 的
npm - Go 的
go mod
解决的问题:
没有 Helm 时,部署一个有多个组件的应用(Deployment + Service + Ingress + ConfigMap + HPA + RBAC…)需要维护多个独立的 YAML 文件,没有版本管理,多环境配置困难。
Helm 的核心概念:
| 概念 | 类比 | 描述 |
|---|---|---|
| Chart | npm package | 包含应用所有 K8s 资源的模板集合 |
| Release | 安装的 package | Chart 安装到集群后的一个实例 |
| Repository | npm registry | 存储和分发 Chart 的仓库 |
| Values | 配置文件 | 自定义 Chart 的参数 |
2. 安装与基础命令
# 安装 Helm
brew install helm # macOS
# 或
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# 验证
helm version
2.1 仓库管理
# 添加常用仓库
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
# 更新仓库索引
helm repo update
# 搜索 Chart
helm search repo redis
helm search repo mysql
# 查看 Chart 信息
helm show chart bitnami/redis
helm show values bitnami/redis # 查看所有可配置参数
2.2 安装与管理 Release
# 安装(最简单形式)
helm install my-redis bitnami/redis
# 安装时覆盖参数
helm install my-redis bitnami/redis \
--set auth.password=mypassword \
--set replica.replicaCount=1
# 使用 values 文件
helm install my-redis bitnami/redis -f redis-values.yaml
# 指定命名空间
helm install my-redis bitnami/redis \
--namespace production \
--create-namespace
# 查看所有 Release
helm list
helm list -A # 所有命名空间
helm list -n production
# 查看 Release 状态
helm status my-redis
# 升级
helm upgrade my-redis bitnami/redis --set replica.replicaCount=3
# 升级(不存在则安装)
helm upgrade --install my-redis bitnami/redis -f values.yaml
# 回滚
helm rollback my-redis 1 # 回滚到版本 1
helm rollback my-redis # 回滚到上一版本
# 查看历史版本
helm history my-redis
# 卸载
helm uninstall my-redis
helm uninstall my-redis --keep-history # 保留历史记录(可以回滚)
2.3 调试命令
# 渲染模板(不安装,只看生成的 YAML)
helm template my-app ./my-chart -f values.yaml
# 渲染并校验
helm template my-app ./my-chart | kubectl apply --dry-run=client -f -
# lint 检查
helm lint ./my-chart
helm lint ./my-chart -f values.yaml
# 渲染特定模板
helm template my-app ./my-chart --show-only templates/deployment.yaml
3. Chart 结构详解
# 创建新 Chart
helm create go-app
# 目录结构
go-app/
├── Chart.yaml # Chart 元数据
├── values.yaml # 默认 Values
├── values.schema.json # Values 的 JSON Schema 校验(可选)
├── README.md # 文档(可选)
├── LICENSE # 许可证(可选)
├── charts/ # 依赖的子 Chart
├── crds/ # Custom Resource Definitions
└── templates/ # K8s 资源模板
├── NOTES.txt # 安装后显示的帮助信息
├── _helpers.tpl # 模板辅助函数(不会渲染为 K8s 资源)
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── configmap.yaml
├── hpa.yaml
├── serviceaccount.yaml
└── tests/ # 测试模板
└── test-connection.yaml
Chart.yaml
apiVersion: v2 # Helm 3 用 v2
name: go-app
description: A Go web application
type: application # application | library
# 版本:Chart 版本(每次修改 Chart 时递增)
version: 1.2.0
# 应用版本(应用程序的版本,如镜像 tag)
appVersion: "2.5.0"
keywords:
- go
- web
- api
maintainers:
- name: Platform Team
email: platform@company.com
dependencies:
- name: redis
version: "~18.0"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
4. 模板语法
Helm 模板使用 Go Template 语法,并扩展了 Sprig 函数库。
4.1 基本语法
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
# 变量引用
name: {{ .Release.Name }}-go-app
namespace: {{ .Release.Namespace }}
labels:
# 引用辅助函数(定义在 _helpers.tpl)
{{- include "go-app.labels" . | nindent 4 }}
annotations:
# 条件语句
{{- if .Values.podAnnotations }}
{{- toYaml .Values.podAnnotations | nindent 4 }}
{{- end }}
spec:
# 三元运算符
replicas: {{ .Values.replicaCount | default 1 }}
selector:
matchLabels:
{{- include "go-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "go-app.selectorLabels" . | nindent 8 }}
{{- if .Values.podLabels }}
{{- toYaml .Values.podLabels | nindent 8 }}
{{- end }}
spec:
containers:
- name: go-app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
4.2 内置对象
.Release.Name → Release 名称(helm install 时的名字)
.Release.Namespace → Release 所在命名空间
.Release.IsInstall → 是否是首次安装
.Release.IsUpgrade → 是否是升级
.Chart.Name → Chart 名称
.Chart.Version → Chart 版本
.Chart.AppVersion → 应用版本
.Values → values.yaml 中的值(或 -f 指定的)
.Files → 访问 Chart 中的非模板文件
.Capabilities → K8s 集群的版本信息
4.3 _helpers.tpl(模板函数)
{{/*
Expand the name of the chart.
*/}}
{{- define "go-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "go-app.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 }}
{{/*
Common labels
*/}}
{{- define "go-app.labels" -}}
helm.sh/chart: {{ include "go-app.chart" . }}
{{ include "go-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "go-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "go-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
4.4 常用模板技巧
# 空白控制(- 消除左右空白/换行)
{{- "去掉左边空白" }}
{{ "去掉右边空白" -}}
{{- "两边都去掉" -}}
# nindent:缩进并在前面加换行
{{- toYaml .Values.resources | nindent 10 }}
# indent:只缩进(不加换行)
{{- toYaml .Values.resources | indent 10 }}
# if/else
{{- if .Values.ingress.enabled }}
...ingress yaml...
{{- end }}
{{- if eq .Values.env "production" }}
LOG_LEVEL: error
{{- else if eq .Values.env "staging" }}
LOG_LEVEL: warn
{{- else }}
LOG_LEVEL: debug
{{- end }}
# range(遍历)
env:
{{- range $key, $val := .Values.extraEnvs }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
# with(修改作用域)
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 2 }}
{{- end }}
# 字符串函数
{{ "hello" | upper }} # → HELLO
{{ "HELLO" | lower }} # → hello
{{ "hello world" | title }} # → Hello World
{{ printf "%s-%s" "foo" "bar" }} # → foo-bar
{{ .Values.name | trunc 63 }} # 截取前 63 字符
{{ .Values.name | replace "-" "_" }} # 替换字符
# 默认值
{{ .Values.port | default 8080 }}
{{ .Values.tag | default .Chart.AppVersion }}
# 类型转换
{{ .Values.port | toString }}
{{ .Values.count | int }}
{{ "true" | toBool }}
# required(必填参数)
{{ required "image.repository is required" .Values.image.repository }}
5. Values 管理
5.1 values.yaml 设计
# 基本信息
replicaCount: 3
image:
repository: myregistry.io/go-app
tag: "" # 空则使用 Chart.AppVersion
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# Service Account
serviceAccount:
create: true
annotations: {}
name: ""
# Pod 配置
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
podLabels: {}
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
# 探针
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
# 资源
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# HPA
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 20
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
# Service
service:
type: ClusterIP
port: 80
targetPort: 8080
# Ingress
ingress:
enabled: false
className: "nginx"
annotations: {}
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
tls: []
# 节点调度
nodeSelector: {}
tolerations: []
affinity: {}
# 环境变量
env: {}
# APP_ENV: production
# LOG_LEVEL: info
# 额外挂载
extraVolumes: []
extraVolumeMounts: []
# 应用特有配置
config:
serverPort: 8080
logLevel: info
enableMetrics: true
metricsPort: 9090
5.2 values.schema.json(参数校验)
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"required": ["image"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 100
},
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": {
"type": "string",
"minLength": 1
},
"tag": {
"type": "string"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
}
},
"service": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer"]
}
}
}
}
}
6. 从零开发一个 Go 应用 Chart
helm create go-api
cd go-api
templates/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "go-api.fullname" . }}
labels:
{{- include "go-api.labels" . | nindent 4 }}
data:
app.yaml: |
server:
port: {{ .Values.config.serverPort }}
log:
level: {{ .Values.config.logLevel }}
metrics:
enabled: {{ .Values.config.enableMetrics }}
port: {{ .Values.config.metricsPort }}
{{- if .Values.config.extra }}
{{- toYaml .Values.config.extra | nindent 4 }}
{{- end }}
templates/deployment.yaml(完整版):
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "go-api.fullname" . }}
labels:
{{- include "go-api.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "go-api.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
# ConfigMap 变化时触发重启
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "go-api.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "go-api.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
{{- if .Values.config.enableMetrics }}
- name: metrics
containerPort: {{ .Values.config.metricsPort }}
protocol: TCP
{{- end }}
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
{{- range $key, $val := .Values.env }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
{{- if .Values.livenessProbe }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.readinessProbe }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
- name: tmp
mountPath: /tmp
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: config
configMap:
name: {{ include "go-api.fullname" . }}
- name: tmp
emptyDir: {}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
templates/NOTES.txt:
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "go-api.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "go-api.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "go-api.fullname" . }} 8080:{{ .Values.service.port }}
echo "Visit http://127.0.0.1:8080"
{{- end }}
7. Helm Hooks
Hooks 允许在 Release 生命周期的特定时间点执行操作。
# templates/db-migrate.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "go-api.fullname" . }}-db-migrate
annotations:
# pre-install: 安装前执行
# post-install: 安装后执行
# pre-upgrade: 升级前执行(数据库迁移最佳位置)
# post-upgrade: 升级后执行
# pre-delete: 删除前执行
# post-delete: 删除后执行
# pre-rollback: 回滚前执行
# post-rollback: 回滚后执行
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5" # 执行顺序(数字越小越先执行)
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: Never
containers:
- name: db-migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command: ["/app/migrate", "--up"]
env:
- name: DATABASE_DSN
valueFrom:
secretKeyRef:
name: {{ include "go-api.fullname" . }}-secret
key: dsn
8. 依赖管理
# Chart.yaml
dependencies:
- name: redis
version: "~18.0"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled # values.yaml 中的 redis.enabled 控制是否安装
- name: postgresql
version: "~13.0"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
# values.yaml
redis:
enabled: true
auth:
enabled: true
password: "redis-password"
master:
persistence:
size: 5Gi
postgresql:
enabled: false
# 下载依赖
helm dependency update ./go-api
# 查看依赖
helm dependency list ./go-api
9. 多环境发布策略
9.1 多 values 文件覆盖
helm upgrade --install go-api ./chart \
-f values.yaml \
-f values-production.yaml \
--set image.tag=$CI_COMMIT_SHA
# values-production.yaml(只覆盖与 base 不同的部分)
replicaCount: 5
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2
memory: 2Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 50
ingress:
enabled: true
hosts:
- host: api.myapp.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-tls
hosts:
- api.myapp.com
config:
logLevel: warn
9.2 Kustomize + Helm(helmfile)
# helmfile.yaml
environments:
development:
values:
- environments/development.yaml
staging:
values:
- environments/staging.yaml
production:
values:
- environments/production.yaml
releases:
- name: go-api
namespace: {{ .Environment.Name }}
chart: ./charts/go-api
values:
- values.yaml
- values/{{ .Environment.Name }}.yaml
set:
- name: image.tag
value: {{ requiredEnv "IMAGE_TAG" }}
# 部署到生产
IMAGE_TAG=v1.2.0 helmfile -e production sync
10. 常用公共 Chart
# Ingress Nginx(反向代理)
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace
# cert-manager(自动 TLS)
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--set installCRDs=true
# Prometheus Stack(监控)
helm upgrade --install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace
# Loki Stack(日志)
helm upgrade --install loki grafana/loki-stack \
--namespace monitoring
# External Secrets Operator
helm upgrade --install external-secrets \
external-secrets/external-secrets \
--namespace external-secrets-operator --create-namespace
# ArgoCD(GitOps)
helm upgrade --install argocd argo/argo-cd \
--namespace argocd --create-namespace
11. 小结
Helm 工作流
开发 Chart → helm lint(校验)→ helm template(预览)
→ helm upgrade --install(部署)
→ helm history(查看历史)
→ helm rollback(回滚)
常用命令速查
# 仓库
helm repo add <name> <url>
helm repo update
helm search repo <keyword>
# 安装/升级
helm upgrade --install <release> <chart> -f values.yaml --namespace <ns>
# 查看
helm list -A
helm status <release>
helm history <release>
# 回滚
helm rollback <release> [revision]
# 调试
helm template <release> <chart> -f values.yaml
helm lint <chart>
# 开发
helm create <chart-name>
helm package <chart-dir>
helm push <package> oci://registry.io/charts
xingliuhua