Skip to content
清晨的一缕阳光
返回

Helm Chart 打包

Helm Chart 打包

Helm 简介

核心概念

Chart

Release

Repository

Values

架构设计

┌─────────────────┐
│   Helm Client   │
└────────┬────────┘

         │ 安装/升级

┌─────────────────┐
│  Chart Repo     │
│  (Chart 存储)    │
└────────┬────────┘

         │ 拉取 Chart

┌─────────────────┐
│  Kubernetes     │
│  Cluster        │
│ ┌─────────────┐ │
│ │   Release   │ │
│ │  (部署实例)  │ │
│ └─────────────┘ │
└─────────────────┘

快速开始

1. 安装 Helm

Linux

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version

macOS

brew install helm
helm version

Windows

choco install kubernetes-helm
helm version

2. 常用命令

# 添加仓库
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami

# 搜索 Chart
helm search repo nginx
helm search hub wordpress

# 安装 Chart
helm install my-release bitnami/nginx

# 查看 Release
helm list
helm status my-release

# 升级 Release
helm upgrade my-release bitnami/nginx --set image.tag=1.21

# 回滚 Release
helm rollback my-release 1

# 卸载 Release
helm uninstall my-release

# 创建 Chart
helm create my-chart

# 打包 Chart
helm package my-chart

# 推送 Chart
helm push my-chart-1.0.0.tgz oci://registry.example.com/charts

创建 Chart

1. Chart 结构

my-chart/
├── Chart.yaml          # Chart 元数据
├── values.yaml         # 默认配置值
├── values-prod.yaml    # 生产环境配置
├── charts/             # 依赖的 Chart
│   └── redis-17.0.0.tgz
├── templates/          # K8s 模板
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── ingress.yaml
│   ├── _helpers.tpl    # 模板辅助函数
│   └── NOTES.txt       # 安装说明
└── .helmignore         # 忽略文件

2. Chart.yaml

apiVersion: v2
name: user-service
description: A Helm chart for User Service
type: application
version: 1.0.0
appVersion: "1.0.0"
keywords:
  - user-service
  - microservice
  - spring-boot
home: https://github.com/example/user-service
sources:
  - https://github.com/example/user-service
maintainers:
  - name: John Doe
    email: john@example.com
icon: https://example.com/icon.png
annotations:
  category: Infrastructure
  licenses: Apache-2.0

# Chart 依赖
dependencies:
  - name: mysql
    version: "9.0.0"
    repository: "https://charts.bitnami.com/bitnami"
    condition: mysql.enabled
  - name: redis
    version: "17.0.0"
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled

3. values.yaml

# 副本数
replicaCount: 3

# 镜像配置
image:
  repository: registry.example.com/user-service
  pullPolicy: IfNotPresent
  tag: "latest"

# 镜像拉取凭证
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

# 服务账号
serviceAccount:
  create: true
  annotations: {}
  name: ""

# Pod 注解
podAnnotations: {}

# Pod 安全上下文
podSecurityContext:
  fsGroup: 1000

# 容器安全上下文
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  capabilities:
    drop:
      - ALL

# Service 配置
service:
  type: ClusterIP
  port: 80
  targetPort: 8080

# Ingress 配置
ingress:
  enabled: false
  className: "nginx"
  annotations: {}
  hosts:
    - host: user-service.example.com
      paths:
        - path: /
          pathType: Prefix
  tls: []

# 资源配置
resources:
  limits:
    cpu: 1000m
    memory: 1Gi
  requests:
    cpu: 500m
    memory: 512Mi

# 自动扩缩容
autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
  targetMemoryUtilizationPercentage: 80

# 健康检查
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 60
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 5

# 环境变量
env:
  SPRING_PROFILES_ACTIVE: "prod"
  NACOS_SERVER_ADDR: "nacos:8848"

# 配置映射
configMap:
  enabled: true
  data:
    application.yml: |
      spring:
        datasource:
          url: jdbc:mysql://{{ .Release.Name }}-mysql:3306/user_db
        redis:
          host: {{ .Release.Name }}-redis-master
          port: 6379

# 密钥
secret:
  enabled: true
  data:
    DB_USERNAME: "app_user"
    DB_PASSWORD: "S3cr3tP@ssw0rd"

# 依赖组件
mysql:
  enabled: true
  auth:
    rootPassword: "root123"
    database: "user_db"
    username: "app_user"
    password: "app123"

redis:
  enabled: true
  auth:
    enabled: true
    password: "redis123"
  master:
    persistence:
      enabled: true
      size: 8Gi

# 监控
monitoring:
  enabled: true
  serviceMonitor:
    enabled: true
    interval: 15s

4. 模板文件

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "user-service.fullname" . }}
  labels:
    {{- include "user-service.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "user-service.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "user-service.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "user-service.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
          livenessProbe:
            {{- toYaml .Values.livenessProbe | nindent 12 }}
          readinessProbe:
            {{- toYaml .Values.readinessProbe | nindent 12 }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          env:
            {{- range $key, $value := .Values.env }}
            - name: {{ $key }}
              value: {{ $value | quote }}
            {{- end }}
          envFrom:
            - configMapRef:
                name: {{ include "user-service.fullname" . }}-config
            - secretRef:
                name: {{ include "user-service.fullname" . }}-secret
          volumeMounts:
            - name: config
              mountPath: /app/config
      volumes:
        - name: config
          configMap:
            name: {{ include "user-service.fullname" . }}-config
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: {{ include "user-service.fullname" . }}
  labels:
    {{- include "user-service.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
      protocol: TCP
      name: http
  selector:
    {{- include "user-service.selectorLabels" . | nindent 4 }}

configmap.yaml

{{- if .Values.configMap.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "user-service.fullname" . }}-config
  labels:
    {{- include "user-service.labels" . | nindent 4 }}
data:
  {{- toYaml .Values.configMap.data | nindent 2 }}
{{- end }}

secret.yaml

{{- if .Values.secret.enabled }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ include "user-service.fullname" . }}-secret
  labels:
    {{- include "user-service.labels" . | nindent 4 }}
type: Opaque
stringData:
  {{- toYaml .Values.secret.data | nindent 2 }}
{{- end }}

ingress.yaml

{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "user-service.fullname" . }}
  labels:
    {{- include "user-service.labels" . | nindent 4 }}
  {{- with .Values.ingress.annotations }}
  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 }}
            backend:
              service:
                name: {{ include "user-service.fullname" $ }}
                port:
                  number: {{ $.Values.service.port }}
          {{- end }}
    {{- end }}
{{- end }}

_helpers.tpl

{{/*
Expand the name of the chart.
*/}}
{{- define "user-service.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "user-service.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 }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "user-service.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "user-service.labels" -}}
helm.sh/chart: {{ include "user-service.chart" . }}
{{ include "user-service.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "user-service.selectorLabels" -}}
app.kubernetes.io/name: {{ include "user-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "user-service.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "user-service.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

NOTES.txt

Thank you for installing {{ .Chart.Name }}.

Your release is named {{ .Release.Name }}.

To learn more about the release, try:

  $ helm status {{ .Release.Name }}
  $ helm get all {{ .Release.Name }}

The service is available at:

{{- 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 "user-service.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 }}
  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "user-service.fullname" . }}'
  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "user-service.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
  echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "user-service.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

多环境配置

1. values-prod.yaml

# 生产环境配置
replicaCount: 5

image:
  tag: "1.0.0"

resources:
  limits:
    cpu: 2000m
    memory: 2Gi
  requests:
    cpu: 1000m
    memory: 1Gi

autoscaling:
  enabled: true
  minReplicas: 5
  maxReplicas: 20

ingress:
  enabled: true
  className: "nginx"
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  hosts:
    - host: user-service.prod.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: user-service-tls
      hosts:
        - user-service.prod.example.com

mysql:
  auth:
    rootPassword: "Pr0dR00tP@ssw0rd!"
    password: "Pr0dUs3rP@ssw0rd!"
  primary:
    persistence:
      size: 50Gi
    resources:
      limits:
        cpu: 2000m
        memory: 4Gi

redis:
  auth:
    password: "Pr0dR3d1sP@ssw0rd!"
  master:
    persistence:
      size: 20Gi

2. values-dev.yaml

# 开发环境配置
replicaCount: 1

image:
  tag: "latest"
  pullPolicy: Always

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi

env:
  SPRING_PROFILES_ACTIVE: "dev"
  DEBUG_MODE: "true"

mysql:
  auth:
    rootPassword: "dev123"
    password: "dev123"

redis:
  auth:
    password: "dev123"

3. 环境部署

# 部署开发环境
helm install user-service ./user-service \
  --namespace dev \
  --create-namespace \
  -f values-dev.yaml

# 部署生产环境
helm install user-service ./user-service \
  --namespace prod \
  --create-namespace \
  -f values-prod.yaml

# 升级生产环境
helm upgrade user-service ./user-service \
  --namespace prod \
  -f values-prod.yaml \
  --set image.tag=1.0.1

# 回滚
helm rollback user-service 1 --namespace prod

Chart 仓库

1. 本地仓库

# 打包 Chart
helm package user-service

# 创建索引
helm repo index . --url https://example.com/charts

# 添加本地仓库
helm repo add my-repo ./charts

2. OCI 仓库

# 登录 OCI 仓库
helm registry login registry.example.com

# 推送 Chart
helm push user-service-1.0.0.tgz oci://registry.example.com/charts

# 拉取 Chart
helm pull oci://registry.example.com/charts/user-service --version 1.0.0

3. GitHub Pages

# 创建 gh-pages 分支
git checkout --orphan gh-pages
git reset --hard

# 打包 Chart
helm package user-service

# 创建索引
helm repo index . --url https://username.github.io/repo

# 推送
git add .
git commit -m "Add Chart"
git push origin gh-pages

最佳实践

1. 版本管理

# Chart.yaml
version: 1.0.0  # Chart 版本
appVersion: "1.0.0"  # 应用版本

# 使用语义化版本
# MAJOR.MINOR.PATCH
# 1.0.0 -> 1.1.0 (新功能)
# 1.1.0 -> 2.0.0 (破坏性变更)

2. 依赖管理

# Chart.yaml
dependencies:
  - name: mysql
    version: "~9.0.0"  # 兼容 9.0.x
    repository: "https://charts.bitnami.com/bitnami"
    condition: mysql.enabled
  
  - name: redis
    version: ">=17.0.0 <18.0.0"  # 版本范围
    repository: "https://charts.bitnami.com/bitnami"

# 更新依赖
helm dependency update
helm dependency build

3. 模板测试

# 渲染模板
helm template my-release ./user-service

# 调试
helm install my-release ./user-service --debug --dry-run

# 测试
helm test my-release

4. 安全配置

# values.yaml
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  fsGroup: 1000
  capabilities:
    drop:
      - ALL

# 使用 Secret 存储敏感信息
secret:
  data:
    DB_PASSWORD: "{{ .Values.dbPassword | b64enc }}"

5. 文档说明

# user-service

## 安装

```bash
helm install user-service ./user-service

配置

参数说明默认值
replicaCount副本数3
image.tag镜像标签latest
resources资源配置{}

依赖


## 总结

Helm 是 Kubernetes 的包管理工具,通过 Chart 简化复杂的 K8s 部署配置管理。

使用 Helm 可以实现配置参数化、多环境管理、版本控制和依赖管理。

在生产环境中,建议建立 Chart 仓库,实施版本管理,并做好安全配置和文档说明。

分享这篇文章到:

上一篇文章
Java 枚举详解
下一篇文章
MySQL 分页优化方案