Aller au contenu
Conteneurs & Orchestration medium

Anatomie d'un chart Helm : structure, templates et helpers

20 min de lecture

logo helm

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.

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
FichierRôleObligatoire ?
Chart.yamlIdentité du chart (nom, version, type)✅ Oui
values.yamlValeurs par défaut✅ Oui
templates/Templates Kubernetes✅ Oui
templates/_helpers.tplFonctions partagéesRecommandé
templates/NOTES.txtMessage post-installationRecommandé
charts/Dépendances téléchargéesSi dépendances
.helmignoreExclusions du packagingRecommandé

Helm peut générer un squelette complet :

Fenêtre de terminal
helm create mon-app

Résultat :

Creating mon-app

Structure générée :

  • Répertoiremon-app/
    • Chart.yaml
    • values.yaml
    • .helmignore
    • Répertoiretemplates/
      • _helpers.tpl
      • deployment.yaml
      • service.yaml
      • serviceaccount.yaml
      • hpa.yaml
      • ingress.yaml
      • NOTES.txt
      • Répertoiretests/
        • test-connection.yaml
apiVersion: v2
name: mon-api
description: Chart minimal pour une API REST
type: application
version: 0.1.0
appVersion: "1.0.0"

Décryptage de chaque champ :

ChampSignificationExemple
apiVersionVersion de l’API Chart (v2 pour Helm 3)v2
nameNom du chart (doit correspondre au dossier)mon-api
descriptionDescription courte pour la rechercheChart pour une API REST
typeapplication (déployable) ou library (réutilisable)application
versionVersion du chart (SemVer)0.1.0
appVersionVersion de l’application déployée"1.0.0"
apiVersion: v2
name: mon-api
description: Chart minimal pour une API REST
type: application
version: 0.1.0
appVersion: "1.0.0"
# Métadonnées supplémentaires
maintainers:
- name: Stéphane Robert
email: stephane@example.com
keywords:
- api
- rest
- demo
home: https://github.com/example/mon-api
sources:
- https://github.com/example/mon-api
icon: https://example.com/icon.png

Ces champs sont affichés dans Artifact Hub et facilitent la découverte du chart.

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.
# Configuration de l'image
image:
repository: ghcr.io/stefanprodan/podinfo
tag: "6.7.1"
pullPolicy: IfNotPresent
# Nombre de réplicas
replicaCount: 1
# Configuration du service
service:
type: ClusterIP
port: 9898
# Configuration applicative
config:
message: "Hello from Helm!"
logLevel: info
# Resources (à activer en production)
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 50m
# memory: 64Mi

Les templates Helm utilisent la syntaxe Go templates avec des extensions Sprig. Les délimiteurs sont {{ et }}.

Syntaxes de base :

SyntaxeEffet
{{ .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

Helm fournit plusieurs objets accessibles dans les templates :

ObjetContenu
.ValuesValeurs de values.yaml + surcharges
.ReleaseInformations sur la release (nom, namespace, revision)
.ChartContenu de Chart.yaml
.CapabilitiesCapacités du cluster (versions API)

Variables .Release disponibles :

# Exemple d'utilisation
metadata:
name: {{ .Release.Name }}-config # Nom de la release
namespace: {{ .Release.Namespace }} # Namespace cible
annotations:
revision: {{ .Release.Revision | quote }} # Numéro de révision

Variables .Chart disponibles :

labels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
apiVersion: apps/v1
kind: Deployment
metadata:
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 }}
apiVersion: v1
kind: Service
metadata:
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 }}

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.

{{/*
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 }}
SyntaxeUsage
{{ 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.

FonctionDescriptionExemple
quoteAjoute des guillemets{{ .Values.name | quote }}"valeur"
defaultValeur par défaut si vide{{ .Values.port | default 8080 }}
requiredErreur si valeur absente{{ required "port requis" .Values.port }}
FonctionDescriptionExemple
upper / lowerCasse{{ "hello" | upper }}HELLO
titlePremière lettre majuscule{{ "hello" | title }}Hello
replaceRemplacement{{ "a-b" | replace "-" "_" }}a_b
truncTronquer{{ "long" | trunc 3 }}lon
trimSuffixEnlever suffixe{{ "app-v1" | trimSuffix "-v1" }}app
FonctionDescriptionQuand l’utiliser
toYamlConvertir en YAMLObjets complexes (resources, env)
nindent NNouvelle ligne + indentationAprès toYaml ou include
indent NIndentation (sans nouvelle ligne)Rarement utilisé

Exemple complet :

{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}

Rendu :

resources:
limits:
cpu: 100m
memory: 128Mi
# 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 }}
Fenêtre de terminal
helm lint mon-api

Résultat (succès) :

==> Linting mon-api
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed

Avec --strict pour plus de rigueur :

Fenêtre de terminal
helm lint mon-api --strict

Les [INFO] deviennent des erreurs en mode strict.

Fenêtre de terminal
helm template ma-release mon-api

Cette commande génère le YAML sans l’appliquer au cluster. Idéal pour vérifier le rendu.

Options utiles :

OptionEffet
--debugAffiche les infos de debug
--show-only templates/deployment.yamlRend un seul template
-n productionSimule le namespace cible
--set replicaCount=3Surcharge une valeur

Exemple : vérifier un template spécifique

Fenêtre de terminal
helm template ma-release mon-api --show-only templates/deployment.yaml

Résultat :

mon-api/templates/deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
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: Helm
spec:
replicas: 1
...
Fenêtre de terminal
helm package mon-api

Résultat :

Successfully packaged chart and saved it to: /path/to/mon-api-0.1.0.tgz

Cette archive .tgz peut être publiée dans un registry.

Helm recommande d’utiliser les labels standard app.kubernetes.io/* :

LabelContenuExemple
app.kubernetes.io/nameNom de l’applicationmon-api
app.kubernetes.io/instanceNom de la releaseprod-mon-api
app.kubernetes.io/versionVersion de l’application1.0.0
app.kubernetes.io/componentComposant dans l’appfrontend
app.kubernetes.io/part-ofApplication parentemon-stack
app.kubernetes.io/managed-byOutil de gestionHelm
helm.sh/chartChart et versionmon-api-0.1.0

Ces labels permettent de filtrer et organiser les ressources dans le cluster.

SymptômeCause probableSolution
parse errorSyntaxe Go template invalideVérifier {{ }} et tirets
nil pointerAccès à une valeur inexistanteUtiliser default ou if
YAML invalideIndentation incorrecteUtiliser nindent avec la bonne valeur
Nom trop longDépasse 63 caractèresUtiliser trunc 63 | trimSuffix "-"
required échoueValeur obligatoire manquanteFournir la valeur avec --set

  1. Créer le squelette

    Fenêtre de terminal
    mkdir -p mon-api/templates
  2. Créer Chart.yaml

    Fenêtre de terminal
    cat > mon-api/Chart.yaml << 'EOF'
    apiVersion: v2
    name: mon-api
    description: Mon premier chart Helm
    type: application
    version: 0.1.0
    appVersion: "1.0.0"
    EOF
  3. Créer values.yaml

    Fenêtre de terminal
    cat > mon-api/values.yaml << 'EOF'
    image:
    repository: ghcr.io/stefanprodan/podinfo
    tag: "6.7.1"
    pullPolicy: IfNotPresent
    replicaCount: 1
    service:
    type: ClusterIP
    port: 9898
    config:
    message: "Hello from my first chart!"
    EOF
  4. 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
  5. Créer deployment.yaml

    Fenêtre de terminal
    cat > mon-api/templates/deployment.yaml << 'EOF'
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    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
  6. Créer service.yaml

    Fenêtre de terminal
    cat > mon-api/templates/service.yaml << 'EOF'
    apiVersion: v1
    kind: Service
    metadata:
    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
  7. Valider avec lint et template

    Fenêtre de terminal
    helm lint mon-api
    helm template test-release mon-api
  8. Installer et vérifier

    Fenêtre de terminal
    helm install mon-api-test mon-api -n default
    kubectl get all -l app.kubernetes.io/instance=mon-api-test
  9. Nettoyer

    Fenêtre de terminal
    helm uninstall mon-api-test

Critères de réussite :

  • helm lint passe sans erreur
  • helm template génère du YAML valide
  • Le chart s’installe et crée un pod Running
  • Les labels sont correctement appliqués

  • Un chart = Chart.yaml + values.yaml + templates/
  • helm create génère un squelette complet (utile en production)
  • _helpers.tpl centralise les fonctions réutilisables (labels, noms)
  • Utilisez include plutôt que template pour pouvoir piper
  • toYaml | nindent N est le pattern pour injecter des objets complexes
  • helm lint et helm template sont vos meilleurs amis pour débugger
  • Les labels app.kubernetes.io/* sont la convention recommandée

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.