IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
在第 39 篇中,我们用 helm create 生成了一个 Chart 骨架,把 Flask + Redis 计数器应用改造成了 Helm Release,体验了"一条命令部署、升级、回滚"的便利。但 helm create 生成的模板是通用的 Nginx 示例,我们只是"改参数"而非"从零构建"。如果现在要你把一个全新的微服务应用 Chart 化,你能否独立完成?
今天这篇,我们就从零开始 ,手动编写一个完整的、生产级的 Helm Chart。从 Chart 的目录结构、Chart.yaml 元数据、values.yaml 设计原则,到 Go Template 模板语法、内置对象、流程控制、命名模板,再到 Chart 的打包和发布,全部覆盖。学完这篇,你将拥有将任意 K8s 应用封装为 Helm Chart 的能力。
一、Chart 的完整结构
一个规范的 Helm Chart 目录结构如下:
bash
flask-redis-chart/
├── Chart.yaml # Chart 元数据(名称、版本、描述)
├── values.yaml # 默认配置值
├── values.schema.json # 可选:JSON Schema 校验 values
├── charts/ # 子 Chart 依赖
├── templates/ # K8s 资源模板
│ ├── NOTES.txt # helm install 后显示的信息
│ ├── _helpers.tpl # 命名模板(可复用的模板片段)
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── pvc.yaml
│ └── hpa.yaml
├── .helmignore # 打包时忽略的文件
├── README.md # 使用文档
└── LICENSE # 可选:开源协议
-
Chart.yaml:Chart 的"身份证",包含名称、版本、API 版本等元数据
-
values.yaml :默认配置值,模板中的
{{ .Values.xxx }}从这里取值 -
templates/:所有 K8s 资源模板,Helm 渲染后生成最终的 YAML 提交给 API Server
-
_helpers.tpl:存放可复用的模板宏,避免在多个文件中重复相同的模板代码
-
NOTES.txt:部署成功后的提示信息,指导用户如何使用部署好的服务
二、从零开始编写 Chart
我们从零开始构建一个 Flask + Redis 应用的 Helm Chart,不依赖 helm create。
2.1 编写 Chart.yaml
bash
apiVersion: v2
name: flask-redis-counter
description: A Helm chart for Flask + Redis Counter Application
type: application
version: 1.0.0
appVersion: "3.0"
maintainers:
- name: IT策士
email: itcishi@example.com
keywords:
- flask
- redis
- counter
home: https://github.com/itcishi/flask-redis-counter
字段说明:
-
apiVersion: v2:Helm v3 的 Chart API 版本 -
name:Chart 名称,必须与目录名一致 -
version:Chart 自身的版本(SemVer 格式),Chart 模板变更时递增 -
appVersion:应用(镜像)的版本,与version独立,应用镜像升级时只需改此字段 -
type: application:application表示这是一个应用 Chart;library表示这是一个库 Chart(只包含可复用的模板宏)
2.2 设计 values.yaml
好的 values.yaml 应该像一份"配置菜单"------结构清晰、注释详尽、有合理的默认值。这是 Helm Chart 设计中最见功力的部分:
bash
# ============================================================
# Flask + Redis 计数器应用 ------ Helm Chart 默认配置
# ============================================================
# -- 全局配置
global:
environment: production
# -- Flask 应用配置
flask:
# -- 副本数
replicaCount: 3
# -- 镜像配置
image:
repository: flask-redis-counter
tag: "3.0"
pullPolicy: IfNotPresent
# -- 服务配置
service:
type: ClusterIP
port: 5000
targetPort: 5000
# -- 资源限制
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
# -- 健康检查
probes:
liveness:
path: /health
port: 5000
periodSeconds: 15
failureThreshold: 3
readiness:
path: /health
port: 5000
periodSeconds: 5
failureThreshold: 2
startup:
path: /health
port: 5000
periodSeconds: 5
failureThreshold: 12
# -- 应用环境变量
config:
FLASK_ENV: production
LOG_LEVEL: info
REDIS_HOST: redis-service
REDIS_PORT: "6379"
# -- ServiceAccount
serviceAccount:
create: true
name: flask-sa
# -- Redis 配置
redis:
# -- 是否部署 Redis(设为 false 可使用外部 Redis)
enabled: true
image:
repository: redis
tag: alpine
# -- 持久化存储
persistence:
enabled: true
size: 1Gi
storageClass: standard
service:
name: redis-service
port: 6379
# -- Ingress 配置
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: counter.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: counter-tls
hosts:
- counter.example.com
设计要点:
-
将配置分组 :
flask.*和redis.*层次清晰,避免顶级字段爆炸 -
提供合理的默认值:所有字段都有默认值,用户不传即可运行
-
注释说明:每个字段的含义和用途
-
开关设计 :
redis.enabled允许用户选择使用内置 Redis 或外部 Redis 服务 -
探针参数可配置:不同环境可能需要调整探针的灵敏度和超时时间
三、Go Template 模板语法详解
Helm 使用 Go 的 text/template 引擎来渲染模板。以下是编写 Chart 时必须掌握的语法。
3.1 基本取值
bash
# 访问 values.yaml 中的值
{{ .Values.flask.replicaCount }}
# 管道操作(类似 Unix 管道,将前一个命令的输出作为后一个命令的输入)
{{ .Values.flask.config.FLASK_ENV | quote }}
# 默认值
{{ .Values.flask.image.tag | default "latest" }}
管道是 Helm 模板中最常用的特性之一。| 将左侧的值传递给右侧的函数处理。quote 函数为字符串添加引号,避免 YAML 解析错误。
3.2 内置对象
除了 .Values,Helm 还提供了丰富的内置对象:
3.3 流程控制
bash
# if/else 条件
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ... 其余 Ingress 配置
{{- end }}
# with 作用域(简化深层取值)
{{- with .Values.flask.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}
# range 遍历
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
{{- end }}
{{- 和 -}} 中的连字符表示去除模板标签前后的空白符,让生成的 YAML 更整洁。nindent 2 表示在内容前添加换行并缩进 2 个空格。
3.4 常用函数
Helm 提供了 Go 模板的完整函数库,还扩展了 Sprig 函数库(共 70+ 函数)。以下是最高频使用的几个:
四、编写模板文件
现在用学到的语法,为 Flask + Redis 应用编写核心模板。
4.1 _helpers.tpl(命名模板)
命名模板用于定义可复用的模板宏------在一个地方定义,多个模板文件中引用:
bash
{{/*
Create a default fully qualified app name.
*/}}
{{- define "flask-redis.fullname" -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "flask-redis.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
{{- end -}}
这些标签是 K8s 推荐的标准标签,用于资源分组和版本追踪。include 调用命名模板时必须传递 .(当前上下文),否则模板内无法访问 .Values。
4.2 configmap.yaml
bash
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
labels:
{{- include "flask-redis.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.flask.config }}
{{ $key }}: {{ $value | quote }}
{{- end }}
range 遍历 flask.config 下的所有键值对,动态生成 ConfigMap 的 data 字段。这样当用户新增配置项时,模板自动识别,无需手动修改 YAML。
4.3 deployment.yaml(核心模板)
bash
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "flask-redis.fullname" . }}
labels:
{{- include "flask-redis.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.flask.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
{{- if .Values.flask.serviceAccount.create }}
serviceAccountName: {{ .Values.flask.serviceAccount.name }}
{{- end }}
containers:
- name: flask
image: "{{ .Values.flask.image.repository }}:{{ .Values.flask.image.tag }}"
imagePullPolicy: {{ .Values.flask.image.pullPolicy }}
ports:
- containerPort: {{ .Values.flask.service.port }}
envFrom:
- configMapRef:
name: {{ .Release.Name }}-config
{{- with .Values.flask.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.flask.probes.liveness }}
livenessProbe:
httpGet:
path: {{ .Values.flask.probes.liveness.path }}
port: {{ .Values.flask.probes.liveness.port }}
periodSeconds: {{ .Values.flask.probes.liveness.periodSeconds }}
failureThreshold: {{ .Values.flask.probes.liveness.failureThreshold }}
{{- end }}
{{- if .Values.flask.probes.readiness }}
readinessProbe:
httpGet:
path: {{ .Values.flask.probes.readiness.path }}
port: {{ .Values.flask.probes.readiness.port }}
periodSeconds: {{ .Values.flask.probes.readiness.periodSeconds }}
failureThreshold: {{ .Values.flask.probes.readiness.failureThreshold }}
{{- end }}
几个设计要点:
-
include "flask-redis.fullname" .使用命名模板生成资源名称,确保同一 Release 的所有资源前缀一致 -
envFrom.configMapRef将整个 ConfigMap 作为环境变量注入,一行配置覆盖所有键值对,比逐项枚举env更简洁 -
with和toYaml配合使用,当资源配置不为空时优雅地插入 YAML 块 -
探针通过
if条件控制,用户可以将某个探针设为空值来禁用
4.4 条件化部署:redis.yaml
通过 redis.enabled 开关,让用户选择使用内置 Redis 还是外部 Redis:
bash
{{- if .Values.redis.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
labels:
{{- include "flask-redis.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}"
ports:
- containerPort: 6379
{{- if .Values.redis.persistence.enabled }}
volumeMounts:
- name: redis-data
mountPath: /data
{{- if .Values.redis.persistence.enabled }}
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-pvc
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.redis.service.name }}
spec:
type: ClusterIP
selector:
app: redis
ports:
- port: {{ .Values.redis.service.port }}
---
{{- if .Values.redis.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.redis.persistence.size }}
storageClassName: {{ .Values.redis.persistence.storageClass }}
{{- end }}
{{- end }}
当 redis.enabled: false 时,整个 Redis Deployment、Service、PVC 都不会被创建。用户可以自行部署 Redis 并在 flask.config.REDIS_HOST 中指定外部服务的地址。
4.5 NOTES.txt
bash
{{- if .Values.ingress.enabled }}
✅ Flask + Redis Counter 已部署!
应用名称: {{ .Release.Name }}
访问地址: http{{ if .Values.ingress.tls }}s{{ end }}://{{ index .Values.ingress.hosts 0 "host" }}
{{- else }}
✅ Flask + Redis Counter 已部署!
应用名称: {{ .Release.Name }}
Service 端口: {{ .Values.flask.service.port }}
使用端口转发访问:
kubectl port-forward svc/{{ .Release.Name }}-service {{ .Values.flask.service.port }}:{{ .Values.flask.service.port }}
{{- end }}
📊 查看状态:
helm status {{ .Release.Name }}
kubectl get pods -l app={{ .Release.Name }}
📈 查看日志:
kubectl logs -f deployment/{{ .Release.Name }}
🔄 升级:
helm upgrade {{ .Release.Name }} . -f custom-values.yaml
五、打包与发布
5.1 本地验证
在打包之前,必须确保 Chart 模板语法正确、渲染结果符合预期:
bash
# 语法检查
helm lint .
# 查看渲染后的 YAML
helm template flask-counter . --debug
# 安装到集群(干运行模式,不实际部署)
helm install flask-counter . --dry-run --debug
5.2 打包
bash
helm package .
# Successfully packaged chart and saved it to: flask-redis-counter-1.0.0.tgz
helm package 将 Chart 目录打包为一个 .tgz 文件,并验证 Chart.yaml 中的版本号是否与文件名一致。
5.3 发布到 Chart 仓库
方式一:发布到 GitHub Pages(免费、最常用)
bash
# 1. 创建 gh-pages 分支
git checkout --orphan gh-pages
# 2. 生成 Helm 仓库索引
helm repo index . --url https://<你的GitHub用户名>.github.io/flask-redis-chart
# 3. 提交 index.yaml 和 .tgz 文件,推送到 gh-pages 分支
方式二:发布到 OCI 兼容的容器镜像仓库
Helm v3.8+ 支持将 Chart 存储为 OCI 镜像(与容器镜像使用相同的仓库和认证机制),简化了 Chart 仓库的托管:
bash
# 登录到 OCI 仓库
helm registry login registry-1.docker.io
# 打包并推送
helm package .
helm push flask-redis-counter-1.0.0.tgz oci://registry-1.docker.io/<你的用户名>
5.4 版本更新
当修改 Chart 模板或 values 后,需要更新版本号并重新打包:
bash
# 修改 Chart.yaml 中的 version
vim Chart.yaml # version: 1.0.0 → 1.1.0
# 重新打包
helm package .
六、常用 Helm 命令速查
七、本篇总结
-
Chart 的完整结构:Chart.yaml 是身份、values.yaml 是菜单、templates/ 是配方、_helpers.tpl 是可复用的模板宏。
-
模板语法核心 :
{{ .Values.xxx }}取值、|管道传递函数、if/else/with/range流程控制、include调用命名模板、toYaml和nindent美化输出。 -
设计原则 :values 提供合理默认值并支持深度自定义,用条件开关(
enabled)控制可选组件,命名模板消除重复代码。 -
打包与发布 :
helm lint/template/install --dry-run三级验证确保正确性,helm package打包为标准.tgz文件,可通过 GitHub Pages 或 OCI 仓库分发。
下一篇------第 41 篇:监控:Metrics Server 与 Prometheus 快速上手,我们将进入 K8s 可观测性领域,从部署 Metrics Server 开始,逐步搭建 Prometheus + Grafana 监控栈,让集群和应用的运行状态不再是一个黑盒。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !