Helm Charts: Packaging Kubernetes Applications - 夜莺博客

Helm Charts: Packaging Kubernetes Applications

Applying raw manifests works until the second environment appears. The moment the same application must run in staging and production with different replica counts, image tags and ingress hosts, hand-edited YAML becomes a liability. Helm solves that by packaging Kubernetes resources as versioned, templated charts and tracking each installation as a release you can inspect, upgrade and roll back. This guide covers chart anatomy, values layering, template functions you will actually use, and the release workflow that keeps a production upgrade reversible.

Chart anatomy

webapp/
├── Chart.yaml          # name, version, appVersion, dependencies
├── values.yaml         # default configuration
├── values.schema.json  # optional JSON schema validating user values
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── _helpers.tpl    # named templates (labels, fullname)
│   └── NOTES.txt       # rendered after install
└── charts/             # vendored chart dependencies
# Chart.yaml
apiVersion: v2
name: webapp
version: 1.4.0          # chart version, bumps on template changes
appVersion: "2.7.3"     # application version, informational
dependencies:
  - name: postgresql
    version: 15.x.x
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled

Splitting chart version from appVersion matters: a chart can change without a new application build, and an application can be redeployed with the same chart version. Bake both into your CI pipeline so a release is traceable.

Templating: the patterns worth memorising

{{/* templates/deployment.yaml */}}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "webapp.fullname" . }}
  labels:
    {{- include "webapp.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "webapp.name" . }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
    spec:
      containers:
        - name: webapp
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          ports:
            - containerPort: {{ .Values.service.targetPort }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
  • include ... | nindent renders a named template at the correct indentation — use it instead of copy-pasting label blocks.
  • The checksum/config annotation forces a rolling restart when a ConfigMap changes, which Kubernetes does not do by itself.
  • Guard optional resources with {{- if .Values.ingress.enabled }} so one chart serves several environments.
  • Never put secrets in values.yaml. Reference an existing Secret or integrate an external secret store.

Values layering across environments

# values.yaml (defaults)
replicaCount: 2
image:
  repository: registry.example.com/webapp
  tag: ""
resources:
  requests: { cpu: 200m, memory: 256Mi }
  limits:   { cpu: "1",  memory: 512Mi }
ingress:
  enabled: false

# values-prod.yaml (overrides only)
replicaCount: 6
ingress:
  enabled: true
  hosts:
    - host: app.example.com
      paths: ["/"]
helm upgrade --install webapp ./webapp \
  -f values-prod.yaml \
  --set image.tag=2.7.3 \
  --namespace production --create-namespace \
  --atomic --timeout 5m

--atomic makes the release roll back automatically if any resource fails to become ready within the timeout, which is what turns a Helm upgrade into a safe operation. Pair it with --wait semantics — atomic implies waiting — and set a timeout that reflects real startup time for the slowest Deployment in the release.

The release workflow

# preview exactly what will change before applying
helm diff upgrade webapp ./webapp -f values-prod.yaml     # with the diff plugin
helm template webapp ./webapp -f values-prod.yaml | less   # no cluster needed

# lifecycle
helm upgrade --install ...        # deploy
helm list -n production           # releases and revisions
helm status webapp -n production
helm history webapp -n production
helm rollback webapp 3 -n production
helm get values webapp -n production
helm get manifest webapp -n production | grep image:

helm template rendering without a cluster is the cheapest validation you can put in a pull request: it catches missing values, YAML type errors and indentation faults before anyone touches production. Add helm lint ./webapp and a JSON schema in values.schema.json so a typo like a string in replicaCount fails the pipeline instead of the cluster.

Hooks and dependencies

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "webapp.fullname" . }}-migrate
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-delete-policy": before-hook-creation

Hooks give you ordered execution: database migrations before an upgrade, cache flushes after, smoke tests before a release is marked successful. Keep hook Jobs idempotent — Helm will re-run them on every upgrade unless the delete policy says otherwise. If you depend on a cert-manager-secured ingress or a stateful backing service, declare it as a chart dependency rather than documenting a manual prerequisite.

Verification after a deploy

kubectl -n production get deploy,sts,svc,ingress
kubectl -n production rollout status deploy/webapp --timeout=180s
kubectl -n production get pods -l app.kubernetes.io/name=webapp -o wide
kubectl -n production logs -l app.kubernetes.io/name=webapp --tail=50
helm get manifest webapp -n production | grep -A2 "image:"

Confirm that the running image tag matches the release you intended, all replicas are ready, and the ingress has an address. Roll back immediately if the image tag differs — that usually means the release values were not the ones staged.

Operations that save time later

  • Never edit Helm-managed objects with kubectl. The next upgrade reverts the change and the diff becomes unreadable.
  • Store charts in a registry (OCI, Harbor, ChartMuseum) and pin versions in CI; deploying from a working directory is not reproducible. Trivy scanning of the same registry is covered in the Harbor vulnerability scanning guide.
  • Keep cluster state recoverable: Helm restores objects, not data. Pair releases with volume snapshots and a tested Velero backup and restore plan.
  • Version charts with intent: semantic version bumps on template changes, and a changelog entry per release.
  • Watch scaling assumptions. If your chart sets a HorizontalPodAutoscaler, the replica count in values becomes a starting point only — see the HPA/VPA/KEDA comparison for which one fits your workload.

原文链接:https://helm.sh/docs/topics/charts/