Ce guide vous apprend à créer votre premier chart Helm de zéro. Vous allez comprendre la structure d'un chart, maîtriser les templates Go, écrire 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 »Un chart n'est qu'un dossier, mais chacun de ses fichiers a un rôle que Helm connaît par son nom. Il n'y a pas de fichier de configuration qui déclarerait « voici mes templates » : c'est l'emplacement qui décide. Comprendre cette convention vous évite la moitié des erreurs de débutant, à commencer par un template rangé au mauvais endroit et jamais rendu.
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 »Ce fichier est la carte d'identité du chart : son nom, sa version, et ce qu'il embarque. Deux de ses champs pèsent bien plus lourd qu'ils n'en ont l'air, le name, qui décide du nom de l'archive produite, et la version, qui décide du tag lors d'une publication OCI.
Structure minimale
Section intitulée « Structure minimale »Ces six lignes suffisent à faire un chart valide. Tout le reste est facultatif, ce qui ne veut pas dire inutile : les champs optionnels vus plus bas sont ce qui rend un chart trouvable et compréhensible par quelqu'un d'autre que son auteur.
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" |
Les deux types de chart, et pourquoi le second existe
Section intitulée « Les deux types de chart, et pourquoi le second existe »Le champ type n'a que deux valeurs, et celle que vous ne connaissez pas encore résout un problème qui arrive vite : la duplication. Au troisième chart écrit dans une équipe, le même _helpers.tpl a été copié trois fois, et la convention de labels part à la dérive dès qu'une des copies évolue seule.
Un chart library est fait pour ça. Il ne contient que des helpers et ne produit aucune ressource : d'autres charts le déclarent en dépendance et appellent ses définitions. C'est la factorisation portée au niveau du dépôt, là où _helpers.tpl ne la porte qu'au niveau du chart.
| Type | Ce qu'il contient | Ce qu'il produit | Installable |
|---|---|---|---|
application | templates, values, helpers | des ressources Kubernetes | oui |
library | uniquement des helpers | rien par lui-même | non |
La distinction n'est pas qu'une convention, Helm l'applique. Tenter d'installer un chart de bibliothèque, ou même d'en calculer le rendu, s'arrête net :
helm install socle ./socleError: INSTALLATION FAILED: library charts are not installablehelm template rend exactement le même refus. C'est une bonne nouvelle : l'erreur arrive à la première seconde, pas après un déploiement silencieusement vide.
L'usage se fait donc toujours par la dépendance. Le chart de bibliothèque déclare ses helpers :
apiVersion: v2name: socleversion: 0.1.0type: library{{- define "socle.labels" -}}app.kubernetes.io/managed-by: {{ .Release.Service }}socle.example.invalid/version: "1"{{- end -}}Le chart applicatif le consomme comme n'importe quelle dépendance, puis appelle la définition par son nom :
metadata: name: via-library labels: {{- include "socle.labels" . | nindent 4 }}Rendu obtenu :
name: via-library labels: app.kubernetes.io/managed-by: Helm socle.example.invalid/version: "1"Le . passé à include est ce qui rend la chose possible : le helper reçoit le contexte du chart appelant, donc ses values et sa release. Un seul endroit décide des labels de toute une organisation, et une correction s'y fait une fois.
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 »C'est le seul fichier du chart que vos utilisateurs liront vraiment. Il définit à la fois les valeurs par défaut et, implicitement, la liste de ce qui est configurable : une clé absente d'ici est une clé que personne ne pensera à surcharger. Sa conception mérite donc autant de soin que celle des templates.
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 »Un template est un manifeste Kubernetes ordinaire dans lequel on a remplacé les valeurs variables par des expressions. C'est tout. Si vous savez écrire un Deployment à la main, vous savez écrire un template : il reste à apprendre les quelques constructions qui permettent de l'adapter au contexte, et surtout les pièges d'indentation propres au YAML généré.
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, révision) |
.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 motif central de tout chart : les valeurs figées
sont remplacées par des expressions {{ .Values.* }}, et les blocs
répétés, labels et sélecteurs, sont factorisés dans des helpers appelés
avec include. Observez le couple include ... | nindent N : le helper
rend un fragment sans indentation, et nindent l'aligne au bon niveau.
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. 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 : 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 »Dès qu'un chart dépasse deux ou trois templates, les mêmes blocs se répètent : les labels, le nom des ressources, le sélecteur. Les helpers permettent de les définir une seule fois, et donc de les corriger une seule fois. C'est la différence entre un chart qu'on maintient et un chart qu'on réécrit.
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 »La bibliothèque Sprig apporte plus de cent fonctions, et vous n'en utiliserez qu'une poignée. Les sections qui suivent retiennent celles qui reviennent dans presque tous les charts publics, regroupées par usage plutôt que par ordre alphabétique.
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, et trimSuffix "-"
le retire pour produire un nom valide. C'est exactement le motif des 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 »Ces trois fonctions règlent le piège le plus fréquent du templating : insérer une structure dans du YAML sans casser l'indentation. La règle de choix tient en une phrase : nindent quand vous partez d'une nouvelle ligne, indent quand le contenu suit déjà quelque chose sur la ligne courante.
| 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: 128MiLes structures de contrôle : écrire un template, pas le copier
Section intitulée « Les structures de contrôle : écrire un template, pas le copier »Trois mots-clés suffisent à écrire n'importe quel chart : if, range et with. Les gabarits que vous récupérez ailleurs ne contiennent rien d'autre, et les lire cesse d'être intimidant une fois qu'on sait ce que chacun fait au point (.), cette variable qui désigne « le contexte courant » et qui est la seule vraie difficulté du langage.
Les exemples ci-dessous s'appuient tous sur le même fichier de values :
environnements: - nom: dev replicas: 1 - nom: prod replicas: 3ingress: enabled: true hote: app.example.comjournal: niveau: infoif et else : produire ou ne pas produire
Section intitulée « if et else : produire ou ne pas produire »La conditionnelle sert à rendre un bloc facultatif. C'est ce qui permet à un même chart de créer un Ingress chez l'un et pas chez l'autre, sans le forker.
data:{{- if .Values.ingress.enabled }} expose: "oui"{{- else }} expose: "non"{{- end }} niveau: "{{ .Values.journal.niveau | upper }}"Rendu avec les values ci-dessus :
data: expose: "oui" niveau: "INFO"Puis avec --set ingress.enabled=false :
data: expose: "non"Le | upper de la dernière ligne est un pipeline : la valeur traverse la fonction, exactement comme dans un shell. C'est la construction la plus fréquente dans les charts, et default, quote et nindent s'emploient de la même manière.
range : répéter un bloc pour chaque élément
Section intitulée « range : répéter un bloc pour chaque élément »range boucle sur une liste et, à chaque tour, fait pointer le point sur l'élément courant. C'est ce qui permet de générer autant de ressources qu'il y a d'entrées dans les values.
{{- range .Values.environnements }}---apiVersion: v1kind: ConfigMapmetadata: name: conf-{{ .nom }}data: replicas: "{{ .replicas }}"{{- end }}Ce template ne produit pas un ConfigMap mais deux :
kind: ConfigMap name: conf-dev replicas: "1"kind: ConfigMap name: conf-prod replicas: "3"Notez le --- à l'intérieur de la boucle : c'est lui qui sépare les documents YAML. Sans lui, Helm rend un seul document malformé.
Quand il faut aussi connaître la position dans la liste, range accepte deux variables, l'indice et l'élément :
data:{{- range $i, $env := .Values.environnements }} rang-{{ $i }}: "{{ $env.nom }}"{{- end }}data: rang-0: "dev" rang-1: "prod"Les noms $i et $env sont des variables de template, déclarées avec :=. Elles gardent leur valeur dans tout le bloc, ce qui les rend précieuses dès que le point change de sens.
with : raccourcir un chemin, au prix d'un piège
Section intitulée « with : raccourcir un chemin, au prix d'un piège »with déplace le point sur une sous-partie des values. Il évite de répéter .Values.ingress. dix fois :
data:{{- with .Values.ingress }} hote: "{{ .hote }}" tls: "{{ .tls }}"{{- end }}data: hote: "app.example.com" tls: "true"Et c'est là que presque tout le monde se fait prendre. À l'intérieur du bloc, le point ne désigne plus la racine : tout ce qui n'est pas sous .Values.ingress devient inaccessible. Ce template, qui paraît anodin, ne compile pas :
{{- with .Values.ingress }} hote: "{{ .hote }}" release: "{{ .Release.Name }}" # ❌ .Release n'existe plus ici{{- end }}Error: templates/t.yaml:8:23 executing "templates/t.yaml" at <.Release.Name>: nil pointer evaluating interface {}.NameLe nil pointer evaluating interface {} est le message le plus fréquent du débutant, et il vient neuf fois sur dix d'un with. La parade tient en un caractère : $ désigne toujours la racine, où que vous vous trouviez dans le template.
{{- with .Values.ingress }} release: "{{ $.Release.Name }}" # rend « c », le nom de la release{{- end }}required : refuser de rendre plutôt que rendre faux
Section intitulée « required : refuser de rendre plutôt que rendre faux »Une value absente ne provoque pas d'erreur : Helm rend une chaîne vide et le manifeste part au cluster, amputé. La fonction required inverse ce comportement et arrête le rendu avec le message de votre choix.
data: hote: "{{ required "ingress.hote est obligatoire pour exposer l'application" .Values.ingress.hote }}"Error: execution error at (templates/t.yaml:6:12): ingress.hote est obligatoire pour exposer l'applicationLe message est celui que vous écrivez. C'est la différence entre un utilisateur de votre chart qui comprend en dix secondes, et un qui ouvre vos templates. Réservez required aux values sans lesquelles le déploiement n'a aucun sens, et donnez une valeur par défaut au reste avec | default.
Le tiret des accolades : lisibilité, pas validité
Section intitulée « Le tiret des accolades : lisibilité, pas validité »{{- supprime l'espace blanc qui précède la balise, -}} celui qui la suit. Sans lui, chaque ligne de contrôle laisse une ligne vide dans le rendu :
# sans tiret # avec {{-data: data: a: "1" a: "1" b: "2" b: "2"Le YAML reste valide dans les deux cas, vérification faite : c'est une question de lisibilité, pas de correction. La nuance compte, car beaucoup de charts mettent des tirets partout par superstition. La règle utile est plus simple : mettez-en sur les lignes qui ne portent que du contrôle, et relisez le rendu avec helm template plutôt que de deviner.
Deux fonctions qui savent ce que le template ignore
Section intitulée « Deux fonctions qui savent ce que le template ignore »tpl et .Capabilities répondent à des questions que les structures de contrôle ne peuvent pas poser, et les deux se rencontrent dès qu'un chart sert à plusieurs équipes.
tpl évalue une chaîne comme un template. Sans elle, une value contenant des accolades est rendue littéralement :
message: "Bienvenue sur {{ .Values.hote }}, release {{ .Release.Name }}"data: brut: "{{ .Values.message }}" evalue: "{{ tpl .Values.message . }}"data: brut: "Bienvenue sur {{ .Values.hote }}, release {{ .Release.Name }}" evalue: "Bienvenue sur app.example.com, release mon-app"Le second argument est le contexte d'évaluation, presque toujours le point. C'est ce qui permet à l'utilisateur de votre chart d'écrire des values paramétrées par d'autres values, sans que vous ayez à prévoir chaque combinaison.
.Capabilities interroge le cluster plutôt que les values. Son usage courant est de rendre une ressource seulement si son API existe :
{{- if .Capabilities.APIVersions.Has "gateway.networking.k8s.io/v1/HTTPRoute" }}# ... la HTTPRoute{{- end }}Ce que .Capabilities répond hors ligne
Section intitulée « Ce que .Capabilities répond hors ligne ».Capabilities interroge le cluster, et helm template ne parle à aucun cluster : la réponse est donc false pour tout. Mesuré sur un cluster où l'Ingress existe bel et bien :
| Commande | APIVersions.Has "networking.k8s.io/v1/Ingress" |
|---|---|
helm template | false |
helm install --dry-run=server | true |
La conséquence est plus gênante qu'une simple valeur fausse : un bloc conditionné par .Capabilities disparaît de votre prévisualisation locale et réapparaît à l'installation. Vous validez donc un rendu qui n'est pas celui qui sera appliqué. Ces charts-là se valident avec --dry-run=server, seul régime où la question est posée au cluster.
.Capabilities.KubeVersion mérite un traitement à part, parce qu'il répond dans les deux cas et paraît donc fiable. Il ne l'est pas : hors ligne, la valeur ne vient pas du cluster mais d'un défaut porté par le binaire. Le drapeau --kube-version le démontre, puisqu'il la change sans qu'aucun cluster ne soit consulté :
helm template sonde ./mon-chart # kubeVersion: "v1.37.0"helm template sonde ./mon-chart --kube-version 1.30.0 # kubeVersion: "v1.30.0"Un template qui compare KubeVersion pour choisir une API teste donc, en local, la version que Helm suppose, pas celle de votre cluster : le rendu peut être juste chez vous et faux en production. Le même drapeau devient en revanche précieux en intégration continue, où il permet de vérifier qu'un chart rend correctement sur plusieurs versions de Kubernetes sans en provisionner aucune.
Embarquer un fichier de configuration dans le chart
Section intitulée « Embarquer un fichier de configuration dans le chart »Recopier un nginx.conf de quarante lignes à l'intérieur d'un template est la mauvaise réponse à un besoin courant. Le fichier devient illisible, perd la coloration syntaxique de son éditeur, et son indentation se mélange à celle du YAML. L'objet .Files évite tout cela : le fichier reste un fichier, dans le chart, et le template va le chercher.
mon-chart/├── config/│ ├── nginx.conf│ └── alertes.ini└── templates/ └── configmap.yamldata: nginx.conf: |{{ .Files.Get "config/nginx.conf" | indent 4 }}data: nginx.conf: | server { listen 80; }Le | indent 4 est obligatoire : sans lui, le contenu s'insère à la colonne zéro et casse le bloc YAML. C'est la cause la plus fréquente d'un error converting YAML to JSON sur un chart qui semblait correct.
Pour embarquer plusieurs fichiers d'un coup, .Files.Glob parcourt un motif, et la variable $ redevient nécessaire puisque range a déplacé le point :
{{- range $chemin, $_ := .Files.Glob "config/*.ini" }} {{ base $chemin }}: |{{ $.Files.Get $chemin | indent 4 }}{{- end }}Valider son chart
Section intitulée « Valider son chart »Trois commandes, du moins coûteux au plus engageant. helm lint lit vos fichiers, helm template montre ce qu'ils produisent, helm package fige le résultat dans une archive. Les enchaîner dans cet ordre attrape les erreurs au niveau où elles coûtent le moins cher à corriger, et les deux premières ne demandent aucun cluster.
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.
Le nom de l'archive suit une règle qui surprend au premier essai : il vient du champ name du Chart.yaml, jamais du nom du dossier. Un dossier appelé src dont le Chart.yaml déclare name: paquet produit donc paquet-0.1.0.tgz, et rien dans la sortie ne rappelle le dossier d'origine :
helm package ./srcLe piège apparaît au moment où l'on duplique un chart pour en dériver un second : copier mon-api/ vers mon-api-interne/ sans changer le name fait produire deux archives du même nom, et la seconde écrase la première sans un mot. Changez le name en même temps que le dossier, c'est le seul champ qui compte ici.
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 »Ces quatre symptômes se produisent au rendu, donc avant le moindre appel au cluster : ils se reproduisent à l'identique avec helm template, hors ligne. C'est une bonne nouvelle pour le diagnostic, puisque vous pouvez les rejouer autant de fois que nécessaire sans rien déployer.
| 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 »Ce lab construit un chart de zéro, sans passer par helm create, justement pour voir ce que le squelette cache. Vous écrirez le Chart.yaml, un values.yaml, un template et un helper, puis vous validerez le tout sans cluster. Comptez une vingtaine de minutes.
-
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
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Six questions sur l'écriture d'un template : le piège de portée de with, le rôle de required, ce qu'est un chart de type library, et ce que .Files refuse de lire.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Upgrade, rollback et cycle de vie : Ce que Helm stocke entre deux versions d'un même chart.
- Dépendances et subcharts : Composer un chart à partir d'autres charts, et gérer leurs values.
- Qualité, schéma et documentation : Rendre le chart lisible et vérifiable par quelqu'un d'autre.