Helm Charts Crash Course

Master Kubernetes Package Management Step by Step

Lesson 1: Introduction to Helm

What is Helm?

Helm is the package manager for Kubernetes. Think of it as the "apt" or "yum" for Kubernetes applications. It helps you define, install, and upgrade complex Kubernetes applications.

Key Concepts

  • Chart: A Helm package containing all resource definitions needed to run an application
  • Release: An instance of a chart running in a Kubernetes cluster
  • Repository: A place where charts are stored and shared

Why Use Helm?

  1. Simplicity: Deploy complex applications with a single command
  2. Reusability: Package once, deploy anywhere
  3. Versioning: Track and rollback releases easily
  4. Configuration: Customize deployments without modifying templates
Fun Fact: Helm was originally created by Deis (now part of Microsoft) and is now a CNCF graduated project.

Basic Helm Commands

# Search for charts helm search repo nginx # Install a chart helm install my-release bitnami/nginx # List releases helm list # Upgrade a release helm upgrade my-release bitnami/nginx # Uninstall a release helm uninstall my-release

Test Your Knowledge - Lesson 1

1. What is a Helm Chart?

2. What command would you use to install a Helm chart?

3. What is a Helm Release?

Lesson 2: Chart Structure

Creating Your First Chart

Let's create a basic Helm chart from scratch. A chart is organized as a collection of files inside a directory.

# Create a new chart helm create mychart

Chart Directory Structure

mychart/ Chart.yaml # Chart metadata values.yaml # Default configuration values charts/ # Dependencies (other charts) templates/ # Template files deployment.yaml service.yaml ingress.yaml _helpers.tpl # Template helpers .helmignore # Files to ignore

Chart.yaml - The Chart Metadata

apiVersion: v2 name: mychart description: A Helm chart for my application type: application version: 0.1.0 # Chart version appVersion: "1.0" # Version of the app keywords: - web - application maintainers: - name: Your Name email: your.email@example.com
Important: The version field is the chart version (semantic versioning), while appVersion is the version of the application being deployed.

values.yaml - Default Configuration

# Default values for mychart replicaCount: 1 image: repository: nginx tag: "1.21" pullPolicy: IfNotPresent service: type: ClusterIP port: 80 resources: limits: cpu: 100m memory: 128Mi requests: cpu: 100m memory: 128Mi
Pro Tip: The values.yaml file provides default values that users can override during installation using --set or custom values files.

Test Your Knowledge - Lesson 2

1. Which file contains the chart's metadata?

2. Where are Kubernetes resource templates stored?

3. What is the purpose of values.yaml?

Lesson 3: Templates and Values

Template Basics

Helm uses the Go templating engine to render Kubernetes manifests. Templates allow you to inject values dynamically.

Simple Deployment Template

apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-deployment labels: app: {{ .Chart.Name }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Chart.Name }} template: metadata: labels: app: {{ .Chart.Name }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" ports: - containerPort: {{ .Values.service.port }}

Built-in Objects

  • .Release.Name: Name of the release
  • .Release.Namespace: Namespace the release is deployed to
  • .Chart.Name: Name from Chart.yaml
  • .Chart.Version: Chart version from Chart.yaml
  • .Values: Values from values.yaml or user-provided values

Template Functions

# Convert to uppercase {{ .Values.environment | upper }} # Default value if not set {{ .Values.replicas | default 3 }} # Quote a string {{ .Values.image.tag | quote }} # Conditional rendering {{ if .Values.ingress.enabled }} enabled: true {{ else }} enabled: false {{ end }} # Range over a list {{ range .Values.environments }} - {{ . }} {{ end }}

Overriding Values

# Using --set flag helm install myapp ./mychart --set replicaCount=3 # Using custom values file helm install myapp ./mychart -f custom-values.yaml # Multiple values helm install myapp ./mychart \ --set image.tag=2.0 \ --set service.type=LoadBalancer
Testing Templates: Use helm template mychart to render templates locally without installing them to see the generated YAML.

Test Your Knowledge - Lesson 3

1. What templating engine does Helm use?

2. How do you access the release name in a template?

3. Which command allows you to override a value during installation?

Lesson 4: Advanced Templates

Named Templates and Helpers

Reusable template snippets can be defined in _helpers.tpl and called throughout your chart.

{{/* _helpers.tpl */}} {{/* Generate full name */}} {{- define "mychart.fullname" -}} {{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Common labels */}} {{- define "mychart.labels" -}} app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end -}}

Using Named Templates

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

Conditional Logic

{{/* Conditionally create Ingress */}} {{ if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ include "mychart.fullname" . }} annotations: {{- range $key, $value := .Values.ingress.annotations }} {{ $key }}: {{ $value | quote }} {{- end }} spec: 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 }} {{ end }}

Flow Control

{{/* with: change scope */}} {{ with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 2 }} {{ end }} {{/* range: iterate over lists */}} env: {{- range $key, $value := .Values.env }} - name: {{ $key }} value: {{ $value | quote }} {{- end }}
Whitespace Control: Use {{- to trim left whitespace and -}} to trim right whitespace. This is crucial for proper YAML indentation.

Template Functions - Advanced

# toYaml - convert to YAML {{ toYaml .Values.resources | nindent 2 }} # toJson - convert to JSON {{ toJson .Values.config }} # b64enc - base64 encode {{ .Values.secret | b64enc }} # sha256sum - generate checksum checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}

Test Your Knowledge - Lesson 4

1. Where are named templates typically defined?

2. What does the {{- syntax do?

3. Which function converts data to YAML format?

Lesson 5: Dependencies and Hooks

Chart Dependencies

Charts can depend on other charts. Dependencies are defined in Chart.yaml.

# Chart.yaml apiVersion: v2 name: myapp version: 1.0.0 dependencies: - name: mysql version: 9.3.4 repository: https://charts.bitnami.com/bitnami condition: mysql.enabled - name: redis version: 17.3.7 repository: https://charts.bitnami.com/bitnami condition: redis.enabled

Managing Dependencies

# Download dependencies helm dependency update ./mychart # List dependencies helm dependency list ./mychart # This creates Chart.lock and downloads charts to charts/ directory

Configuring Dependencies

# values.yaml - configure subchart values mysql: enabled: true auth: rootPassword: "mypassword" database: "myapp" primary: persistence: size: 8Gi redis: enabled: true auth: enabled: false master: persistence: size: 2Gi

Helm Hooks

Hooks allow you to intervene at specific points in a release lifecycle.

# Hook example: pre-install job apiVersion: batch/v1 kind: Job metadata: name: "{{ .Release.Name }}-db-init" annotations: "helm.sh/hook": pre-install "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": hook-succeeded spec: template: spec: containers: - name: db-init image: mysql:8.0 command: ["/bin/sh", "-c"] args: - | mysql -h mysql -u root -p$MYSQL_ROOT_PASSWORD << EOF CREATE DATABASE IF NOT EXISTS myapp; EOF restartPolicy: Never

Available Hook Points

  • pre-install: Executes after templates are rendered, but before resources are created
  • post-install: Executes after all resources are loaded into Kubernetes
  • pre-upgrade: Executes before upgrade
  • post-upgrade: Executes after upgrade
  • pre-delete: Executes before deletion
  • post-delete: Executes after all resources are deleted
  • pre-rollback: Executes before rollback
  • post-rollback: Executes after rollback
Hook Weight: Use hook-weight to order hook execution. Lower weights execute first (can be negative).

Test Your Knowledge - Lesson 5

1. Where are chart dependencies defined?

2. What command downloads chart dependencies?

3. Which hook executes after all resources are created?

Lesson 6: Production Best Practices

Chart Testing and Validation

# Lint your chart for issues helm lint ./mychart # Dry-run to see what would be deployed helm install myapp ./mychart --dry-run --debug # Template validation helm template myapp ./mychart # Install with test helm test myrelease

Creating Chart Tests

# templates/tests/test-connection.yaml apiVersion: v1 kind: Pod metadata: name: "{{ include "mychart.fullname" . }}-test" annotations: "helm.sh/hook": test spec: containers: - name: wget image: busybox command: ['wget'] args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never

Security Best Practices

  • RBAC: Always include proper RBAC resources (ServiceAccount, Role, RoleBinding)
  • Security Context: Set appropriate security contexts
  • Secrets: Never hardcode secrets in values.yaml
  • Image Tags: Avoid using 'latest' tag
# Security context example securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 capabilities: drop: - ALL readOnlyRootFilesystem: true allowPrivilegeEscalation: false

Resource Management

# Always set resource limits and requests resources: limits: cpu: 500m memory: 512Mi requests: cpu: 250m memory: 256Mi # Liveness and readiness probes livenessProbe: httpGet: path: /healthz port: http initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: http initialDelaySeconds: 5 periodSeconds: 5

Packaging and Distribution

# Package your chart helm package ./mychart # Generate index for chart repository helm repo index . --url https://charts.example.com # Sign your chart (security) helm package --sign --key 'mykey' --keyring ~/.gnupg/secring.gpg ./mychart # Push to OCI registry helm push mychart-0.1.0.tgz oci://registry.example.com/charts

Version Management

# Upgrade with rollback on failure helm upgrade myapp ./mychart --atomic --timeout 5m # Rollback to previous version helm rollback myapp # Rollback to specific revision helm rollback myapp 3 # View release history helm history myapp
Production Tip: Always use --atomic flag in production. It automatically rolls back if the upgrade fails.

Documentation

  • Create comprehensive README.md
  • Document all values with comments in values.yaml
  • Include examples for common use cases
  • Add NOTES.txt for post-installation instructions
# templates/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 }} {{ if .Values.ingress.enabled }} Visit your application at: {{- range .Values.ingress.hosts }} http://{{ .host }} {{- end }} {{ else }} Get the application URL by running: export POD_NAME=$(kubectl get pods -l "app={{ .Chart.Name }}" -o jsonpath="{.items[0].metadata.name}") kubectl port-forward $POD_NAME 8080:80 {{ end }}

Final Test - Lesson 6

1. Which command checks your chart for potential issues?

2. What flag automatically rolls back on upgrade failure?

3. Why should you avoid using 'latest' as an image tag?