在 Kubernetes 里,部署一个应用从来不只是“把镜像跑起来”这么简单。一个完整的服务通常还会涉及 Deployment、Service、Ingress、ConfigMap、Secret、HPA,甚至初始化 Job、发布前检查和安装后的数据迁移。随着环境增多、配置变复杂、发布频率提高,直接维护一堆 YAML 文件会迅速变成一件低效且脆弱的事情。
Helm 的价值,就在于把这些“可复用、可参数化、可发布”的 Kubernetes 资源组织起来,形成标准化的应用交付包。对 Go 应用尤其如此:Go 服务通常天然适合容器化,配合 Helm 可以很好地落地多环境部署、版本管理和自动化发布。
本文将围绕“云原生篇 3:Helm Chart 编写与管理”这个主题,系统讲清楚以下内容:
- Helm 核心概念:Chart、Release、Repository
- 从零编写一个 Go 应用的 Helm Chart
values.yaml设计与多环境覆盖- 模板语法:条件、循环、命名模板
- Helm Hooks 与生命周期管理
- Chart 测试与发布到 OCI Registry
全文示例均可直接运行或按需裁剪,适合作为团队内部 Helm Chart 编写规范的起点。
1. Helm 核心概念
在正式写 Chart 之前,先把 Helm 的三个核心概念厘清。
1.1 Chart 是什么
Chart 可以理解为一个 Kubernetes 应用的“安装包模板”。
它不是某一个固定环境下的最终 YAML,而是一组:
- 模板文件
- 默认配置
- 元数据
- 可选依赖项
组合在一起的发布单元。
一个 Chart 往往对应一个可部署应用,例如:
- 一个 Go API 服务
- 一个 MySQL 实例
- 一个完整的监控组件
典型目录结构如下:
my-go-app/
├── Chart.yaml
├── values.yaml
├── charts/
├── templates/
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── serviceaccount.yaml
│ └── tests/
│ └── connection-test.yaml
└── .helmignore
其中:
Chart.yaml:描述 Chart 的基本信息values.yaml:默认配置值templates/:Kubernetes 资源模板charts/:子 Chart 依赖_helpers.tpl:公共命名模板和辅助函数
1.2 Release 是什么
Release 是某个 Chart 在 Kubernetes 集群里的一次“已安装实例”。
同一个 Chart,可以安装出多个 Release。例如:
go-web-devgo-web-staginggo-web-prod
它们可以:
- 安装在不同命名空间
- 使用不同 values 配置
- 指向不同镜像版本
- 使用不同域名和资源配额
也就是说:Chart 是模板,Release 是实例。
例如下面这条命令:
helm install go-web-prod ./my-go-app -n prod
含义是:把本地 my-go-app 这个 Chart,安装成名为 go-web-prod 的 Release,并部署到 prod 命名空间。
1.3 Repository 是什么
Repository 是存放和分发 Chart 的仓库。
传统 Helm Repository 通过 index.yaml 管理 Chart 索引;而现在更常见的方式,是把 Chart 直接发布到 OCI Registry,例如:
- Harbor
- Docker Hub(支持 OCI)
- GitHub Container Registry
- 阿里云 ACR
- 企业内部制品库
OCI Registry 的好处是:
- 与镜像仓库统一管理
- 权限体系更一致
- 版本分发更标准
- 更适合 CI/CD 自动化
例如:
helm push my-go-app-0.1.0.tgz oci://registry.example.com/helm
2. 从零准备一个 Go 示例应用
为了让 Helm Chart 更贴近实际使用,我们先准备一个极简 Go Web 服务。
2.1 示例目录结构
go-web/
├── Dockerfile
├── go.mod
└── main.go
2.2 main.go
package main
import (
"encoding/json"
"log"
"net/http"
"os"
)
type Response struct {
Message string `json:"message"`
Version string `json:"version"`
Env string `json:"env"`
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(Response{
Message: "hello from go-web",
Version: getEnv("APP_VERSION", "v1.0.0"),
Env: getEnv("APP_ENV", "dev"),
})
})
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
addr := ":8080"
log.Printf("server listening on %s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Fatalf("server exited: %v", err)
}
}
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
2.3 go.mod
module example.com/go-web
go 1.22
2.4 Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o server .
FROM alpine:3.20
WORKDIR /app
COPY --from=builder /app/server /app/server
EXPOSE 8080
ENTRYPOINT ["/app/server"]
构建并推送镜像后,假设镜像地址为:
registry.example.com/demo/go-web:v1.0.0
3. 从零编写一个 Go 应用的 Helm Chart
接下来开始编写 Chart。
3.1 初始化 Chart
可以直接使用 Helm 命令创建骨架:
helm create my-go-app
不过在实际项目里,很多团队会删掉默认模板,改成自己的风格。本文直接给出一套更适合 Go Web 服务的结构。
3.2 Chart 目录结构
my-go-app/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-prod.yaml
├── .helmignore
└── templates/
├── _helpers.tpl
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── serviceaccount.yaml
├── configmap.yaml
├── hpa.yaml
├── hooks-migrate.yaml
└── tests/
└── connection-test.yaml
3.3 Chart.yaml
Chart.yaml 用来定义 Chart 元信息。
apiVersion: v2
name: my-go-app
description: A Helm chart for deploying a Go web application
type: application
version: 0.1.0
appVersion: "1.0.0"
home: https://example.com/my-go-app
sources:
- https://example.com/my-go-app.git
keywords:
- go
- kubernetes
- helm
maintainers:
- name: 杨晓明
这里需要注意两个版本:
version:Chart 自身版本appVersion:应用版本,通常对应镜像版本或业务版本
3.4 .helmignore
.helmignore 的作用类似 .gitignore,避免无关文件打进 Chart 包里。
.git/
.gitignore
Dockerfile
README.md
*.swp
*.tmp
3.5 values.yaml
这是默认值文件,也是整个 Chart 的配置入口。
replicaCount: 2
image:
repository: registry.example.com/demo/go-web
tag: v1.0.0
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
podLabels: {}
podSecurityContext: {}
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
service:
type: ClusterIP
port: 80
targetPort: 8080
containerPort: 8080
config:
APP_ENV: dev
APP_VERSION: v1.0.0
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: go-web.local
paths:
- path: /
pathType: Prefix
tls: []
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 5
targetCPUUtilizationPercentage: 80
extraEnv: []
nodeSelector: {}
tolerations: []
affinity: {}
hooks:
migrate:
enabled: false
image: registry.example.com/demo/db-migrate:v1.0.0
command:
- /bin/sh
- -c
- ./migrate.sh
4. values.yaml 设计与多环境覆盖
values.yaml 看起来只是一个配置文件,但它实际上决定了 Chart 的可维护性上限。
一个好的 values 设计,至少应该满足:
- 默认值清晰
- 结构稳定
- 便于多环境覆盖
- 便于 CI/CD 动态注入
- 尽量少把复杂逻辑写死在模板里
4.1 values 设计原则
建议遵循以下原则:
- 按关注点分组:如
image、service、ingress、resources - 默认值可本地运行:避免上来就一堆必填项
- 布尔开关显式化:如
ingress.enabled、autoscaling.enabled - 环境差异放 values 文件,不放模板判断
- 模板负责渲染,values 负责表达意图
4.2 开发环境覆盖文件
replicaCount: 1
image:
tag: dev-latest
config:
APP_ENV: dev
APP_VERSION: dev-latest
ingress:
enabled: true
hosts:
- host: go-web-dev.example.com
paths:
- path: /
pathType: Prefix
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
autoscaling:
enabled: false
文件名可保存为 values-dev.yaml。
4.3 生产环境覆盖文件
replicaCount: 3
image:
tag: v1.0.0
config:
APP_ENV: prod
APP_VERSION: v1.0.0
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 20m
hosts:
- host: go-web.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: go-web-tls
hosts:
- go-web.example.com
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 75
hooks:
migrate:
enabled: true
image: registry.example.com/demo/db-migrate:v1.0.0
文件名可保存为 values-prod.yaml。
4.4 多环境部署命令
开发环境:
helm upgrade --install go-web-dev ./my-go-app \
-n dev --create-namespace \
-f my-go-app/values.yaml \
-f my-go-app/values-dev.yaml
生产环境:
helm upgrade --install go-web-prod ./my-go-app \
-n prod --create-namespace \
-f my-go-app/values.yaml \
-f my-go-app/values-prod.yaml
如果需要在流水线中临时覆盖镜像 tag,还可以使用:
helm upgrade --install go-web-prod ./my-go-app \
-n prod \
-f my-go-app/values.yaml \
-f my-go-app/values-prod.yaml \
--set image.tag=v1.0.1
4.5 values 覆盖优先级
Helm 常见覆盖优先级从低到高为:
- Chart 内置
values.yaml -f values-xxx.yaml- 多个
-f按顺序后者覆盖前者 --set--set-string--set-file
因此 CI/CD 通常会采用:
- 仓库里维护基础
values.yaml - 不同环境维护独立 values 文件
- 流水线运行时用
--set覆盖镜像 tag、commit sha 等动态值
5. Helm 模板语法:条件、循环、命名模板
Helm 模板基于 Go Template,并额外集成了 Sprig 函数库。真正写 Chart 时,最常用的就是:
- 条件判断
- 循环渲染
- 命名模板
- 管道与函数
- 缩进控制
5.1 _helpers.tpl:命名模板
先定义公共命名模板,后面所有资源都可以复用。
{{- define "my-go-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "my-go-app.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name (include "my-go-app.name" .) | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "my-go-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" -}}
{{- end -}}
{{- define "my-go-app.labels" -}}
helm.sh/chart: {{ include "my-go-app.chart" . }}
app.kubernetes.io/name: {{ include "my-go-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{- define "my-go-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-go-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{- define "my-go-app.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "my-go-app.fullname" .) .Values.serviceAccount.name -}}
{{- else -}}
{{- default "default" .Values.serviceAccount.name -}}
{{- end -}}
{{- end -}}
这里的 define 就是在声明命名模板,后续通过 include 复用。这样做的好处是:
- 统一名称生成逻辑
- 统一 label 风格
- 降低模板重复度
- 便于团队标准化
5.2 Deployment 模板
下面是核心的 templates/deployment.yaml。
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-go-app.fullname" . }}
labels:
{{- include "my-go-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-go-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-go-app.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ include "my-go-app.serviceAccountName" . }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ include "my-go-app.name" . }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.containerPort }}
protocol: TCP
envFrom:
- configMapRef:
name: {{ include "my-go-app.fullname" . }}
{{- with .Values.extraEnv }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
这个模板里已经包含了几个非常典型的写法。
条件判断
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
如果开启了 HPA,就不再显式设置 Deployment 的 replicas。
with 作用域切换
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
with 的含义是:当值存在且非空时,进入该作用域,并把当前 . 切换为该对象。
toYaml 与 nindent
{{- toYaml .Values.resources | nindent 12 }}
这是 Helm 模板最常见的组合之一:
toYaml:把对象转为 YAMLnindent:换行并按指定空格数缩进
5.3 ConfigMap 模板
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "my-go-app.fullname" . }}
labels:
{{- include "my-go-app.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.config }}
{{ $key }}: {{ $value | quote }}
{{- end }}
这里用到了 range 循环,把 config 下的键值对全部渲染到 ConfigMap 中。
循环语法
{{- range $key, $value := .Values.config }}
{{ $key }}: {{ $value | quote }}
{{- end }}
这种方式非常适合:
- 环境变量
- 注解集合
- labels 集合
- hosts/path 列表
5.4 Service 模板
apiVersion: v1
kind: Service
metadata:
name: {{ include "my-go-app.fullname" . }}
labels:
{{- include "my-go-app.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "my-go-app.selectorLabels" . | nindent 4 }}
5.5 Ingress 模板
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-go-app.fullname" . }}
labels:
{{- include "my-go-app.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
ingressClassName: {{ .Values.ingress.className }}
{{- if .Values.ingress.tls }}
tls:
{{- toYaml .Values.ingress.tls | nindent 4 }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "my-go-app.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
这里有两个重点:
- 最外层
if控制是否生成 Ingress - 嵌套
range渲染多 host、多 path
同时这里还用到了 $:
.代表当前作用域$代表根作用域
因为进入 range 之后,当前 . 已经不再是全局上下文,因此访问全局对象时通常要用 $。
5.6 ServiceAccount 模板
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "my-go-app.serviceAccountName" . }}
labels:
{{- include "my-go-app.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
5.7 HPA 模板
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "my-go-app.fullname" . }}
labels:
{{- include "my-go-app.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "my-go-app.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
6. Helm Hooks 与生命周期管理
在很多真实场景中,应用发布并不只是“安装 Deployment”。例如:
- 发布前执行数据库迁移
- 安装后写入初始化数据
- 卸载前做清理
- 升级前做兼容检查
这类逻辑就适合用 Helm Hooks。
6.1 Helm Hooks 支持的常见阶段
Helm 常见 Hook 阶段包括:
pre-installpost-installpre-upgradepost-upgradepre-deletepost-deletepre-rollbackpost-rollbacktest
本质上,Hook 是通过资源注解来声明执行时机。
6.2 发布前执行数据库迁移
假设我们希望在安装或升级前执行一次迁移 Job,可以写一个 Hook 模板。
{{- if .Values.hooks.migrate.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-go-app.fullname" . }}-migrate-{{ .Release.Revision }}
labels:
{{- include "my-go-app.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 1
template:
metadata:
labels:
{{- include "my-go-app.selectorLabels" . | nindent 8 }}
spec:
restartPolicy: Never
containers:
- name: migrate
image: {{ .Values.hooks.migrate.image | quote }}
command:
{{- toYaml .Values.hooks.migrate.command | nindent 12 }}
{{- end }}
上面三个 Hook 注解非常关键:
helm.sh/hook:定义触发时机helm.sh/hook-weight:定义多个 Hook 的执行顺序,越小越先执行helm.sh/hook-delete-policy:定义 Hook 资源删除策略
6.3 生命周期管理建议
在使用 Hooks 时,建议把握几个原则:
- Hook 只做短任务:例如迁移、检查、初始化,不要跑长时间常驻任务
- Job 必须可重试且幂等:避免升级失败后状态不一致
- 要设置删除策略:否则 Job 资源会越积越多
- 不要把核心业务部署依赖写得过重:否则发布链路会变脆弱
- 把失败视为发布失败的一部分:Hook 失败不是“小问题”,而是发布失败
6.4 安装、升级、回滚、卸载常用命令
安装:
helm install go-web-dev ./my-go-app -n dev --create-namespace
升级:
helm upgrade go-web-dev ./my-go-app -n dev -f my-go-app/values-dev.yaml
回滚:
helm rollback go-web-dev 1 -n dev
卸载:
helm uninstall go-web-dev -n dev
查看 Release 历史:
helm history go-web-dev -n dev
7. Chart 测试:Lint、模板渲染、安装验证
Chart 不应该等到部署时才发现有问题。至少要建立三层检查:
- 语法和规范检查
- 模板渲染检查
- 安装后连通性检查
7.1 helm lint
helm lint 是最基础的一步。
helm lint ./my-go-app
如果要带某个环境配置一起检查:
helm lint ./my-go-app -f ./my-go-app/values-prod.yaml
7.2 helm template
helm template 会把 Chart 渲染成最终 YAML,但不实际安装到集群。
helm template go-web-dev ./my-go-app \
-n dev \
-f ./my-go-app/values.yaml \
-f ./my-go-app/values-dev.yaml
这一步非常适合在 CI 中:
- 检查 YAML 是否符合预期
- 对照 diff 审核资源变化
- 配合
kubectl apply --dry-run=client做进一步验证
7.3 dry-run 安装验证
helm upgrade --install go-web-dev ./my-go-app \
-n dev --create-namespace \
-f ./my-go-app/values-dev.yaml \
--dry-run --debug
--debug 会输出更多渲染上下文,排查模板问题时非常有帮助。
7.4 Helm Test
Helm 支持用 test Hook 编写安装后的验证任务。下面写一个最常见的服务连通性测试。
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-go-app.fullname" . }}-test-connection"
labels:
{{- include "my-go-app.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox:1.36
command:
- sh
- -c
- >-
wget -qO- http://{{ include "my-go-app.fullname" . }}:{{ .Values.service.port }}/healthz | grep ok
restartPolicy: Never
安装完成后,执行:
helm test go-web-dev -n dev
如果测试成功,说明至少服务在集群内可以被访问。
7.5 一个可直接执行的本地校验脚本
团队里通常会把校验动作固化成脚本,方便开发、测试、CI 统一使用。
#!/usr/bin/env bash
set -euo pipefail
CHART_DIR="./my-go-app"
helm lint "${CHART_DIR}"
helm lint "${CHART_DIR}" -f "${CHART_DIR}/values-dev.yaml"
helm lint "${CHART_DIR}" -f "${CHART_DIR}/values-prod.yaml"
helm template go-web-dev "${CHART_DIR}" \
-n dev \
-f "${CHART_DIR}/values.yaml" \
-f "${CHART_DIR}/values-dev.yaml" >/dev/null
helm template go-web-prod "${CHART_DIR}" \
-n prod \
-f "${CHART_DIR}/values.yaml" \
-f "${CHART_DIR}/values-prod.yaml" >/dev/null
echo "helm chart validation passed"
8. Chart 打包与发布到 OCI Registry
在团队协作中,Chart 一旦稳定下来,就应该像镜像一样被版本化和分发,而不是每次都依赖源码目录部署。
8.1 打包 Chart
helm package ./my-go-app
执行后会生成类似文件:
my-go-app-0.1.0.tgz
8.2 登录 OCI Registry
以企业镜像仓库为例:
helm registry login registry.example.com \
-u your-username \
-p your-password
更安全的方式是通过标准输入传密码:
export HELM_REGISTRY_PASSWORD="your-password"
printf '%s' "${HELM_REGISTRY_PASSWORD}" | helm registry login registry.example.com \
-u your-username \
--password-stdin
8.3 推送到 OCI Registry
helm push my-go-app-0.1.0.tgz oci://registry.example.com/helm
推送成功后,Chart 地址类似于:
oci://registry.example.com/helm/my-go-app
8.4 从 OCI Registry 拉取和安装
查看 Chart 信息:
helm show chart oci://registry.example.com/helm/my-go-app --version 0.1.0
安装指定版本:
helm install go-web-prod oci://registry.example.com/helm/my-go-app \
--version 0.1.0 \
-n prod --create-namespace \
-f ./my-go-app/values-prod.yaml
拉取到本地:
helm pull oci://registry.example.com/helm/my-go-app --version 0.1.0
8.5 CI/CD 发布思路
一个比较常见的自动化流程如下:
- 开发提交 Chart 变更
- CI 执行
helm lint - CI 执行
helm template - 如有测试集群,可执行试安装验证
- 使用
helm package打包 - 将
.tgz推送到 OCI Registry - 部署流水线按版本拉取并安装
例如在 CI 中:
#!/usr/bin/env bash
set -euo pipefail
CHART_DIR="./my-go-app"
CHART_VERSION="0.1.0"
REGISTRY="registry.example.com"
REPO="oci://${REGISTRY}/helm"
helm lint "${CHART_DIR}"
helm template go-web-ci "${CHART_DIR}" -f "${CHART_DIR}/values.yaml" >/dev/null
helm package "${CHART_DIR}" --version "${CHART_VERSION}"
printf '%s' "${HELM_REGISTRY_PASSWORD}" | helm registry login "${REGISTRY}" \
-u "${HELM_REGISTRY_USERNAME}" \
--password-stdin
helm push "my-go-app-${CHART_VERSION}.tgz" "${REPO}"
9. 一套完整的实战操作顺序
如果你希望把本文内容真正落地到项目里,可以按下面顺序执行:
第一步:构建并推送 Go 应用镜像
docker build -t registry.example.com/demo/go-web:v1.0.0 ./go-web
docker push registry.example.com/demo/go-web:v1.0.0
第二步:校验 Chart 模板
helm lint ./my-go-app
helm template go-web-dev ./my-go-app -f ./my-go-app/values-dev.yaml
第三步:安装到开发环境
helm upgrade --install go-web-dev ./my-go-app \
-n dev --create-namespace \
-f ./my-go-app/values.yaml \
-f ./my-go-app/values-dev.yaml
第四步:执行安装后测试
helm test go-web-dev -n dev
第五步:打包并发布 Chart
helm package ./my-go-app
helm push my-go-app-0.1.0.tgz oci://registry.example.com/helm
第六步:生产环境按版本安装
helm upgrade --install go-web-prod oci://registry.example.com/helm/my-go-app \
--version 0.1.0 \
-n prod --create-namespace \
-f ./my-go-app/values-prod.yaml
10. 结语
Helm 的核心价值,不在于“少写几份 YAML”,而在于把 Kubernetes 应用的部署方式真正工程化、标准化、可复用化。
对于 Go 应用来说,一个质量足够高的 Helm Chart,至少应该具备这些特征:
- Chart 元数据清晰,版本语义明确
values.yaml结构稳定,适合多环境覆盖- 模板复用合理,命名模板统一
- 条件和循环只服务于渲染,不把业务逻辑塞进模板
- Hook 用于补足生命周期动作,而不是制造发布复杂度
- 能被 lint、template、test、package、push 这条链路稳定消费
当团队把 Helm Chart 当作正式交付物来维护时,Kubernetes 发布流程才会真正变得可协作、可审查、可追踪、可自动化。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!