从 0 到 1 构建自定义 Helm Chart

发布于 15 天前  65 次阅读


从 0 到 1 构建自定义 Helm Chart

制作自己的 Helm Chart(Helm 包)是 Kubernetes 应用打包和分发的核心步骤,需遵循标准化目录结构、配置模板化、依赖管理等规范。以下是 从 0 到 1 构建自定义 Helm Chart 的详细步骤,包含核心概念、实操流程、最佳实践及常见问题。

一、前置准备:环境搭建

在开始前,需确保本地已安装以下工具:

工具 作用 安装验证命令
Helm Chart 构建、打包、部署的核心工具 helm version
Kubernetes 集群 (可选)用于测试 Chart 部署 kubectl cluster-info
文本编辑器 编写 Chart 配置(如 VS Code、Vim) -
  • Helm 安装参考:官方文档(支持 Windows/macOS/Linux)

  • 若暂无 K8s 集群,可使用 minikubekind 快速搭建本地测试环境。

二、核心概念:Helm Chart 目录结构

一个标准的 Helm Chart 需遵循固定目录结构,确保兼容性和可维护性。创建后默认结构如下:

my-chart/                # Chart 根目录(名称建议与应用名一致)
├── Chart.yaml           # Chart 元数据(版本、描述、依赖等)
├── values.yaml          # 全局配置变量(可被模板引用,支持用户自定义)
├── charts/              # 子 Chart 依赖(如依赖的 Redis、MySQL 等)
├── templates/           # K8s 资源模板目录(核心)
│   ├── deployment.yaml  # Deployment 模板(定义应用副本、镜像等)
│   ├── service.yaml     # Service 模板(定义访问入口)
│   ├── ingress.yaml     # (可选)Ingress 模板(定义域名路由)
│   ├── _helpers.tpl     # (可选)模板函数/变量(复用逻辑)
│   └── NOTES.txt        # (可选)部署后提示信息(如访问地址)
└── .helmignore          # (可选)打包时忽略的文件(如日志、临时文件)

三、实操步骤:构建自定义 Chart

部署一个 Nginx 应用 为例,演示完整的 Chart 制作流程。

步骤 1:创建基础 Chart 框架

使用 helm create 命令自动生成标准目录结构(避免手动创建目录的繁琐):

# 创建名为 "my-nginx-chart" 的 Chart(名称可自定义)
helm create my-nginx-chart

执行后,会在当前目录生成 my-nginx-chart 文件夹,包含上述所有默认文件。

步骤 2:编辑 Chart 元数据(Chart.yaml)

Chart.yaml 是 Chart 的 “身份证”,记录基本信息和依赖,需根据实际需求修改:

apiVersion: v2          # Chart API 版本(v2 是 Helm 3 推荐版本)
name: my-nginx-chart    # Chart 名称(必须与根目录一致)
description: A custom Helm Chart for Nginx deployment  # 应用描述
type: application       # Chart 类型(application:应用类;library:库类,供其他 Chart 引用)
version: 0.1.0          # Chart 版本(遵循 SemVer 2 规范:主版本.次版本.补丁版本)
appVersion: "1.25"     # 应用本身的版本(如 Nginx 版本)
# (可选)依赖其他 Chart,如需要 Redis 可添加如下配置
dependencies:
  - name: redis         # 依赖的 Chart 名称(需在 Helm 仓库中存在)
    version: 17.10.0    # 依赖的 Chart 版本
    repository: https://charts.bitnami.com/bitnami  # 依赖的 Chart 仓库地址
  • 关键说明
*   `version`:Chart 版本变更需遵循规则(如修复 bug 升补丁版,新增功能升次版本,不兼容变更升主版本);

*   `dependencies`:若有依赖,后续需执行 `helm dependency update` 拉取依赖到 `charts/` 目录。

步骤 3:配置全局变量(values.yaml)

values.yaml 是 Chart 的 “配置中心”,定义模板中可引用的变量(用户部署时可通过 --set 或自定义 values 文件覆盖)。以 Nginx 为例,修改核心配置:

# 1. 应用基础配置
replicaCount: 2  # 部署的副本数(默认 1,此处改为 2 实现高可用)

# 2. 镜像配置(核心)
image:
  repository: nginx  # 镜像仓库(如私有仓库可写 "harbor.example.com/nginx")
  pullPolicy: IfNotPresent  # 镜像拉取策略(Always/IfNotPresent/Never)
  tag: "1.25"        # 镜像标签(与 appVersion 保持一致)

# 3. 镜像拉取密钥(可选,私有仓库需配置)
imagePullSecrets:
  - name: my-registry-secret  # 需提前在 K8s 集群中创建该 Secret

# 4. Service 配置(访问入口)
service:
  type: NodePort  # Service 类型(ClusterIP/NodePort/LoadBalancer)
  port: 80        # Service 暴露的端口
  nodePort: 30080 # NodePort 端口(范围 30000-32767,可选)

# 5. 资源限制(可选,避免应用占用过多资源)
resources:
  limits:
    cpu: "500m"   # 最大 CPU 占用(500 毫核 = 0.5 核)
    memory: "512Mi"# 最大内存占用
  requests:
    cpu: "100m"   # 最小 CPU 申请
    memory: "128Mi"# 最小内存申请

# 6. (可选)Nginx 自定义配置(通过 ConfigMap 挂载)
nginxConfig:
  serverName: localhost
  root: /usr/share/nginx/html
  • 设计原则
*   只保留 “需要用户自定义” 的变量(如副本数、镜像地址、端口),固定逻辑不放入 `values.yaml`;

*   变量命名需清晰(如 `image.repository` 而非 `img.repo`),便于用户理解。

步骤 4:编写 K8s 资源模板(templates/ 目录)

templates/ 目录是 Chart 的核心,存放 带 Helm 模板语法的 K8s YAML 文件(模板语法基于 Go Template,支持变量引用、条件判断、循环等)。

以下是核心模板文件的编写示例:

模板 1:Deployment 模板(templates/deployment.yaml)

定义 Nginx 应用的部署逻辑(副本、镜像、挂载、资源限制等):

apiVersion: apps/v1
kind: Deployment
metadata:
  # 引用 values 中的 Chart 名称和版本,生成唯一名称
  name: {{ .Release.Name }}-{{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}          # 固定标签(Chart 名称)
    release: {{ .Release.Name }}    # 动态标签(Helm Release 名称,每次部署唯一)
spec:
  replicas: {{ .Values.replicaCount }}  # 引用 values 中的副本数
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
      release: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
        release: {{ .Release.Name }}
    spec:
      # (可选)引用镜像拉取密钥
      imagePullSecrets:
        {{- toYaml .Values.imagePullSecrets | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          # 引用镜像配置(仓库:标签)
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          # 容器端口(需与 Nginx 内部端口一致,默认 80)
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          # (可选)健康检查(确保容器正常运行)
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 30  # 启动后延迟 30s 检查
            periodSeconds: 10        # 每 10s 检查一次
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
          # (可选)引用资源限制
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          # (可选)挂载 ConfigMap 到容器(自定义 Nginx 配置)
          volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx/conf.d/default.conf
              subPath: default.conf
      # (可选)定义 ConfigMap 卷(与上面的 volumeMounts 对应)
      volumes:
        - name: nginx-config
          configMap:
            name: {{ .Release.Name }}-nginx-config
            items:
              - key: default.conf
                path: default.conf

模板 2:Service 模板(templates/service.yaml)

定义应用的访问入口,关联 Deployment 的 Pod:

apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-{{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
    release: {{ .Release.Name }}
spec:
  type: {{ .Values.service.type }}  # 引用 values 中的 Service 类型
  selector:
    # 必须与 Deployment 中 Pod 的标签一致,否则无法关联
    app: {{ .Chart.Name }}
    release: {{ .Release.Name }}
  ports:
    - port: {{ .Values.service.port }}  # Service 暴露的端口
      targetPort: http                 # 对应容器的端口(与 Deployment 中 port.name 一致)
      protocol: TCP
      name: http
      {{- if eq .Values.service.type "NodePort" }}
      nodePort: {{ .Values.service.nodePort }}  # 仅 NodePort 类型需要
      {{- end }}

模板 3:(可选)ConfigMap 模板(templates/configmap.yaml)

用于挂载自定义 Nginx 配置(避免直接修改镜像):

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-nginx-config
  labels:
    app: {{ .Chart.Name }}
    release: {{ .Release.Name }}
data:
  default.conf: |
    server {
        listen 80;
        server_name {{ .Values.nginxConfig.serverName }};  # 引用 values 中的配置
        root {{ .Values.nginxConfig.root }};
        location / {
            index index.html;
        }
    }

模板 4:(可选)NOTES.txt(部署后提示)

部署成功后,Helm 会输出该文件的内容,指导用户访问应用:

恭喜!{{ .Chart.Name }} 已成功部署到 {{ .Release.Namespace }} 命名空间。

访问方式:
1. 若 Service 类型为 NodePort:
   curl http://<K8s-Node-IP>:{{ .Values.service.nodePort }}

2. 若 Service 类型为 ClusterIP(仅集群内访问):
   kubectl exec -it <任意Pod名> -- curl http://{{ .Release.Name }}-{{ .Chart.Name }}:{{ .Values.service.port }}

查看 Pod 状态:
kubectl get pods -l app={{ .Chart.Name }} -n {{ .Release.Namespace }}

步骤 5:处理依赖(可选)

Chart.yaml 中定义了依赖(如 Redis),需执行以下命令拉取依赖到 charts/ 目录:

# 进入 Chart 根目录
cd my-nginx-chart
# 拉取依赖(自动下载到 charts/ 目录)
helm dependency update

执行后,会生成 Chart.lock 文件(记录依赖的精确版本,确保每次部署依赖一致),不可手动修改。

步骤 6:验证 Chart 合法性

使用 helm lint 命令检查 Chart 是否存在语法错误、目录结构问题等:

# 检查当前目录的 Chart
helm lint my-nginx-chart
  • 若输出 1 chart(s) linted, 0 chart(s) failed,说明 Chart 合法;

  • 若有错误(如模板语法错误、变量未定义),需根据提示修复。

步骤 7:本地测试部署(可选)

在正式打包前,可通过 helm install --dry-run 模拟部署,验证模板渲染后的 K8s 配置是否正确:

# 模拟部署(--dry-run 不实际创建资源,--debug 输出渲染后的 YAML)
helm install my-nginx-test ./my-nginx-chart --dry-run --debug
  • 查看输出的 YAML 内容,确认:
*   镜像地址、副本数、端口等是否与 `values.yaml` 一致;

*   标签匹配(Deployment 的 selector 与 Pod 标签一致);

*   依赖资源(如 ConfigMap)是否正确挂载。

步骤 8:打包 Chart

将 Chart 打包为 .tgz 文件(便于分发和部署):

# 打包当前目录的 Chart,输出 my-nginx-chart-0.1.0.tgz(名称=Chart名-版本)
helm package my-nginx-chart

打包后,当前目录会生成 .tgz 文件,可上传到 Helm 仓库(如 Harbor、ChartMuseum)供他人使用。

四、部署自定义 Chart

打包完成后,可在 K8s 集群中部署该 Chart:

# 1. 部署 Chart(指定 Release 名称为 my-nginx,使用自定义 values 文件)
helm install my-nginx ./my-nginx-chart \
  --namespace my-nginx-namespace \  # 部署到指定命名空间(需提前创建)
  --set replicaCount=3 \            # 覆盖 values 中的副本数(优先级高于 values.yaml)
  --values my-custom-values.yaml    # (可选)使用自定义 values 文件覆盖配置

# 2. 查看部署状态
helm list -n my-nginx-namespace  # 查看 Release 状态
kubectl get pods -n my-nginx-namespace  # 查看 Pod 是否正常运行

# 3. (可选)更新部署(修改配置后执行)
helm upgrade my-nginx ./my-nginx-chart --set image.tag=1.26 -n my-nginx-namespace

# 4. (可选)卸载部署
helm uninstall my-nginx -n my-nginx-namespace

五、最佳实践

  1. 模板复用:将重复逻辑(如标签、注释)放入 templates/_helpers.tpl,例如:
# 定义标签模板,可在其他模板中引用
{{- define "my-nginx-chart.labels" }}
app: {{ .Chart.Name }}
release: {{ .Release.Name }}
version: {{ .Chart.Version }}
{{- end }}

在 Deployment 中引用:

metadata:
  labels:
    {{- include "my-nginx-chart.labels" . | nindent 4 }}
  1. 变量校验:使用 helm lint 定期检查,避免变量未定义或类型错误;

  2. 版本管理:严格遵循 SemVer 2 规范管理 Chart.yaml 中的 version,避免版本混乱;

  3. 安全配置

  • 敏感信息(如密码、密钥)不放入 values.yaml,改用 K8s Secret,通过 values.yaml 引用 Secret 名称;

  • 私有镜像仓库需配置 imagePullSecrets,避免硬编码凭证;

  1. 文档完善:在 Chart 根目录添加 README.md,说明:
  • Chart 功能、适用场景;

  • values.yaml 中关键变量的含义;

  • 部署步骤和依赖要求。

六、常见问题

  1. 模板渲染错误
  • 报错 undefined variable:检查变量名是否与 values.yaml 一致(如 .Values.image.repo 应为 .Values.image.repository);

  • 语法错误:确保模板标签闭合(如 {{ .Release.Name }} 而非 {{ .Release.Name)。

  1. 依赖拉取失败
  • 检查 Chart.yamlrepository 地址是否正确(如 Bitnami 仓库地址是否为 https://charts.bitnami.com/bitnami);

  • 网络问题:配置 Helm 代理(export HTTP_PROXY=http://proxy:port)。

  1. Pod 启动失败
  • 镜像拉取失败:检查 image.repository 是否正确,私有仓库是否配置 imagePullSecrets

  • 健康检查失败:调整 livenessProbe/readinessProbeinitialDelaySeconds(如 Nginx 启动较慢,可设为 30s)。

通过以上步骤,你已完成自定义 Helm Chart 的制作、验证、打包和部署。可根据实际应用(如 Java 服务、Python 服务)调整 templates/ 目录下的资源模板和 values.yaml 配置,实现标准化的 K8s 应用管理。

他人使用你制作的 my-nginx-chart-0.1.0.tgz Helm 包

非常简单,无需手动解压,直接通过 Helm 命令即可完成部署。以下是详细步骤和使用方式:
一、直接使用本地包部署(适合测试或内部共享)
如果对方能直接获取到 .tgz 文件(例如通过文件传输、共享目录等),可直接用以下命令部署:
步骤 1:将包传输给使用者
通过邮件、网盘、scp 等方式,将 my-nginx-chart-0.1.0.tgz 发送给对方,对方保存到本地目录(如 ~/charts/)。
步骤 2:部署本地包
使用者执行以下 Helm 命令部署:

# 部署本地的 Chart 包,指定 Release 名称(如 my-nginx-deploy)
helm install my-nginx-deploy ~/charts/my-nginx-chart-0.1.0.tgz \
  --namespace my-nginx  # 可选:指定部署的命名空间(需提前创建)

说明:
Helm 会自动处理 .tgz 包的解压和解析,无需手动执行 tar -zxvf 等命令;
部署时可通过 --set 覆盖配置(如修改副本数):

# 示例:部署时将副本数改为 3,端口改为 30081
helm install my-nginx-deploy ~/charts/my-nginx-chart-0.1.0.tgz \
  --set replicaCount=3 \
  --set service.nodePort=30081 \
  --namespace my-nginx

二、通过 Helm 仓库共享(适合团队 / 公开分发)
如果需要多人长期使用,更推荐将 .tgz 包上传到 Helm 仓库(类似 Docker 镜像仓库),使用者通过仓库地址拉取部署,步骤如下:
步骤 1:你需要搭建或使用现有 Helm 仓库
常见的 Helm 仓库方案:
公开仓库:如 Artifact Hub(需审核);
私有仓库:如 Harbor(支持 Helm 仓库)、ChartMuseum、GitLab Pages 等。
以 Harbor 为例,上传步骤:
在 Harbor 中创建一个 Helm 仓库(如 my-charts);
使用 helm push 命令上传包:

# 先添加 Harbor 仓库到本地 Helm
helm repo add my-harbor https://harbor.example.com/chartrepo/my-charts \
  --username 用户名 --password 密码

# 上传 .tgz 包到仓库
helm push my-nginx-chart-0.1.0.tgz my-harbor

步骤 2:使用者通过仓库部署
使用者添加你的仓库到本地:

helm repo add my-harbor https://harbor.example.com/chartrepo/my-charts \
  --username 用户名 --password 密码

# 更新仓库索引(确保获取最新 Chart)
helm repo update

搜索并部署你的 Chart:
# 搜索仓库中的 Chart
helm search repo my-harbor/my-nginx-chart

# 部署(无需本地保存 .tgz 文件,直接从仓库拉取)
helm install my-nginx-deploy my-harbor/my-nginx-chart \
  --version 0.1.0 \  # 指定版本(可选,默认最新版)
  --namespace my-nginx

三、验证部署结果
无论通过哪种方式部署,使用者都可以通过以下命令验证:

# 查看已部署的 Release
helm list -n my-nginx

# 查看 Pod 状态(确保 Running)
kubectl get pods -n my-nginx

# 查看 Service 信息(获取访问地址)
kubectl get svc -n my-nginx

如果部署时包含 NOTES.txt, Helm 会自动输出访问指南,例如:
恭喜!my-nginx-chart 已成功部署到 my-nginx 命名空间。

访问方式:
curl http://<节点IP>:30080

四、总结
本地包使用:适合临时测试,直接用 helm install <包路径> 部署,无需解压;
仓库分发:适合团队协作,上传到仓库后,使用者通过仓库地址一键部署;
灵活性:使用者可通过 --set 或自定义 values.yaml 覆盖默认配置,无需修改你的原始包。
这种方式既简化了部署流程,又保证了配置的灵活性,是 Helm 包的标准使用方式。

(注:文档部分内容可能由 AI 生成)