Les modèles en profondeur
Pourquoi
Le chart de Signalements écrit à la leçon 4 n'utilise qu'une poignée de constructions. Dès que l'on veut aller plus loin (un bloc optionnel, une liste de variables d'environnement fournie par l'utilisateur, une annotation calculée), on rencontre le langage des modèles dans toute sa subtilité : un langage de gabarits de texte, pas de YAML, avec ses règles de portée, d'espaces et de types. La plupart des difficultés de Helm viennent de là. Un modèle qui « semble juste » produit un YAML mal indenté, une valeur absente devient un pointeur nul, un with change le sens du point et fait échouer un .Release.Name quelques lignes plus bas.
Cette leçon donne le modèle mental qui évite de deviner. Chaque exemple a été rendu avec helm template (Helm 3.16.3), et les erreurs montrées sont des messages réels. Le langage est le même avec Helm 4.
Les concepts
Un moteur de texte
Helm utilise le package text/template du langage Go, enrichi des fonctions de la bibliothèque Sprig et de quelques fonctions propres à Helm (include, required, toYaml, tpl, lookup...). Ce moteur ne connaît ni YAML, ni Kubernetes : il lit un texte, remplace les actions entre {{ et }} par leur résultat, et laisse le reste intact. Le résultat est un texte que Helm lit ensuite comme du YAML. Tout ce qui suit découle de cette séparation.
Les objets intégrés
À l'entrée d'un modèle, un objet racine, le point (.), donne accès à des données organisées en sous-objets :
| Objet | Contenu |
|---|---|
.Values | les valeurs fusionnées (défauts du chart, fichiers, --set) |
.Release | .Name, .Namespace, .IsInstall, .IsUpgrade, .Revision, .Service (vaut Helm) |
.Chart | le contenu de Chart.yaml : .Name, .Version, .AppVersion... (la première lettre en majuscule) |
.Capabilities | ce que le cluster sait faire : .KubeVersion.Version, .APIVersions.Has "...", .HelmVersion |
.Files | l'accès aux fichiers du chart autres que les modèles : .Files.Get "chemin", .Files.Glob, .Files.AsConfig |
.Template | .Name et .BasePath du modèle en cours |
Voyons-les ensemble avec un chart d'exemple dont le modèle est un ConfigMap :
# values.yaml
nom: signalements
port: 8000
actif: false
vide: ""
etiquettes:
equipe: voirie
cycle: prod
env:
- {name: LOG_LEVEL, value: info}
- {name: TZ, value: Europe/Paris}
modele: "https://{{ .Release.Name }}.formation.test"# templates/t.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
data:
release: {{ .Release.Name | quote }}
ns: {{ .Release.Namespace | quote }}
install: {{ .Release.IsInstall | quote }}
revision: {{ .Release.Revision | quote }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }}
appVersion: {{ .Chart.AppVersion | quote }}
kube: {{ .Capabilities.KubeVersion.Version | quote }}
helm: {{ .Capabilities.HelmVersion.Version | quote }}
gateway: {{ .Capabilities.APIVersions.Has "gateway.networking.k8s.io/v1" | quote }}
fichier: {{ .Files.Get "files/exemple.txt" | trim | quote }}
defaut: {{ .Values.vide | default "valeur-par-defaut" | quote }}
majuscules: {{ .Values.nom | upper | quote }}
port: {{ .Values.port | quote }}
tpl: {{ tpl .Values.modele . | quote }}Sortie réelle de helm template demo scratch -n formation :
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-scratch
data:
release: "demo"
ns: "formation"
install: "true"
revision: "1"
chart: scratch-0.1.0
appVersion: "1.2.0"
kube: "v1.31.0"
helm: "v3.16.3"
gateway: "false"
fichier: "mot: bonjour"
defaut: "valeur-par-defaut"
majuscules: "SIGNALEMENTS"
port: "8000"
tpl: "https://demo.formation.test"Plusieurs enseignements.
helm templateinvente son contexte : la release n'existe pas, donc.Release.IsInstallest vrai, la révision vaut 1, et.Capabilities.KubeVersionest une valeur par défaut de Helm (v1.31.0pour cette version, sans rapport avec votre cluster).gateway: "false"parce quehelm templatene sait pas quelles API le cluster expose. Pour tester un modèle qui dépend des capacités, il faut les fournir :--kube-version 1.36.0 --api-versions gateway.networking.k8s.io/v1, avec lequel la même ligne devientgateway: "true". Avechelm installsur un vrai cluster, ces valeurs sont lues sur le cluster.- Les noms des champs de
.Chartcommencent par une majuscule (.Chart.AppVersion), alors que les clés de.Valuesgardent la casse de votre fichier (.Values.replicaCount). La différence vient de ce que.Chartest une structure Go et.Valuesune table de clés libres. .Files.Getlit un fichier du chart (icifiles/exemple.txt, qui contientmot: bonjour) : utile pour embarquer un fichier de configuration dans un ConfigMap. Les fichiers listés dans.helmignore, et ceux detemplates/, ne sont pas accessibles.
Les pipelines
Comme dans un shell, le tube | envoie le résultat de gauche comme dernier argument de la fonction de droite : .Values.nom | upper | quote équivaut à quote (upper .Values.nom). La forme tube se lit de gauche à droite, ce qui est plus naturelle pour enchaîner plusieurs transformations.
Le point crucial est « dernier argument ». Pour default, dont la signature est default VALEUR_PAR_DEFAUT VALEUR, on écrit donc .Values.vide | default "défaut", soit default "défaut" .Values.vide : la valeur à tester vient en dernier, donc par le tube.
Les fonctions de tous les jours
Les fonctions viennent de Sprig et de Helm. Voici celles à connaître, avec des sorties réelles pour les moins évidentes :
| Fonction | Rôle | Exemple |
|---|---|---|
default | valeur de repli si la valeur est « vide » (nulle, "", 0, false, liste ou map vide) | .Values.tag | default .Chart.AppVersion |
required | fait échouer le rendu avec un message si la valeur est vide | required "tag obligatoire" .Values.tag |
quote, squote | entoure de guillemets doubles (simples) | .Values.nom | quote |
upper, lower, trim, trunc N, trimSuffix, replace | manipulations de chaînes | .Values.nom | trunc 5 |
printf | formatage à la manière de Go | printf "%s-%s" .Release.Name .Chart.Name |
toYaml, toJson, fromYaml | conversion entre structure et texte | toYaml .Values.resources |
indent N, nindent N | indente de N espaces (nindent ajoute d'abord un retour à la ligne) | toYaml .Values.x | nindent 4 |
include | rend un fragment nommé et le renvoie comme texte | include "chart.labels" . |
tpl | rend une chaîne comme un modèle | tpl .Values.modele . |
lookup | lit un objet du cluster | voir plus bas |
fail | arrête le rendu avec un message | fail "combinaison invalide" |
quote : pourquoi on l'utilise tant
Dans un fichier YAML, port: 8000 est un entier, actif: true un booléen, version: 1.20 un nombre à virgule, tag: 0123 un entier octal selon l'analyseur. Une valeur qui doit être une chaîne doit donc être citée : {{ .Values.tag | quote }}. C'est impératif pour les variables d'environnement (env.value doit être une chaîne : value: 8000 est refusé par l'API) et pour les étiquettes et annotations (leurs valeurs sont des chaînes). quote met des guillemets doubles et échappe ce qu'il faut.
toYaml et nindent
toYaml convertit une structure (map, liste) en texte YAML ; c'est l'outil pour insérer un bloc fourni par l'utilisateur (les ressources, les tolérances, les annotations). Le texte produit tient sur plusieurs lignes, et l'insertion dans un modèle demande de le réindenter à la profondeur de la clé :
etiquettes:
{{- toYaml .Values.etiquettes | nindent 4 }}nindent 4 commence par un retour à la ligne, puis indente chaque ligne de 4 espaces. Le {{- qui précède supprime l'espace et le saut de ligne avant l'action, pour que l'on n'obtienne pas une ligne vide. C'est la forme canonique à connaître par cœur.
include et template
Pour appeler un fragment défini avec define, il existe deux formes. La action template "nom" . insère le résultat directement dans le texte ; elle n'est pas une fonction, donc on ne peut pas lui appliquer de tube. La fonction include "nom" . renvoie le résultat comme une chaîne, que l'on peut filtrer : include "chart.labels" . | nindent 4. C'est pour cela que l'on utilise toujours include dans Helm. Le piège est classique : template "x" . | upper ne produit pas l'erreur attendue, parce que le moteur lit | upper comme un tube appliqué à l'argument du template. Sortie réelle :
$ helm template demo scratch
Error: template: scratch/templates/t.yaml:1:36: executing "scratch/templates/t.yaml" at <upper>: wrong type for value; expected string; got chartutil.Values
upper a reçu non pas le texte du fragment, mais le point (chartutil.Values), et refuse un non-texte. Avec include, le résultat est une chaîne et le tube agit sur lui.
Le deuxième argument d'include est le contexte passé au fragment : presque toujours le point (.), pour que le fragment voie .Values, .Release et le reste. Passer autre chose (.Values.etiquettes) restreint le fragment à cette donnée : utile à l'occasion, mais le fragment ne verra plus .Release.
tpl
Les valeurs de values.yaml sont du texte que Helm ne rend pas comme un modèle. Si un utilisateur écrit modele: "https://{{ .Release.Name }}.formation.test", {{ .Values.modele }} affiche littéralement https://{{ .Release.Name }}.formation.test. tpl rend la chaîne comme un modèle, avec le contexte donné : {{ tpl .Values.modele . }} produit https://demo.formation.test (voir la sortie plus haut). C'est utile pour laisser l'utilisateur composer des noms ou des annotations avec des références à la release, mais c'est aussi une porte d'exécution de modèles à partir de valeurs : voir la section Sécurité.
Le contrôle de flux
if
{{- if .Values.actif }}
actif: "oui"
{{- else }}
actif: "non"
{{- end }}La condition est « vraie » si la valeur n'est pas vide. Sont faux : false, 0, "", nil, une liste ou une map vide. Sont vrais : tout le reste, y compris la chaîne "false" (non vide) ! Une valeur passée par --set-string actif=false est la chaîne "false" et donc vraie : un piège concret. Sortie réelle de quelques tests (valeurs du chart d'exemple) :
data:
vide: vide
zero: faux
liste: oui
inconnu: non
eq: ok
et: y
taille: 2Les modèles étaient {{ if .Values.vide }}rempli{{ else }}vide{{ end }} (chaîne vide, donc faux), {{ if 0 }}..., {{ if .Values.env }}oui{{ end }} (liste non vide), {{ if .Values.absent }}... (clé inexistante : faux, sans erreur), {{ if eq .Values.nom "signalements" }}, et {{ if and .Values.actif (not .Values.vide) }} (actif vaut false). Les comparaisons sont des fonctions à préfixe : eq, ne, lt, gt, and, or, not. On écrit eq .Values.nom "x", pas .Values.nom == "x".
with et la portée du point
with teste une valeur et, si elle n'est pas vide, change la portée du point : à l'intérieur, . désigne cette valeur.
{{- with .Values.etiquettes }}
{{- toYaml . | nindent 4 }}
{{- end }}L'intérêt : ne rien produire si les étiquettes sont vides, et écrire . au lieu de .Values.etiquettes. Le revers : à l'intérieur du bloc, .Release.Name n'existe plus, parce que le point n'est plus l'objet racine. Message réel :
Error: template: scratch/templates/t.yaml:3:16: executing "scratch/templates/t.yaml" at <.Release.Name>: nil pointer evaluating interface {}.Name
Le remède est la variable $, qui désigne toujours l'objet racine, quel que soit l'endroit : {{ $.Release.Name }}. On la retrouve dans presque tous les range et with qui touchent à la release.
range
range répète un bloc pour chaque élément d'une liste ou d'une map. Le point désigne à chaque tour l'élément courant.
{{- range .Values.env }}
{{ .name }}: {{ .value | quote }}
{{- end }}
{{- range $cle, $val := .Values.etiquettes }}
etiquette-{{ $cle }}: {{ $val }}
{{- end }}Avec la forme $cle, $val :=, on obtient la clé (ou l'indice) et la valeur : pour une map, les clés sont parcourues dans l'ordre alphabétique, ce qui rend le rendu déterministe. Sortie réelle, dans le contexte complet ci-dessous.
Les variables
Une variable se déclare avec := et se réaffecte avec = :
{{- $nom := printf "%s-%s" .Release.Name .Chart.Name }}
nom: {{ $nom }}Elle vit jusqu'à la fin du bloc où elle est déclarée (if, with, range, ou le fichier). Les variables servent à garder une valeur que le changement de portée rendrait inaccessible, ou à nommer un calcul répété.
Les espaces : {{- et -}}
C'est la source d'erreurs la plus fréquente. Le moteur laisse intact tout le texte entre les actions, y compris les retours à la ligne et les espaces. Une ligne qui ne contient qu'une action {{ if ... }} produit une ligne vide dans le résultat. Pour l'éviter, un tiret accolé à l'accolade supprime les espaces (et les retours à la ligne) du côté du tiret : {{- supprime tout ce qui précède, jusqu'au dernier caractère non blanc ; -}} supprime tout ce qui suit. La syntaxe exige une espace entre le tiret et l'action : {{- if ...}}, pas {{-if.
Voici un modèle réunissant les constructions précédentes, et son rendu réel :
apiVersion: v1
kind: ConfigMap
metadata:
name: demo
labels:
{{- include "scratch.labels" . | nindent 4 }}
{{- with .Values.etiquettes }}
{{- toYaml . | nindent 4 }}
{{- end }}
data:
{{- if .Values.actif }}
actif: "oui"
{{- else }}
actif: "non"
{{- end }}
{{- range .Values.env }}
{{ .name }}: {{ .value | quote }}
{{- end }}
{{- range $cle, $val := .Values.etiquettes }}
etiquette-{{ $cle }}: {{ $val }}
{{- end }}
{{- with .Values.etiquettes }}
release-dans-with: {{ $.Release.Name }}
{{- end }}
{{- $nom := printf "%s-%s" .Release.Name .Chart.Name }}
nom: {{ $nom }}apiVersion: v1
kind: ConfigMap
metadata:
name: demo
labels:
app.kubernetes.io/name: scratch
app.kubernetes.io/instance: demo
cycle: prod
equipe: voirie
data:
actif: "non"
LOG_LEVEL: "info"
TZ: "Europe/Paris"
etiquette-cycle: prod
etiquette-equipe: voirie
release-dans-with: demo
nom: demo-scratchObservez que chaque ligne de contrôle commence par {{-, ce qui « colle » son résultat à la ligne précédente, et que l'indentation du texte produit ne dépend que des lignes de contenu. Les clés des étiquettes sortent triées (cycle avant equipe) bien que le fichier les ait dans l'ordre inverse : toYaml trie les clés d'une map.
lookup : lire le cluster
lookup apiVersion kind namespace nom lit un objet existant dans le cluster pendant le rendu, et renvoie une map (ou une liste de maps pour une recherche sans nom). Cas d'usage typique : ne pas régénérer un mot de passe aléatoire à chaque mise à jour, en réutilisant celui du Secret déjà créé.
Mais lookup ne fonctionne que si Helm parle réellement au cluster. La documentation le dit : Helm « is not supposed to contact the Kubernetes API Server during a helm template|install|upgrade|delete|rollback --dry-run operation », donc lookup y renvoie une map vide. Réel :
$ helm template demo scratch # avec existe: {{ lookup "v1" "ConfigMap" "formation" "x" | toJson }}
existe: {}
Pour le tester sans installer, la documentation indique d'utiliser --dry-run=server, qui contacte le serveur. Ce comportement a deux conséquences. Un modèle qui dépend de lookup doit toujours prévoir le cas d'une réponse vide ({{ if $secret }}...{{ else }}...{{ end }}). Et le rendu diffère entre helm template (sans cluster) et helm install (avec) : c'est un défaut pour la prévisibilité, et une raison de n'utiliser lookup qu'en dernier recours. Argo CD, qui rend les charts sans cluster à la manière de helm template, ne le résout pas non plus : un chart qui dépend de lookup se rend autrement sous Argo CD (leçon 10).
En pratique
La meilleure façon d'apprendre est de casser des modèles. Dans le chart d'exemple, placez ce fichier dans templates/t.yaml et lancez helm template demo . à chaque variante.
Une erreur à la fois
Indentation de include. Le fragment produit deux lignes ; avec indent au lieu de nindent, la première ligne se colle après {{, et l'indentation de la suivante est fausse :
metadata:
labels:
{{ include "scratch.labels" . | indent 4 }}Error: YAML parse error on scratch/templates/t.yaml: error converting YAML to JSON: yaml: line 3: did not find expected key
Use --debug flag to render out invalid YAML
Avec --debug, Helm affiche le texte produit et son erreur. La première ligne est décalée (4 espaces d'indentation de la ligne + 4 d'indent), la deuxième est à 4 : le YAML est invalide. Correctif : {{- include "scratch.labels" . | nindent 4 }}, avec le tiret et nindent. À noter que, dans un rendu où l'indentation serait seulement fausse mais pas invalide, aucune erreur n'apparaît : les clés atterrissent au mauvais niveau. Réel, avec un nindent 4 sans {{- dans un bloc déjà indenté :
annotations:
a: "1"
app.kubernetes.io/name: scratch
app.kubernetes.io/instance: demoUne ligne vide (inoffensive ici) et les étiquettes rangées parmi les annotations : l'objet est valide, et faux. Seule la relecture du rendu le révèle.
Pointeur nul. On écrit .Values.absent.profond alors que absent n'existe pas :
Error: template: scratch/templates/t.yaml:2:15: executing "scratch/templates/t.yaml" at <.Values.absent.profond>: nil pointer evaluating interface {}.profond
Lire .Values.absent donne nil sans erreur (c'est pourquoi if .Values.absent fonctionne), mais lire un champ de nil échoue. Le remède est de tester le parent ({{ if .Values.absent }}), ou d'utiliser dig ou default (dict) pour fournir une map de repli : (.Values.absent | default dict).profond.
Un type inattendu. {{ .Values.etiquettes }} sans toYaml ne produit pas du YAML mais la représentation Go d'une map :
data:
a: map[cycle:prod equipe:voirie]
b: [{"name":"LOG_LEVEL","value":"info"},{"name":"TZ","value":"Europe/Paris"}]La première ligne est invalide pour Kubernetes (un objet là où il attendait une chaîne). La seconde montre toJson, utile pour insérer un petit JSON dans une annotation ou un ConfigMap.
Déclencher un redéploiement quand la configuration change
Un cas d'usage célèbre combine tout ce qui précède. Un Deployment ne redémarre pas quand un ConfigMap change (leçon 5 du cours Kubernetes). Une technique courante : ajouter au modèle de pod une annotation qui contient l'empreinte du ConfigMap rendu, de sorte qu'un changement de configuration change le modèle, donc déclenche un remplacement des pods.
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}include rend le fichier configmap.yaml du chart comme texte, sha256sum le résume, et l'annotation change dès qu'un octet du ConfigMap rendu change. $.Template.BasePath donne le chemin du répertoire des modèles (signalements/templates). Le chart de Signalements n'a pas de ConfigMap pour l'instant ; la technique sera utile dès qu'il en aura un.
Sous le capot
Deux passes. Helm traite chaque fichier de templates/ en le passant au moteur avec l'objet racine, ce qui produit une chaîne ; il lit ensuite cette chaîne comme du YAML, ce qui produit les objets. Les erreurs de la première passe nomment le fichier et la ligne du modèle. Celles de la seconde nomment la ligne du texte rendu, que --debug permet de lire. La confusion entre les deux numéros de ligne est le piège de diagnostic le plus courant.
Les fonctions viennent de trois sources. Les fonctions standard de text/template (eq, and, len, printf, index), la bibliothèque Sprig (default, trunc, b64enc, dict...) et les ajouts de Helm (include, tpl, required, toYaml, lookup, fail). Le code de Helm (pkg/engine/funcs.go) retire de Sprig quelques fonctions qui accèdent à l'environnement du poste (env, expandenv), pour que le rendu ne dépende pas de la machine qui l'exécute et ne puisse pas en lire des secrets.
required et fail. Ce sont des fonctions qui retournent une erreur : le moteur arrête alors tout le rendu et Helm affiche le message. L'argument de required est évalué avant l'appel, ce qui compte pour required "..." (.Values.a | default .Values.b).
Pièges courants
default avec un booléen. .Values.actif | default true renvoie true même quand l'utilisateur a mis false, parce que false est « vide ». Pour un booléen avec défaut vrai, on teste l'existence de la clé : {{ if hasKey .Values "actif" }}... ou ternary. Mieux : on fixe actif: true dans values.yaml, et le modèle n'utilise plus default.
Les nombres d'un fichier de valeurs sont des flottants. Un entier lu dans un fichier YAML arrive dans le modèle en float64 : avec nombre: 1000000, {{ .Values.nombre }} affiche 1e+06, et 12345678901 affiche 1.2345678901e+10 (réel, Helm 3.16.3). {{ .Values.nombre | int }} ou printf "%d" rend l'entier. Les petits nombres (3, 8000) s'affichent normalement ; c'est avec les grands (délais en millisecondes, tailles en octets) que le piège se déclenche. Notez aussi que par --set, le même nombre reste un entier.
Les clés avec un tiret. .Values.mon-parametre est une erreur de syntaxe : le moteur lit mon moins parametre. On écrit index .Values "mon-parametre". Pour cette raison, les clés de valeurs sont en camelCase.
Sécurité
tplexécute des modèles issus des valeurs. Qui contrôle une valeur passée àtplcontrôle ce qui est rendu avec le contexte entier du chart : il lit.Values(donc tout ce qu'il contient), peut appelerlookup,include,fail. Si les valeurs viennent d'utilisateurs de confiance (votre dépôt de déploiement), le risque est faible ; si elles viennent d'un système tiers (un formulaire, une API), n'utilisez pastplsur elles.lookuplit le cluster avec vos droits. Un chart qui l'utilise peut lire des Secrets du cluster pendant le rendu. Relisez les modèles d'un chart tiers qui l'emploient.- Les valeurs injectées dans le YAML sont du texte. Une valeur sans
quote, contenant des guillemets, des retours à la ligne ou des deux-points, peut casser le YAML, voire y ajouter des champs : une injection de configuration. Citez systématiquement ce qui doit être une chaîne, ettoYamlce qui est une structure. - Pas de secret dans le rendu. Les manifestes rendus sont écrits dans le Secret de release (leçon 3) et affichés par
helm templateet--debug. Unb64encappliqué à un mot de passe en valeur n'est pas du chiffrement.
En production
- Garder les modèles simples. Un modèle qui contient dix imbrications de
ifest un programme, et s'écrit mal en texte. Déplacez la logique dans des fragments nommés, et le choix dans des valeurs claires. - Tester le rendu comme du code. Rendre le chart avec chaque jeu de valeurs réel (recette, production), conserver les rendus de référence, et comparer à chaque modification : le diff du rendu est la revue la plus utile. Des outils (
helm unittest, tests de rendu en CI) automatisent cette comparaison. - Rendre indépendant du cluster. Évitez
lookupet les décisions fondées sur.Capabilitiesquand c'est possible : Argo CD ethelm templatene voient pas les mêmes choses qu'unhelm install, et un rendu non déterministe rend une application perpétuellement désynchronisée (leçon 5 du cours GitOps).
Exercices
1. Prévoir un if (niveau 100). Pour chaque valeur de .Values.x, le bloc {{ if .Values.x }}A{{ else }}B{{ end }} produit-il A ou B ? Valeurs : true, "false", 0, [], "0", {}, nil.
Solution
A pour true, "false" (chaîne non vide) et "0" (chaîne non vide). B pour 0, [], {} et nil. La règle : seules les valeurs vides (false, 0, "", nil, liste ou map vide) sont fausses, et le moteur ne convertit pas les chaînes. Une valeur "false" passée par --set-string est donc vraie.
2. Corriger une portée (niveau 200). Ce fragment échoue avec nil pointer evaluating interface {}.Name. Pourquoi, et comment le corriger sans changer sa logique ?
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
release: {{ .Release.Name }}
{{- end }}Solution
Dans le range, le point est l'élément courant de la liste (une map avec name et value) : .Release n'existe pas à ce niveau. Remplacer .Release.Name par $.Release.Name, où $ est l'objet racine. Autre solution : déclarer {{- $release := .Release.Name }} avant le range et écrire {{ $release }}.
3. Rendre un bloc optionnel (niveau 200). Écrivez le fragment qui ajoute à metadata du Service un bloc annotations fourni par la valeur service.annotations (une map), et qui n'écrit rien du tout si la map est vide. Quelle sortie pour {service: {annotations: {exemple.com/a: "1"}}} ?
Solution
metadata:
name: {{ include "signalements.fullname" . }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}Pour la valeur donnée : annotations: suivi de exemple.com/a: "1" indenté de 4 espaces. Avec une map vide, le with ne produit rien : ni la ligne annotations:, ni ligne vide, grâce aux {{-. toYaml conserve les guillemets sur "1", ce qui est nécessaire pour une valeur d'annotation.
4. Où est l'erreur (niveau 200) ? Un collègue écrit image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }} et déclare que la version 1.20 n'est pas déployée correctement quand elle vient d'un fichier de valeurs avec tag: 1.20. Qu'obtient-il, pourquoi, et comment le corriger dans le fichier et dans le modèle ?
Solution
Dans un fichier YAML, 1.20 est un nombre à virgule flottante : il devient 1.2 à l'affichage, et l'image déployée est ...:1.2. Dans le fichier, il faut écrire tag: "1.20" avec des guillemets. Dans le modèle, on se protège avec {{ .Values.image.tag | default .Chart.AppVersion | toString }} ou, mieux, on documente l'obligation de guillemets dans values.yaml et l'on valide par un schéma (leçon 6) que tag est de type chaîne. Avec --set image.tag=1.20, en revanche, la valeur reste une chaîne "1.20" (le décodage de --set ne convertit pas les nombres à virgule) : le problème est propre aux fichiers.
Récapitulatif
- Le moteur de modèles produit du texte ; Helm le lit ensuite comme du YAML. Les erreurs de la première passe nomment la ligne du modèle, celles de la seconde la ligne du texte rendu (
--debugl'affiche). - Objets intégrés :
.Values,.Release,.Chart(champs en majuscule),.Capabilities,.Files,.Template.helm templateinvente le contexte : passez--kube-versionet--api-versionspour tester les capacités. - Le tube
|transmet comme dernier argument. À retenir :default,required,quote(indispensable pour les chaînes),toYaml | nindent N,include(une chaîne que l'on peut filtrer, contrairement àtemplate),tpl(rend une chaîne comme modèle). ifteste la non-vacuité :false,0,"",nil, liste et map vides sont faux ;"false"est vrai.withetrangechangent le point ;$désigne toujours la racine.{{-et-}}suppriment les espaces voisins ; chaque ligne de contrôle commence par{{-.lookuprenvoie une map vide avechelm templateet--dry-run, et ne lit le cluster qu'avec--dry-run=server,installouupgrade: à éviter, et à prévoir vide.- Pièges de sécurité :
tplsur des valeurs non fiables, absence dequote, secrets dans le rendu.
Pour aller plus loin
- Le Chart Template Guide de la documentation de Helm, qui suit pas à pas les mêmes notions avec d'autres exemples.
- La documentation du package
text/templatede Go, qui définit précisément les types, les comparaisons et les règles d'espaces, et celle de Sprig pour le catalogue complet des fonctions. - Le code de
pkg/enginedanshelm/helm, en particulierfuncs.go, pour voir ce que Helm ajoute et retire. - La leçon suivante, Valeurs, schéma et fonctions d'aide (leçon 6), qui valide les valeurs par un schéma plutôt que par
requiredseul.