Lab 21 - Writing your own chart

Lab 21 - Writing your own chart

Goal: package an application so it can be installed many times with different values. Chapter: Helm charts

# 1 - scaffold and read what you got
cd ~
helm create shop
find shop -type f | sort
cat shop/Chart.yaml
head -30 shop/values.yaml

helm create gives you a working nginx chart with far more machinery than you need. Reading it is worthwhile: it shows the conventions every public chart follows.

# 2 - render the default chart before changing anything
helm template shop | head -40
helm lint shop

helm template needs no cluster and no network. Run it after every edit - it is faster than any install and it shows the real YAML.

Make it yours

# 3 - strip the scaffold down
cd shop
rm -f templates/hpa.yaml templates/ingress.yaml templates/serviceaccount.yaml
rm -rf templates/tests

Edit values.yaml:

replicaCount: 2

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

service:
  type: NodePort
  port: 80
  nodePort: 30081

content:
  message: "Shop from a chart"

serviceAccount:
  create: false

Add templates/configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "shop.fullname" . }}-content
data:
  index.html: |
    <h1>{{ .Values.content.message }}</h1>
    <p>release: {{ .Release.Name }} / chart: {{ .Chart.Name }}-{{ .Chart.Version }}</p>

Two things to notice. include "shop.fullname" . is a macro from templates/_helpers.tpl that builds a name from the release name - that is what lets you install the chart twice without a collision. And .Release.Name is not known until install time, which is exactly why a chart is a template and not a manifest.

In templates/deployment.yaml, mount that ConfigMap into the container (volumes + volumeMounts at /usr/share/nginx/html), and in templates/service.yaml add nodePort: {{ .Values.service.nodePort }}.

# 4 - the loop: lint -> render -> dry-run -> install
helm lint .
helm template myshop . | less
helm install myshop . --dry-run --debug | head -40
helm install myshop . -n shop --create-namespace
kubectl get all,cm -n shop
curl -s http://worker01:30081
# 5 - the point of a chart: same package, different values
helm install myshop2 . -n shop \
  --set content.message="Second instance" \
  --set service.nodePort=30082 \
  --set replicaCount=1
curl -s http://worker01:30082
helm list -n shop

Same templates, two releases, no name collisions, no copy-pasted YAML. This is what you would otherwise do with a folder per environment.

# 6 - upgrade by value
helm upgrade myshop . --set content.message="Upgraded" -n shop
curl -s http://worker01:30081
helm history myshop -n shop
# 7 - package it
cd ~
helm package shop
ls -l shop-0.1.0.tgz
helm install fromtgz ./shop-0.1.0.tgz -n shop --set service.nodePort=30083
curl -s http://worker01:30083

The .tgz is the shippable artifact - upload it to a chart repository or an OCI registry and other people can install exactly this version.

If you have time

  • Wrap an optional resource in {{- if .Values.ingress.enabled }} and prove with helm template --set ingress.enabled=false that it disappears.
  • Use required "content.message is required" .Values.content.message and see what helm template --set content.message=null reports.
  • Bump version in Chart.yaml to 0.2.0 and package again.

Clean up

helm uninstall myshop myshop2 fromtgz -n shop
kubectl delete ns shop