Chapter 6: Helm Package Management
Haiyue
25min
Chapter 6: Helm Package Management
Learning Objectives
- Understand Helm’s design philosophy and core concepts
- Master the creation and usage of Helm Charts
- Learn to deploy and manage applications using Helm
- Become proficient in Chart version management and rollback
Key Concepts
What is Helm
Helm is the package manager for Kubernetes, similar to apt/yum for Linux or npm for Node.js. It simplifies the deployment and management of Kubernetes applications.
🔄 正在渲染 Mermaid 图表...
Helm Core Concepts
🔄 正在渲染 Mermaid 图表...
Helm Architecture
🔄 正在渲染 Mermaid 图表...
Installing Helm
# macOS (using Homebrew)
brew install helm
# Linux (using script)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Windows (using Chocolatey)
choco install kubernetes-helm
# Or download binary
# https://github.com/helm/helm/releases
# Verify installation
helm version
# version.BuildInfo{Version:"v3.12.0", ...}
Using Helm Repositories
# Add official repositories
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
# Update repository index
helm repo update
# List added repositories
helm repo list
# Search for Charts
helm search repo nginx
helm search repo mysql --versions # Show all versions
# Search Artifact Hub (public repositories)
helm search hub prometheus
# View Chart information
helm show chart bitnami/nginx
helm show values bitnami/nginx # View default values
helm show all bitnami/nginx # View all information
# Remove repository
helm repo remove stable
Deploying Applications
Basic Deployment
# Install a Chart
helm install my-nginx bitnami/nginx
# Specify namespace
helm install my-nginx bitnami/nginx -n web --create-namespace
# View Release status
helm status my-nginx
# List all Releases
helm list
helm list -A # All namespaces
# View Release history
helm history my-nginx
# Uninstall Release
helm uninstall my-nginx
helm uninstall my-nginx --keep-history # Keep history
Custom Configuration
# Method 1: Command line parameters
helm install my-nginx bitnami/nginx \
--set replicaCount=3 \
--set service.type=LoadBalancer \
--set resources.requests.memory=256Mi
# Method 2: Values file
cat > my-values.yaml <<EOF
replicaCount: 3
service:
type: LoadBalancer
port: 80
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 200m
ingress:
enabled: true
hostname: nginx.example.com
EOF
helm install my-nginx bitnami/nginx -f my-values.yaml
# Method 3: Multiple values files (later overrides earlier)
helm install my-nginx bitnami/nginx \
-f values-base.yaml \
-f values-production.yaml
# View final rendered templates (without deployment)
helm template my-nginx bitnami/nginx -f my-values.yaml
# Simulate installation (dry-run)
helm install my-nginx bitnami/nginx -f my-values.yaml --dry-run --debug
Upgrade and Rollback
# Upgrade Release
helm upgrade my-nginx bitnami/nginx -f my-values.yaml
# Upgrade with new parameters
helm upgrade my-nginx bitnami/nginx --set replicaCount=5
# Install or upgrade (idempotent operation)
helm upgrade --install my-nginx bitnami/nginx -f my-values.yaml
# View history versions
helm history my-nginx
# REVISION STATUS DESCRIPTION
# 1 superseded Install complete
# 2 superseded Upgrade complete
# 3 deployed Upgrade complete
# Rollback to specific version
helm rollback my-nginx 2
# Rollback to previous version
helm rollback my-nginx
# View differences between two versions
helm diff revision my-nginx 2 3 # Requires helm-diff plugin
Creating Helm Charts
Chart Structure
mychart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration values
├── charts/ # Dependent Charts
├── templates/ # Template files
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── _helpers.tpl # Template helper functions
│ ├── NOTES.txt # Post-installation notes
│ └── tests/ # Test templates
│ └── test-connection.yaml
├── .helmignore # Files to ignore when packaging
└── README.md # Documentation
Creating a Chart
# Create new Chart
helm create mychart
# View generated files
tree mychart
Chart.yaml
apiVersion: v2
name: mychart
description: A Helm chart for my application
type: application
version: 0.1.0 # Chart version
appVersion: "1.0.0" # Application version
# Maintainer information
maintainers:
- name: Your Name
email: your@email.com
url: https://example.com
# Keywords (for searching)
keywords:
- web
- nginx
# Project homepage
home: https://github.com/example/mychart
# Icon
icon: https://example.com/icon.png
# Dependencies
dependencies:
- name: mysql
version: "9.x.x"
repository: https://charts.bitnami.com/bitnami
condition: mysql.enabled
- name: redis
version: "17.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
values.yaml
# Application configuration
replicaCount: 1
image:
repository: nginx
pullPolicy: IfNotPresent
tag: "" # Defaults to appVersion
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# Service configuration
service:
type: ClusterIP
port: 80
# Ingress configuration
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: chart-example.local
paths:
- path: /
pathType: Prefix
tls: []
# Resource limits
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
# Autoscaling
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# Node selector
nodeSelector: {}
# Tolerations
tolerations: []
# Affinity
affinity: {}
# Application configuration
config:
logLevel: info
database:
host: localhost
port: 5432
# Dependency configuration
mysql:
enabled: false
auth:
rootPassword: "secret"
redis:
enabled: false
Template Syntax
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
env:
- name: LOG_LEVEL
value: {{ .Values.config.logLevel | quote }}
- name: DB_HOST
value: {{ .Values.config.database.host | quote }}
{{- if .Values.mysql.enabled }}
- name: MYSQL_HOST
value: {{ include "mychart.fullname" . }}-mysql
{{- end }}
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
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 }}
Helper Templates (_helpers.tpl)
# templates/_helpers.tpl
{{/*
Generate application full name
*/}}
{{- 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 }}
{{/*
Common labels
*/}}
{{- 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 }}
{{/*
Selector labels
*/}}
{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Chart name and version
*/}}
{{- define "mychart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Application name
*/}}
{{- define "mychart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
Service Template
# templates/service.yaml
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.selectorLabels" . | nindent 4 }}
Ingress Template
# templates/ingress.yaml
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.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 "mychart.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
ConfigMap Template
# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
data:
config.yaml: |
logLevel: {{ .Values.config.logLevel }}
database:
host: {{ .Values.config.database.host }}
port: {{ .Values.config.database.port }}
{{- if .Values.config.extra }}
extra:
{{- toYaml .Values.config.extra | nindent 6 }}
{{- end }}
NOTES.txt
# templates/NOTES.txt
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ (index $host.paths 0).path }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "mychart.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 "mychart.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "mychart.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 "mychart.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 }}
Chart Testing
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-test-connection"
labels:
{{- include "mychart.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
# Run Chart tests
helm test my-release
# View test results
kubectl logs my-release-mychart-test-connection
Chart Packaging and Publishing
# Check Chart syntax
helm lint mychart
# Package Chart
helm package mychart
# Generates mychart-0.1.0.tgz
# Generate index.yaml (for repository)
helm repo index .
# Install local Chart
helm install my-release ./mychart
helm install my-release ./mychart-0.1.0.tgz
# Push to Chart repository (ChartMuseum example)
curl --data-binary "@mychart-0.1.0.tgz" http://chartmuseum:8080/api/charts
# Push to OCI repository
helm push mychart-0.1.0.tgz oci://registry.example.com/charts
Dependency Management
# Download dependencies
helm dependency update mychart
# View dependencies
helm dependency list mychart
# Dependencies are downloaded to charts/ directory
ls mychart/charts/
# Dependency definition in Chart.yaml
dependencies:
- name: mysql
version: "9.x.x"
repository: https://charts.bitnami.com/bitnami
condition: mysql.enabled
tags:
- database
- name: redis
version: "17.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
tags:
- cache
# Enable/disable dependencies in values.yaml
mysql:
enabled: true
auth:
database: myapp
username: appuser
redis:
enabled: false
# Or using tags
tags:
database: true
cache: false
Practical Exercise
Creating a Complete Web Application Chart
# Create Chart
helm create webapp
# Edit files
# webapp/Chart.yaml
apiVersion: v2
name: webapp
description: A complete web application with frontend, backend, and database
type: application
version: 1.0.0
appVersion: "2.0.0"
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
# webapp/values.yaml
# Global configuration
global:
environment: production
domain: example.com
# Frontend configuration
frontend:
replicaCount: 2
image:
repository: myapp/frontend
tag: "2.0.0"
resources:
requests:
cpu: 100m
memory: 128Mi
# Backend configuration
backend:
replicaCount: 3
image:
repository: myapp/backend
tag: "2.0.0"
resources:
requests:
cpu: 200m
memory: 256Mi
# Ingress configuration
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: www.example.com
paths:
- path: /
pathType: Prefix
service: frontend
- path: /api
pathType: Prefix
service: backend
tls:
- hosts:
- www.example.com
secretName: webapp-tls
# Database configuration
postgresql:
enabled: true
auth:
database: webapp
username: webapp
existingSecret: webapp-db-secret
primary:
persistence:
size: 10Gi
# Cache configuration
redis:
enabled: true
auth:
enabled: false
master:
persistence:
size: 1Gi
# webapp/templates/frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "webapp.fullname" . }}-frontend
labels:
{{- include "webapp.labels" . | nindent 4 }}
app.kubernetes.io/component: frontend
spec:
replicas: {{ .Values.frontend.replicaCount }}
selector:
matchLabels:
{{- include "webapp.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: frontend
template:
metadata:
labels:
{{- include "webapp.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: frontend
spec:
containers:
- name: frontend
image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
ports:
- name: http
containerPort: 80
env:
- name: API_URL
value: "http://{{ include "webapp.fullname" . }}-backend:8080"
resources:
{{- toYaml .Values.frontend.resources | nindent 12 }}
---
# webapp/templates/backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "webapp.fullname" . }}-backend
labels:
{{- include "webapp.labels" . | nindent 4 }}
app.kubernetes.io/component: backend
spec:
replicas: {{ .Values.backend.replicaCount }}
selector:
matchLabels:
{{- include "webapp.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: backend
template:
metadata:
labels:
{{- include "webapp.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: backend
spec:
containers:
- name: backend
image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}"
ports:
- name: http
containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "webapp.fullname" . }}-secrets
key: database-url
{{- if .Values.redis.enabled }}
- name: REDIS_URL
value: "redis://{{ include "webapp.fullname" . }}-redis-master:6379"
{{- end }}
resources:
{{- toYaml .Values.backend.resources | nindent 12 }}
# Update dependencies
helm dependency update webapp
# Check templates
helm template webapp ./webapp -f values-production.yaml
# Deploy application
helm upgrade --install webapp ./webapp \
--namespace webapp \
--create-namespace \
-f values-production.yaml
# View status
helm status webapp -n webapp
kubectl get all -n webapp
Multi-Environment Deployment
# values-development.yaml
global:
environment: development
frontend:
replicaCount: 1
image:
tag: "latest"
backend:
replicaCount: 1
image:
tag: "latest"
ingress:
enabled: false
postgresql:
enabled: true
primary:
persistence:
size: 1Gi
redis:
enabled: false
# values-staging.yaml
global:
environment: staging
domain: staging.example.com
frontend:
replicaCount: 2
backend:
replicaCount: 2
ingress:
enabled: true
hosts:
- host: staging.example.com
paths:
- path: /
pathType: Prefix
service: frontend
postgresql:
primary:
persistence:
size: 5Gi
redis:
enabled: true
# Deploy to different environments
helm upgrade --install webapp-dev ./webapp \
-f values-development.yaml \
-n development
helm upgrade --install webapp-staging ./webapp \
-f values-staging.yaml \
-n staging
helm upgrade --install webapp-prod ./webapp \
-f values-production.yaml \
-n production
Helm Plugins
# Install common plugins
# helm-diff: View upgrade differences
helm plugin install https://github.com/databus23/helm-diff
# helm-secrets: Manage encrypted values
helm plugin install https://github.com/jkroepke/helm-secrets
# helm-s3: Use S3 as Chart repository
helm plugin install https://github.com/hypnoglow/helm-s3
# View installed plugins
helm plugin list
# Using helm-diff
helm diff upgrade my-release ./mychart -f values.yaml
# Using helm-secrets
helm secrets enc secrets.yaml
helm secrets dec secrets.yaml
helm upgrade --install my-release ./mychart -f secrets://secrets.yaml
Common Troubleshooting
# View Release details
helm get all my-release
helm get values my-release
helm get manifest my-release
helm get notes my-release
helm get hooks my-release
# Debug template rendering
helm template my-release ./mychart --debug
# View rendered specific template
helm template my-release ./mychart -s templates/deployment.yaml
# Check Chart syntax
helm lint ./mychart
# Simulate installation
helm install my-release ./mychart --dry-run --debug
# View installation failure reason
helm status my-release
kubectl describe pod -l app.kubernetes.io/instance=my-release
Best Practices
- Version Control: Store Charts in Git repositories
- Semantic Versioning: Follow semver conventions for version management
- Safe Defaults: values.yaml should have sensible default values
- Complete Documentation: Write clear README and NOTES.txt
- Test Coverage: Write Chart tests to verify correct deployment
- Sensitive Data: Use helm-secrets or external secret management
Summary
Through this chapter, you should have mastered:
- Helm Concepts: Understanding the relationship between Charts, Releases, and Repositories
- Deployment Management: Using Helm to install, upgrade, and rollback applications
- Chart Development: Creating custom Charts and using template syntax
- Dependency Management: Managing Chart dependencies and sub-Charts
- Best Practices: Multi-environment configuration, version management, and plugin usage
In the next chapter, we will learn about monitoring and logging, mastering the use of Prometheus, Grafana, and EFK/ELK.