Ce guide vous apprend à créer votre premier chart Helm de zéro. Vous allez comprendre la structure complète d'un chart, maîtriser les templates Go, créer des helpers réutilisables et suivre les conventions qui rendent un chart maintenable. En 30 minutes, vous saurez écrire des charts professionnels.
Prérequis
Section intitulée « Prérequis »- Helm installé (voir module H1-01)
- Maîtrise de
helm installet des values (voir modules précédents) - Connaissances Kubernetes : Deployment, Service, ConfigMap
Structure d'un chart Helm
Section intitulée « Structure d'un chart Helm »Les fichiers essentiels
Section intitulée « Les fichiers essentiels »Un chart Helm est un dossier contenant des fichiers avec des rôles précis :
Répertoiremon-chart/
- Chart.yaml Métadonnées du chart (nom, version, description)
- values.yaml Valeurs par défaut configurables
Répertoiretemplates/ Templates Kubernetes (Deployment, Service, etc.)
- _helpers.tpl Fonctions réutilisables (helpers)
- deployment.yaml Template du Deployment
- service.yaml Template du Service
- NOTES.txt Message affiché après installation
Répertoirecharts/ Dépendances (subcharts)
- …
- .helmignore Fichiers à exclure du package
| Fichier | Rôle | Obligatoire ? |
|---|---|---|
Chart.yaml | Identité du chart (nom, version, type) | ✅ Oui |
values.yaml | Valeurs par défaut | ✅ Oui |
templates/ | Templates Kubernetes | ✅ Oui |
templates/_helpers.tpl | Fonctions partagées | Recommandé |
templates/NOTES.txt | Message post-installation | Recommandé |
charts/ | Dépendances téléchargées | Si dépendances |
.helmignore | Exclusions du packaging | Recommandé |
Générer un squelette avec helm create
Section intitulée « Générer un squelette avec helm create »Helm peut générer un squelette complet :
helm create mon-appRésultat :
Creating mon-appStructure générée :
Chart.yaml : l'identité du chart
Section intitulée « Chart.yaml : l'identité du chart »Structure minimale
Section intitulée « Structure minimale »apiVersion: v2name: mon-apidescription: Chart minimal pour une API RESTtype: applicationversion: 0.1.0appVersion: "1.0.0"Décryptage de chaque champ :
| Champ | Signification | Exemple |
|---|---|---|
apiVersion | Version de l'API Chart (v2 pour Helm 3) | v2 |
name | Nom du chart (doit correspondre au dossier) | mon-api |
description | Description courte pour la recherche | Chart pour une API REST |
type | application (déployable) ou library (réutilisable) | application |
version | Version du chart (SemVer) | 0.1.0 |
appVersion | Version de l'application déployée | "1.0.0" |
Champs optionnels recommandés
Section intitulée « Champs optionnels recommandés »apiVersion: v2name: mon-apidescription: Chart minimal pour une API RESTtype: applicationversion: 0.1.0appVersion: "1.0.0"
# Métadonnées supplémentairesmaintainers: - name: Stéphane Robert email: stephane@example.comkeywords: - api - rest - demohome: https://github.com/example/mon-apisources: - https://github.com/example/mon-apiicon: https://example.com/icon.pngCes champs sont affichés dans Artifact Hub et facilitent la découverte du chart.
values.yaml : les valeurs configurables
Section intitulée « values.yaml : les valeurs configurables »Principes de conception
Section intitulée « Principes de conception »Le fichier values.yaml contient les valeurs par défaut que les utilisateurs peuvent surcharger. Un bon values.yaml doit être :
- Auto-documenté : chaque section commentée
- Avec des défauts sensés : le chart doit fonctionner sans surcharge
- Organisé par composant :
image,service,resources, etc.
Exemple complet
Section intitulée « Exemple complet »# Configuration de l'imageimage: repository: ghcr.io/stefanprodan/podinfo tag: "6.7.1" pullPolicy: IfNotPresent
# Nombre de réplicasreplicaCount: 1
# Configuration du serviceservice: type: ClusterIP port: 9898
# Configuration applicativeconfig: message: "Hello from Helm!" logLevel: info
# Resources (à activer en production)resources: {} # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 50m # memory: 64MiTemplates : le cœur du chart
Section intitulée « Templates : le cœur du chart »Syntaxe Go templates
Section intitulée « Syntaxe Go templates »Les templates Helm utilisent la syntaxe Go templates avec des extensions Sprig. Les délimiteurs sont {{ et }}.
Syntaxes de base :
| Syntaxe | Effet |
|---|---|
{{ .Values.replicaCount }} | Insère la valeur (avec espaces autour) |
{{- .Values.replicaCount }} | Supprime l'espace avant |
{{ .Values.replicaCount -}} | Supprime l'espace après |
{{- .Values.replicaCount -}} | Supprime les espaces des deux côtés |
Les variables prédéfinies
Section intitulée « Les variables prédéfinies »Helm fournit plusieurs objets accessibles dans les templates :
| Objet | Contenu |
|---|---|
.Values | Valeurs de values.yaml + surcharges |
.Release | Informations sur la release (nom, namespace, revision) |
.Chart | Contenu de Chart.yaml |
.Capabilities | Capacités du cluster (versions API) |
Variables .Release disponibles :
# Exemple d'utilisationmetadata: name: {{ .Release.Name }}-config # Nom de la release namespace: {{ .Release.Namespace }} # Namespace cible annotations: revision: {{ .Release.Revision | quote }} # Numéro de révisionVariables .Chart disponibles :
labels: app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}Template Deployment minimal
Section intitulée « Template Deployment minimal »Ce template montre le pattern central de tout chart : les valeurs figées sont
remplacées par des expressions {{ .Values.* }}, et les blocs répétés
(labels, sélecteurs) sont factorisés dans des helpers appelés avec
include. Observez le couple include ... | nindent N : le helper
renvoie du texte brut, et nindent l'indente au bon niveau du YAML. La valeur
d'indentation change selon la profondeur (4 sous metadata.labels, 6 sous
selector.matchLabels), et se tromper de nombre produit un YAML invalide au
rendu.
apiVersion: apps/v1kind: Deploymentmetadata: name: {{ include "mon-api.fullname" . }} labels: {{- include "mon-api.labels" . | nindent 4 }}spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "mon-api.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "mon-api.selectorLabels" . | nindent 8 }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: {{ .Values.service.port }} protocol: TCP {{- with .Values.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }}Template Service minimal
Section intitulée « Template Service minimal »Le Service réutilise exactement les mêmes helpers que le Deployment, et c'est
tout l'intérêt de les avoir factorisés. Le point à ne pas manquer est le lien
entre les deux objets : le selector du Service s'appuie sur
mon-api.selectorLabels, les mêmes labels que ceux posés sur les Pods du
Deployment. Si ces deux ensembles divergent, le Service ne trouve aucun Pod et
le trafic n'aboutit nulle part, sans que helm lint ni helm template ne
signalent quoi que ce soit, puisque le YAML reste valide.
apiVersion: v1kind: Servicemetadata: name: {{ include "mon-api.fullname" . }} labels: {{- include "mon-api.labels" . | nindent 4 }}spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: {{- include "mon-api.selectorLabels" . | nindent 4 }}Helpers (_helpers.tpl) : factoriser le code
Section intitulée « Helpers (_helpers.tpl) : factoriser le code »Pourquoi des helpers ?
Section intitulée « Pourquoi des helpers ? »Les helpers évitent la duplication de code. Au lieu de répéter les labels dans chaque template, on les définit une fois dans _helpers.tpl.
Helpers essentiels
Section intitulée « Helpers essentiels »Ces quatre helpers forment le socle repris par tous les charts sérieux, et
helm create les génère à l'identique. Le plus subtil est mon-api.fullname :
il tronque le nom à 63 caractères parce que c'est la limite d'un label DNS
Kubernetes, et évite de préfixer deux fois le nom de la release quand celle-ci
contient déjà le nom du chart. Notez la distinction entre labels et
selectorLabels : les seconds sont un sous-ensemble stable des premiers. Un
selector de Deployment est immuable après création, il ne doit donc jamais
contenir la version, qui change à chaque mise à jour.
{{/*Nom court du chart*/}}{{- define "mon-api.name" -}}{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}{{- end }}
{{/*Nom complet (release + chart)Tronqué à 63 caractères (limite DNS Kubernetes)*/}}{{- define "mon-api.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 }}
{{/*Labels communs (pour tous les objets K8s)*/}}{{- define "mon-api.labels" -}}helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 }}app.kubernetes.io/name: {{ include "mon-api.name" . }}app.kubernetes.io/instance: {{ .Release.Name }}app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}app.kubernetes.io/managed-by: {{ .Release.Service }}{{- end }}
{{/*Labels de sélection (selector du Deployment/Service)*/}}{{- define "mon-api.selectorLabels" -}}app.kubernetes.io/name: {{ include "mon-api.name" . }}app.kubernetes.io/instance: {{ .Release.Name }}{{- end }}Utiliser un helper : include vs template
Section intitulée « Utiliser un helper : include vs template »| Syntaxe | Usage |
|---|---|
{{ include "mon-api.labels" . }} | ✅ Recommandé : permet le piping |
{{ template "mon-api.labels" . }} | ⚠️ Ancien style : pas de piping |
Exemple avec include et piping :
metadata: labels: {{- include "mon-api.labels" . | nindent 4 }}Le | nindent 4 ajoute un retour à la ligne puis indente de 4 espaces.
Fonctions Go templates essentielles
Section intitulée « Fonctions Go templates essentielles »Fonctions de base
Section intitulée « Fonctions de base »Ces trois fonctions reviennent dans presque tous les templates. Retenez surtout
la différence entre default et required : la première fournit une
valeur de repli quand l'utilisateur n'a rien précisé, la seconde bloque le
rendu avec un message d'erreur si la valeur manque. Utilisez required pour
tout ce qui n'a pas de défaut raisonnable (un mot de passe, un nom d'hôte), afin
d'échouer tôt et clairement plutôt que de déployer un manifeste incomplet.
| Fonction | Description | Exemple |
|---|---|---|
quote | Ajoute des guillemets | {{ .Values.name | quote }} → "valeur" |
default | Valeur par défaut si vide | {{ .Values.port | default 8080 }} |
required | Erreur si valeur absente | {{ required "port requis" .Values.port }} |
Fonctions de manipulation de texte
Section intitulée « Fonctions de manipulation de texte »Ces fonctions servent surtout à respecter les contraintes de nommage de
Kubernetes. Les deux plus utilisées sont trunc et trimSuffix, presque
toujours combinées : trunc 63 coupe à la longueur maximale d'un label, mais
peut laisser un tiret en fin de chaîne si la coupe tombe au mauvais endroit, et
trimSuffix "-" le retire pour produire un nom valide. C'est exactement le
motif que vous avez vu dans les helpers plus haut.
| Fonction | Description | Exemple |
|---|---|---|
upper / lower | Casse | {{ "hello" | upper }} → HELLO |
title | Première lettre majuscule | {{ "hello" | title }} → Hello |
replace | Remplacement | {{ "a-b" | replace "-" "_" }} → a_b |
trunc | Tronquer | {{ "long" | trunc 3 }} → lon |
trimSuffix | Enlever suffixe | {{ "app-v1" | trimSuffix "-v1" }} → app |
Fonctions pour YAML
Section intitulée « Fonctions pour YAML »| Fonction | Description | Quand l'utiliser |
|---|---|---|
toYaml | Convertir en YAML | Objets complexes (resources, env) |
nindent N | Nouvelle ligne + indentation | Après toYaml ou include |
indent N | Indentation (sans nouvelle ligne) | Rarement utilisé |
Exemple complet :
{{- with .Values.resources }}resources: {{- toYaml . | nindent 2 }}{{- end }}Rendu :
resources: limits: cpu: 100m memory: 128MiStructures conditionnelles
Section intitulée « Structures conditionnelles »# if / else / end{{- if .Values.ingress.enabled }}# ... créer l'Ingress{{- end }}
# with : change le contexte (le point .){{- with .Values.config }}message: {{ .message }} # .message = .Values.config.message{{- end }}
# range : boucle{{- range .Values.ports }}- port: {{ .port }} name: {{ .name }}{{- end }}Valider son chart
Section intitulée « Valider son chart »helm lint : validation statique
Section intitulée « helm lint : validation statique »helm lint mon-apiRésultat (succès) :
==> Linting mon-api[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failedAvec --strict pour plus de rigueur :
helm lint mon-api --strictLes [INFO] deviennent des erreurs en mode strict.
helm template : prévisualiser le rendu
Section intitulée « helm template : prévisualiser le rendu »helm template ma-release mon-apiCette commande génère le YAML sans l'appliquer au cluster. Idéal pour vérifier le rendu.
Options utiles :
| Option | Effet |
|---|---|
--debug | Affiche les infos de debug |
--show-only templates/deployment.yaml | Rend un seul template |
-n production | Simule le namespace cible |
--set replicaCount=3 | Surcharge une valeur |
Exemple : vérifier un template spécifique
helm template ma-release mon-api --show-only templates/deployment.yamlRésultat :
---apiVersion: apps/v1kind: Deploymentmetadata: name: ma-release-mon-api labels: helm.sh/chart: mon-api-0.1.0 app.kubernetes.io/name: mon-api app.kubernetes.io/instance: ma-release app.kubernetes.io/version: "1.0.0" app.kubernetes.io/managed-by: Helmspec: replicas: 1 ...helm package : créer l'archive
Section intitulée « helm package : créer l'archive »helm package mon-apiRésultat :
Successfully packaged chart and saved it to: /path/to/mon-api-0.1.0.tgzCette archive .tgz peut être publiée dans un registry.
Labels Kubernetes recommandés
Section intitulée « Labels Kubernetes recommandés »Helm recommande d'utiliser les labels standard app.kubernetes.io/* :
| Label | Contenu | Exemple |
|---|---|---|
app.kubernetes.io/name | Nom de l'application | mon-api |
app.kubernetes.io/instance | Nom de la release | prod-mon-api |
app.kubernetes.io/version | Version de l'application | 1.0.0 |
app.kubernetes.io/component | Composant dans l'app | frontend |
app.kubernetes.io/part-of | Application parente | mon-stack |
app.kubernetes.io/managed-by | Outil de gestion | Helm |
helm.sh/chart | Chart et version | mon-api-0.1.0 |
Ces labels permettent de filtrer et organiser les ressources dans le cluster.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
parse error | Syntaxe Go template invalide | Vérifier {{ }} et tirets |
nil pointer | Accès à une valeur inexistante | Utiliser default ou if |
| YAML invalide | Indentation incorrecte | Utiliser nindent avec la bonne valeur |
| Nom trop long | Dépasse 63 caractères | Utiliser trunc 63 | trimSuffix "-" |
required échoue | Valeur obligatoire manquante | Fournir la valeur avec --set |
Lab B1, Créer un chart minimal
Section intitulée « Lab B1, Créer un chart minimal »-
Créer le squelette
Fenêtre de terminal mkdir -p mon-api/templates -
Créer Chart.yaml
Fenêtre de terminal cat > mon-api/Chart.yaml << 'EOF'apiVersion: v2name: mon-apidescription: Mon premier chart Helmtype: applicationversion: 0.1.0appVersion: "1.0.0"EOF -
Créer values.yaml
Fenêtre de terminal cat > mon-api/values.yaml << 'EOF'image:repository: ghcr.io/stefanprodan/podinfotag: "6.7.1"pullPolicy: IfNotPresentreplicaCount: 1service:type: ClusterIPport: 9898config:message: "Hello from my first chart!"EOF -
Créer _helpers.tpl
Fenêtre de terminal cat > mon-api/templates/_helpers.tpl << 'EOF'{{- define "mon-api.fullname" -}}{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}{{- end }}{{- define "mon-api.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 }}{{- define "mon-api.selectorLabels" -}}app.kubernetes.io/name: {{ .Chart.Name }}app.kubernetes.io/instance: {{ .Release.Name }}{{- end }}EOF -
Créer deployment.yaml
Fenêtre de terminal cat > mon-api/templates/deployment.yaml << 'EOF'apiVersion: apps/v1kind: Deploymentmetadata:name: {{ include "mon-api.fullname" . }}labels:{{- include "mon-api.labels" . | nindent 4 }}spec:replicas: {{ .Values.replicaCount }}selector:matchLabels:{{- include "mon-api.selectorLabels" . | nindent 6 }}template:metadata:labels:{{- include "mon-api.selectorLabels" . | nindent 8 }}spec:containers:- name: {{ .Chart.Name }}image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"ports:- containerPort: {{ .Values.service.port }}EOF -
Créer service.yaml
Fenêtre de terminal cat > mon-api/templates/service.yaml << 'EOF'apiVersion: v1kind: Servicemetadata:name: {{ include "mon-api.fullname" . }}labels:{{- include "mon-api.labels" . | nindent 4 }}spec:type: {{ .Values.service.type }}ports:- port: {{ .Values.service.port }}targetPort: {{ .Values.service.port }}selector:{{- include "mon-api.selectorLabels" . | nindent 4 }}EOF -
Valider avec lint et template
Fenêtre de terminal helm lint mon-apihelm template test-release mon-api -
Installer et vérifier
Fenêtre de terminal helm install mon-api-test mon-api -n defaultkubectl get all -l app.kubernetes.io/instance=mon-api-test -
Nettoyer
Fenêtre de terminal helm uninstall mon-api-test
Critères de réussite :
-
helm lintpasse sans erreur -
helm templategénère du YAML valide - Le chart s'installe et crée un pod Running
- Les labels sont correctement appliqués
À retenir
Section intitulée « À retenir »- Un chart =
Chart.yaml+values.yaml+templates/ helm creategénère un squelette complet (utile en production)_helpers.tplcentralise les fonctions réutilisables (labels, noms)- Utilisez
includeplutôt quetemplatepour pouvoir piper toYaml | nindent Nest le pattern pour injecter des objets complexeshelm lintethelm templatesont vos meilleurs amis pour débugger- Les labels
app.kubernetes.io/*sont la convention recommandée