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 »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 »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 »{{/*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 »| 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 »| 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