Skip to content

Helm Charts

概述

Helm是Kubernetes的包管理器,被称为"Kubernetes的apt/yum"。它将Kubernetes资源打包成Charts,实现应用的版本化管理、分发和部署。Helm简化了Kubernetes应用的部署流程,提供了模板化、参数化配置能力,是云原生应用交付的事实标准工具。

核心概念

1. Chart

Chart是Helm的打包格式,包含一组Kubernetes资源定义文件:

  • 类似于Homebrew的formula、apt的dpkg
  • 包含应用的所有Kubernetes资源定义
  • 支持参数化配置和模板化

2. Release

Release是Chart的运行实例:

  • 每次安装Chart都会创建一个Release
  • 同一个Chart可以安装多次,每次都是独立的Release
  • Release有自己的名称和版本历史

3. Repository

Repository是存储和共享Charts的仓库:

  • 类似于Docker Hub、PyPI
  • 支持公共仓库和私有仓库
  • 可以添加多个仓库源

4. Values

Values是Chart的配置参数:

  • 通过values.yaml文件定义默认值
  • 安装时可以覆盖默认值
  • 支持多层配置合并

Helm架构

┌─────────────────────────────────────────────────────────────┐
│                      Helm Client                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │ 本地Charts   │  │ Values配置    │  │ Release管理  │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└─────────────────────────────────────────────────────────────┘
         ↓                    ↓                    ↓
┌─────────────┐    ┌──────────────┐    ┌──────────────────┐
│ Chart       │    │ Kubernetes   │    │ Repository       │
│ Repository  │    │ API Server   │    │ (远程仓库)        │
└─────────────┘    └──────────────┘    └──────────────────┘
         ↓                    ↓                    ↓
┌─────────────┐    ┌──────────────┐    ┌──────────────────┐
│ Chart存储    │    │ Secret存储    │    │ Artifact Hub     │
│ (本地/远程)  │    │ (Release状态) │    │ (公共仓库)        │
└─────────────┘    └──────────────┘    └──────────────────┘

Helm安装与配置

安装Helm

Linux/macOS安装

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

brew install helm

Windows安装

powershell
choco install kubernetes-helm

scoop install helm

验证安装

bash
helm version

helm version --short

配置Helm仓库

添加常用仓库

bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add stable https://charts.helm.sh/stable
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add jetstack https://charts.jetstack.io

helm repo update

helm repo list

搜索Charts

bash
helm search repo nginx

helm search repo nginx --versions

helm search hub wordpress

helm show chart bitnami/nginx

helm show readme bitnami/nginx

helm show values bitnami/nginx

helm show all bitnami/nginx

Chart结构

标准Chart目录结构

mychart/
├── Chart.yaml              # Chart元数据
├── values.yaml             # 默认配置值
├── charts/                 # 依赖的Charts
├── templates/              # 模板文件目录
│   ├── NOTES.txt          # 安装后的说明信息
│   ├── deployment.yaml    # Deployment资源
│   ├── service.yaml       # Service资源
│   ├── ingress.yaml       # Ingress资源
│   ├── configmap.yaml     # ConfigMap资源
│   ├── secret.yaml        # Secret资源
│   ├── _helpers.tpl       # 模板助手函数
│   └── tests/             # 测试文件
│       └── test-connection.yaml
├── templates/tests/        # 测试Pod定义
├── .helmignore            # 打包时忽略的文件
├── LICENSE                # 许可证
└── README.md              # 说明文档

Chart.yaml详解

yaml
apiVersion: v2
name: mychart
description: A Helm chart for Kubernetes
type: application
version: 1.0.0
appVersion: "1.16.0"
kubeVersion: ">=1.20.0-0"
keywords:
  - nginx
  - web
  - http
home: https://example.com
sources:
  - https://github.com/example/mychart
maintainers:
  - name: John Doe
    email: john@example.com
    url: https://example.com
icon: https://example.com/icon.png
deprecated: false
annotations:
  artifacthub.io/license: Apache-2.0
  artifacthub.io/signKey: |
    fingerprint: "C874011F0AB405110D02105534365D9472D7468F"
    url: https://key.url
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled
    tags:
      - database
    alias: postgres

values.yaml详解

yaml
replicaCount: 3

image:
  repository: nginx
  tag: "1.21"
  pullPolicy: IfNotPresent

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  annotations: {}
  name: ""

podAnnotations: {}

podSecurityContext:
  fsGroup: 1000

securityContext:
  runAsNonRoot: true
  runAsUser: 1000

service:
  type: ClusterIP
  port: 80
  targetPort: 80
  nodePort: ""

ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls: []

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

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

nodeSelector: {}

tolerations: []

affinity: {}

config:
  data: {}

secrets:
  data: {}

模板开发

基础模板语法

Deployment模板示例

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "mychart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "mychart.selectorLabels" . | nindent 8 }}
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "mychart.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.livenessProbe.enabled }}
          livenessProbe:
            httpGet:
              path: {{ .Values.livenessProbe.path }}
              port: http
            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
          {{- end }}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            httpGet:
              path: {{ .Values.readinessProbe.path }}
              port: http
            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          {{- with .Values.volumeMounts }}
          volumeMounts:
            {{- toYaml . | nindent 12 }}
          {{- end }}
      {{- with .Values.volumes }}
      volumes:
        {{- 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 }}

_helpers.tpl助手函数

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

{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}

{{- define "mychart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end -}}

{{- define "mychart.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 -}}

{{- define "mychart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end -}}

{{- define "mychart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "mychart.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end -}}

条件判断与循环

条件判断

yaml
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "mychart.fullname" . }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- if .Values.ingress.className }}
  ingressClassName: {{ .Values.ingress.className }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType }}
            backend:
              service:
                name: {{ include "mychart.fullname" $ }}
                port:
                  number: {{ $.Values.service.port }}
          {{- end }}
    {{- end }}
  {{- if .Values.ingress.tls }}
  tls:
    {{- range .Values.ingress.tls }}
    - hosts:
        {{- range .hosts }}
        - {{ . | quote }}
        {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
  {{- end }}
{{- end }}

循环遍历

yaml
{{- range $key, $value := .Values.config.data }}
{{ $key }}: {{ $value | quote }}
{{- end }}

{{- range .Values.hosts }}
- {{ . }}
{{- end }}

{{- range $index, $host := .Values.hosts }}
- index: {{ $index }}
  host: {{ $host }}
{{- end }}

Helm操作命令

Release管理

安装Chart

bash
helm install myrelease bitnami/nginx

helm install myrelease bitnami/nginx --namespace web --create-namespace

helm install myrelease bitnami/nginx -f values.yaml

helm install myrelease bitnami/nginx --set replicaCount=3

helm install myrelease bitnami/nginx --set image.tag=1.22

helm install myrelease bitnami/nginx --set service.type=NodePort

helm install myrelease bitnami/nginx --set service.nodePort=30080

helm install myrelease ./mychart

helm install myrelease ./mychart --dry-run --debug

helm install myrelease ./mychart --generate-name

helm install myrelease ./mychart --timeout 10m

helm install myrelease ./mychart --wait

helm install myrelease ./mychart --wait-for-jobs

升级Release

bash
helm upgrade myrelease bitnami/nginx

helm upgrade myrelease bitnami/nginx --set replicaCount=5

helm upgrade myrelease bitnami/nginx -f new-values.yaml

helm upgrade myrelease bitnami/nginx --reuse-values

helm upgrade myrelease bitnami/nginx --reset-values

helm upgrade myrelease bitnami/nginx --force

helm upgrade myrelease bitnami/nginx --atomic

helm upgrade --install myrelease bitnami/nginx

helm upgrade myrelease bitnami/nginx --version 15.0.0

回滚Release

bash
helm rollback myrelease

helm rollback myrelease 2

helm rollback myrelease 2 --dry-run

helm rollback myrelease 2 --force

卸载Release

bash
helm uninstall myrelease

helm uninstall myrelease --namespace web

helm uninstall myrelease --keep-history

helm uninstall myrelease --dry-run

查看Release

bash
helm list

helm list --all-namespaces

helm list -n web

helm list --all

helm list --filter 'nginx'

helm list --output json

helm list --output yaml

helm status myrelease

helm status myrelease -n web

helm status myrelease --show-desc

helm history myrelease

helm history myrelease --max 10

helm get values myrelease

helm get values myrelease --revision 2

helm get values myrelease --all

helm get manifest myrelease

helm get manifest myrelease --revision 2

helm get notes myrelease

helm get hooks myrelease

helm get all myrelease

Chart管理

创建Chart

bash
helm create mychart

helm create mychart --starter my-starter

验证Chart

bash
helm lint mychart

helm lint mychart --strict

helm lint mychart -f values.yaml

helm template myrelease mychart

helm template myrelease mychart --debug

helm template myrelease mychart -f values.yaml

helm template myrelease mychart --set replicaCount=3

helm template myrelease mychart --show-only templates/deployment.yaml

helm template myrelease mychart --output-dir ./output

打包Chart

bash
helm package mychart

helm package mychart --version 1.2.3

helm package mychart --app-version 2.0.0

helm package mychart --destination ./charts

helm package mychart --sign --key 'John Doe' --keyring ~/.gnupg/pubring.gpg

helm package mychart --dependency-update

依赖管理

bash
helm dependency list mychart

helm dependency update mychart

helm dependency build mychart

helm dependency update mychart --skip-refresh

仓库管理

bash
helm repo add myrepo https://charts.example.com

helm repo add myrepo https://charts.example.com --username user --password pass

helm repo update

helm repo remove myrepo

helm repo index ./charts

helm repo index ./charts --url https://charts.example.com

helm push mychart-1.0.0.tgz myrepo

helm pull bitnami/nginx

helm pull bitnami/nginx --version 15.0.0

helm pull bitnami/nginx --destination ./charts

helm pull bitnami/nginx --untar

helm pull bitnami/nginx --untardir ./extracted

实践示例

示例1:完整的Web应用Chart

创建Chart结构

bash
helm create webapp

Chart.yaml

yaml
apiVersion: v2
name: webapp
description: A production-ready web application Helm chart
type: application
version: 1.0.0
appVersion: "2.0.0"
kubeVersion: ">=1.20.0-0"
keywords:
  - web
  - nginx
  - frontend
home: https://github.com/example/webapp
sources:
  - https://github.com/example/webapp
maintainers:
  - name: DevOps Team
    email: devops@example.com
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled
  - name: redis
    version: "17.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled

values.yaml

yaml
global:
  imageRegistry: ""
  imagePullSecrets: []
  storageClass: ""

replicaCount: 3

image:
  repository: nginx
  tag: "1.25"
  pullPolicy: IfNotPresent

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  annotations: {}
  name: ""

podAnnotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "9113"

podSecurityContext:
  runAsNonRoot: true
  runAsUser: 101
  fsGroup: 101

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
  readOnlyRootFilesystem: true

service:
  type: ClusterIP
  port: 80
  targetPort: 8080
  annotations: {}

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  hosts:
    - host: webapp.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: webapp-tls
      hosts:
        - webapp.example.com

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

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
  targetMemoryUtilizationPercentage: 80

livenessProbe:
  enabled: true
  path: /health
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

readinessProbe:
  enabled: true
  path: /ready
  initialDelaySeconds: 5
  periodSeconds: 5
  timeoutSeconds: 3
  failureThreshold: 3

volumes:
  - name: cache
    emptyDir: {}
  - name: config
    configMap:
      name: webapp-config

volumeMounts:
  - name: cache
    mountPath: /var/cache/nginx
  - name: config
    mountPath: /etc/nginx/conf.d
    readOnly: true

nodeSelector: {}

tolerations: []

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values:
                  - webapp
          topologyKey: kubernetes.io/hostname

config:
  nginx: |
    upstream backend {
        server backend-service:8080;
    }
    server {
        listen 8080;
        location / {
            proxy_pass http://backend;
        }
    }

postgresql:
  enabled: true
  auth:
    postgresPassword: "change-me"
    database: webapp

redis:
  enabled: true
  auth:
    password: "change-me"

templates/deployment.yaml

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "webapp.fullname" . }}
  labels:
    {{- include "webapp.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "webapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        {{- with .Values.podAnnotations }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
      labels:
        {{- include "webapp.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "webapp.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.livenessProbe.enabled }}
          livenessProbe:
            httpGet:
              path: {{ .Values.livenessProbe.path }}
              port: http
            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            httpGet:
              path: {{ .Values.readinessProbe.path }}
              port: http
            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          volumeMounts:
            {{- toYaml .Values.volumeMounts | nindent 12 }}
      volumes:
        {{- toYaml .Values.volumes | nindent 8 }}
      {{- 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/configmap.yaml

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "webapp.fullname" . }}-config
  labels:
    {{- include "webapp.labels" . | nindent 4 }}
data:
  nginx.conf: |
    {{- .Values.config.nginx | nindent 4 }}

部署命令

bash
helm dependency update ./webapp

helm install webapp ./webapp -n production --create-namespace

helm install webapp ./webapp -n production -f custom-values.yaml

helm upgrade webapp ./webapp -n production --reuse-values --set image.tag=2.1.0

helm rollback webapp -n production

helm uninstall webapp -n production

示例2:多环境配置管理

目录结构

webapp/
├── Chart.yaml
├── values.yaml
├── values/
│   ├── dev.yaml
│   ├── staging.yaml
│   └── production.yaml
├── templates/
│   └── ...
└── charts/

values/dev.yaml

yaml
replicaCount: 1

image:
  tag: "latest"

ingress:
  enabled: true
  hosts:
    - host: webapp.dev.example.com
      paths:
        - path: /
          pathType: Prefix

resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

autoscaling:
  enabled: false

postgresql:
  enabled: true
  auth:
    postgresPassword: "dev-password"

redis:
  enabled: true
  auth:
    password: "dev-password"

values/staging.yaml

yaml
replicaCount: 2

image:
  tag: "v2.0.0"

ingress:
  enabled: true
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
  hosts:
    - host: webapp.staging.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: webapp-staging-tls
      hosts:
        - webapp.staging.example.com

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

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 5

postgresql:
  enabled: true
  auth:
    postgresPassword: "staging-password"

redis:
  enabled: true
  auth:
    password: "staging-password"

values/production.yaml

yaml
replicaCount: 3

image:
  tag: "v2.0.0"

ingress:
  enabled: true
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
  hosts:
    - host: webapp.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: webapp-production-tls
      hosts:
        - webapp.example.com

resources:
  limits:
    cpu: 2000m
    memory: 1Gi
  requests:
    cpu: 500m
    memory: 512Mi

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20
  targetCPUUtilizationPercentage: 60
  targetMemoryUtilizationPercentage: 70

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
            - key: app.kubernetes.io/name
              operator: In
              values:
                - webapp
        topologyKey: kubernetes.io/hostname
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: node-role.kubernetes.io/worker
              operator: Exists

postgresql:
  enabled: false

redis:
  enabled: false

部署脚本

bash
helm upgrade --install webapp ./webapp \
  -n dev \
  --create-namespace \
  -f values/dev.yaml

helm upgrade --install webapp ./webapp \
  -n staging \
  --create-namespace \
  -f values/staging.yaml

helm upgrade --install webapp ./webapp \
  -n production \
  --create-namespace \
  -f values/production.yaml \
  --atomic \
  --timeout 10m

示例3:Chart依赖管理

主Chart配置

yaml
apiVersion: v2
name: fullstack-app
description: Full stack application with frontend, backend, and database
version: 1.0.0
appVersion: "3.0.0"
dependencies:
  - name: frontend
    version: "1.x.x"
    repository: file://charts/frontend
    condition: frontend.enabled
    tags:
      - frontend
    
  - name: backend
    version: "1.x.x"
    repository: file://charts/backend
    condition: backend.enabled
    tags:
      - backend
    
  - name: postgresql
    version: "12.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled
    alias: database
    tags:
      - database
    
  - name: redis
    version: "17.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled
    tags:
      - cache
    
  - name: elasticsearch
    version: "19.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: elasticsearch.enabled
    tags:
      - search

values.yaml

yaml
global:
  environment: production
  imageRegistry: registry.example.com
  imagePullSecrets:
    - name: registry-secret

frontend:
  enabled: true
  replicaCount: 3
  image:
    repository: frontend
    tag: "v2.0.0"
  ingress:
    enabled: true
    hosts:
      - host: app.example.com

backend:
  enabled: true
  replicaCount: 5
  image:
    repository: backend
    tag: "v2.0.0"
  config:
    database:
      host: "{{ .Release.Name }}-database"
      port: 5432
    redis:
      host: "{{ .Release.Name }}-redis-master"
      port: 6379

postgresql:
  enabled: true
  auth:
    postgresPassword: "change-me"
    database: appdb
  primary:
    persistence:
      size: 50Gi
      storageClass: fast-ssd

redis:
  enabled: true
  auth:
    password: "change-me"
  master:
    persistence:
      size: 10Gi

elasticsearch:
  enabled: false

部署命令

bash
helm dependency update ./fullstack-app

helm dependency build ./fullstack-app

helm install myapp ./fullstack-app \
  --set global.environment=production \
  --set frontend.replicaCount=5 \
  --set backend.replicaCount=10

helm install myapp ./fullstack-app \
  --set frontend.enabled=true \
  --set backend.enabled=true \
  --set postgresql.enabled=true \
  --set redis.enabled=true \
  --set elasticsearch.enabled=false

helm install myapp ./fullstack-app --tags frontend,backend

helm install myapp ./fullstack-app --tags database

Chart仓库管理

搭建私有仓库

使用GitHub Pages

bash
mkdir -p docs
helm package mychart --destination docs/
helm repo index docs/ --url https://username.github.io/charts

git add docs/
git commit -m "Add chart package"
git push origin main

使用对象存储(S3)

bash
helm plugin install https://github.com/hypnoglow/helm-s3.git

helm s3 init s3://my-bucket/charts

helm repo add my-s3-repo s3://my-bucket/charts

helm s3 push mychart-1.0.0.tgz my-s3-repo

使用Harbor

bash
helm repo add harbor https://harbor.example.com/chartrepo/library

helm push mychart-1.0.0.tgz harbor

使用ChartMuseum

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: chartmuseum
  namespace: charts
spec:
  replicas: 1
  selector:
    matchLabels:
      app: chartmuseum
  template:
    metadata:
      labels:
        app: chartmuseum
    spec:
      containers:
      - name: chartmuseum
        image: ghcr.io/helm/chartmuseum:v0.15.0
        args:
        - --port=8080
        - --storage=local
        - --storage-local-rootdir=/charts
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: charts
          mountPath: /charts
      volumes:
      - name: charts
        persistentVolumeClaim:
          claimName: chartmuseum-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: chartmuseum
  namespace: charts
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: chartmuseum
bash
helm repo add myrepo http://chartmuseum.example.com

curl -L http://chartmuseum.example.com/api/charts --data-binary @mychart-1.0.0.tgz

helm push mychart-1.0.0.tgz myrepo

版本控制与CI/CD

Chart版本管理

语义化版本

yaml
version: 1.2.3

MAJOR.MINOR.PATCH

MAJOR: 不兼容的API变更
MINOR: 向后兼容的功能新增
PATCH: 向后兼容的问题修复

版本更新策略

bash
helm package mychart --version 1.2.0

helm upgrade myrelease myrepo/mychart --version 1.2.0

helm search repo mychart --versions

helm search repo mychart --version ">=1.0.0,<2.0.0"

GitOps集成

ArgoCD集成

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: webapp
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example/webapp-chart.git
    targetRevision: HEAD
    path: charts/webapp
    helm:
      valueFiles:
        - values/production.yaml
      parameters:
        - name: replicaCount
          value: "5"
        - name: image.tag
          value: "v2.1.0"
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Flux集成

yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 1m
  url: https://charts.example.com
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: webapp
  namespace: production
spec:
  interval: 5m
  chart:
    spec:
      chart: webapp
      version: ">=1.0.0"
      sourceRef:
        kind: HelmRepository
        name: webapp
        namespace: flux-system
  values:
    replicaCount: 3
    image:
      tag: v2.0.0
    ingress:
      enabled: true
      hosts:
        - host: webapp.example.com
          paths:
            - path: /
              pathType: Prefix

CI/CD流水线示例

GitLab CI

yaml
stages:
  - lint
  - test
  - build
  - deploy

lint:
  stage: lint
  image: alpine/helm:3.12.0
  script:
    - helm lint charts/webapp
    - helm template webapp charts/webapp > /dev/null

test:
  stage: test
  image: alpine/helm:3.12.0
  script:
    - helm plugin install https://github.com/quintush/helm-unittest
    - helm unittest charts/webapp

build:
  stage: build
  image: alpine/helm:3.12.0
  script:
    - helm package charts/webapp --version ${CI_COMMIT_TAG}
    - helm repo index ./ --url https://charts.example.com
  artifacts:
    paths:
      - "*.tgz"
      - index.yaml
  only:
    - tags

deploy:
  stage: deploy
  image: alpine/helm:3.12.0
  script:
    - helm upgrade --install webapp charts/webapp
      --namespace production
      --values values/production.yaml
      --set image.tag=${CI_COMMIT_TAG}
      --atomic
      --timeout 10m
  only:
    - tags
  when: manual

GitHub Actions

yaml
name: Helm CI/CD

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Helm
        uses: azure/setup-helm@v3
        with:
          version: v3.12.0
      
      - name: Lint Chart
        run: |
          helm lint charts/webapp
          helm template webapp charts/webapp > /dev/null

  test:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Helm
        uses: azure/setup-helm@v3
      
      - name: Install unittest plugin
        run: helm plugin install https://github.com/quintush/helm-unittest
      
      - name: Run tests
        run: helm unittest charts/webapp

  publish:
    runs-on: ubuntu-latest
    needs: test
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Helm
        uses: azure/setup-helm@v3
      
      - name: Package Chart
        run: |
          helm package charts/webapp --version ${GITHUB_REF#refs/tags/}
          helm repo index ./ --url https://charts.example.com
      
      - name: Push to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./

  deploy:
    runs-on: ubuntu-latest
    needs: publish
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Helm
        uses: azure/setup-helm@v3
      
      - name: Set up kubectl
        uses: azure/setup-kubectl@v3
      
      - name: Configure kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
      
      - name: Deploy to Production
        run: |
          helm upgrade --install webapp charts/webapp \
            --namespace production \
            --values values/production.yaml \
            --set image.tag=${GITHUB_REF#refs/tags/} \
            --atomic \
            --timeout 10m

故障排查指南

问题1:Chart安装失败

症状

bash
Error: INSTALLATION FAILED: unable to build kubernetes objects from release manifest

排查步骤

bash
helm template myrelease ./mychart --debug

helm template myrelease ./mychart --debug 2>&1 | grep -A 10 "Error"

helm install myrelease ./mychart --dry-run --debug

kubectl apply -f --dry-run=client -o yaml - < <(helm template myrelease ./mychart)

解决方案

  • 检查模板语法错误
  • 验证Kubernetes资源定义
  • 检查values参数类型
  • 确认API版本兼容性

问题2:Release状态异常

症状

bash
helm list
NAME      STATUS
myrelease  pending-install

排查步骤

bash
helm status myrelease

kubectl get all -l app.kubernetes.io/instance=myrelease

kubectl describe pods -l app.kubernetes.io/instance=myrelease

kubectl logs -l app.kubernetes.io/instance=myrelease --all-containers

kubectl get secret -l owner=helm,name=myrelease

解决方案

bash
helm rollback myrelease

helm uninstall myrelease

kubectl delete secret sh.helm.release.v1.myrelease.v1

helm install myrelease ./mychart --force

问题3:依赖更新失败

症状

bash
Error: no cached repository for helm-manager found. (try 'helm repo update')

排查步骤

bash
helm repo list

helm repo update

helm dependency list ./mychart

ls -la ./mychart/charts/

cat ./mychart/Chart.lock

解决方案

bash
helm repo add bitnami https://charts.bitnami.com/bitnami

helm repo update

helm dependency update ./mychart

helm dependency build ./mychart

问题4:模板渲染错误

症状

bash
Error: INSTALLATION FAILED: render error in "webapp/templates/deployment.yaml": template: webapp/templates/deployment.yaml:15:20: executing "webapp/templates/deployment.yaml" at <.Values.replicaCount>: can't evaluate field replicaCount in type interface {}

排查步骤

bash
helm template myrelease ./mychart --debug

helm get values myrelease --all

helm template myrelease ./mychart -f values.yaml --debug

helm lint ./mychart --strict

解决方案

  • 检查values.yaml缩进
  • 验证模板变量路径
  • 确认条件判断语法
  • 使用default函数提供默认值

问题5:升级回滚失败

症状

bash
Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress

排查步骤

bash
helm history myrelease

helm status myrelease

kubectl get secret -l owner=helm,name=myrelease

kubectl get configmap -l owner=helm,name=myrelease

解决方案

bash
helm rollback myrelease

helm rollback myrelease --force

kubectl delete secret sh.helm.release.v1.myrelease.v<version>

helm upgrade myrelease ./mychart --force

最佳实践

1. Chart设计最佳实践

命名规范

yaml
name: myapp
fullname: "{{ .Release.Name }}-{{ .Chart.Name }}"

labels:
  app.kubernetes.io/name: {{ include "myapp.name" . }}
  app.kubernetes.io/instance: {{ .Release.Name }}
  app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
  app.kubernetes.io/component: frontend
  app.kubernetes.io/part-of: myapp
  app.kubernetes.io/managed-by: {{ .Release.Service }}

资源组织

yaml
templates/
├── deployment.yaml
├── service.yaml
├── configmap.yaml
├── secret.yaml
├── ingress.yaml
├── hpa.yaml
├── serviceaccount.yaml
├── _helpers.tpl
└── tests/
    └── test-connection.yaml

2. Values设计最佳实践

分层配置

yaml
global:
  imageRegistry: ""
  imagePullSecrets: []

image:
  repository: nginx
  tag: "1.25"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  hosts: []

resources:
  limits: {}
  requests: {}

使用条件开关

yaml
ingress:
  enabled: false

autoscaling:
  enabled: false

serviceAccount:
  create: true

{{- if .Values.ingress.enabled -}}
{{- end -}}

{{- if .Values.autoscaling.enabled -}}
{{- end -}}

3. 模板开发最佳实践

使用助手函数

yaml
{{- define "myapp.labels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}

{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
{{- end -}}

使用命名模板

yaml
{{- define "myapp.imagePullSecrets" -}}
{{- if .Values.global.imagePullSecrets }}
imagePullSecrets:
  {{- toYaml .Values.global.imagePullSecrets | nindent 2 }}
{{- else if .Values.imagePullSecrets }}
imagePullSecrets:
  {{- toYaml .Values.imagePullSecrets | nindent 2 }}
{{- end }}
{{- end -}}

使用checksum触发滚动更新

yaml
annotations:
  checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
  checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}

4. 安全最佳实践

安全上下文

yaml
podSecurityContext:
  runAsNonRoot: true
  runAsUser: 1000
  fsGroup: 1000

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
  readOnlyRootFilesystem: true

敏感信息管理

yaml
{{- if .Values.secrets.create -}}
apiVersion: v1
kind: Secret
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
type: Opaque
data:
  {{- range $key, $value := .Values.secrets.data }}
  {{ $key }}: {{ $value | b64enc | quote }}
  {{- end }}
{{- end }}

5. 测试最佳实践

Chart测试

yaml
apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "myapp.fullname" . }}-test-connection"
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": test
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never

运行测试

bash
helm test myrelease

helm test myrelease --logs

6. 文档最佳实践

README.md

markdown
# MyChart

## Prerequisites
- Kubernetes 1.20+
- Helm 3.0+

## Installing the Chart
\`\`\`bash
helm install myrelease ./mychart
\`\`\`

## Uninstalling the Chart
\`\`\`bash
helm uninstall myrelease
\`\`\`

## Configuration
| Parameter | Description | Default |
|-----------|-------------|---------|
| `replicaCount` | Number of replicas | `3` |
| `image.repository` | Image repository | `nginx` |
| `image.tag` | Image tag | `1.25` |

NOTES.txt

1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
  {{- range .paths }}
  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
  {{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "myapp.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 "myapp.fullname" . }}'
  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "myapp.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 "myapp.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 }}

总结

本章详细介绍了Helm Charts的核心概念和实践方法:

  1. 基础概念: 掌握了Chart、Release、Repository等核心概念
  2. Chart开发: 学会了Chart结构设计和模板开发
  3. 操作命令: 掌握了Helm常用命令和最佳实践
  4. 多环境管理: 理解了多环境配置管理策略
  5. 仓库管理: 学会了搭建和管理私有Chart仓库
  6. CI/CD集成: 掌握了与GitOps工具的集成方法
  7. 故障排查: 掌握了常见问题的诊断和解决方法

Helm是Kubernetes应用交付的核心工具,为后续的Operator开发和高级特性应用奠定了基础。

下一步学习