Valeurs, schéma et fonctions d'aide
Pourquoi
Un chart a deux faces. À l'intérieur, des modèles qui produisent des manifestes. À l'extérieur, un fichier values.yaml : c'est l'interface que les autres équipes, les pipelines et Argo CD utilisent sans jamais lire les modèles. Un chart dont les valeurs sont mal pensées se paie à chaque usage : on ne sait pas quoi surcharger, on se trompe de clé, on découvre à la mise en production que la surcharge a été ignorée.
Ce dernier cas est le plus insidieux. Par défaut, Helm n'a aucune opinion sur les valeurs que vous lui passez : une clé inconnue est acceptée sans un mot. Voici le chart de Signalements sans schéma, rendu avec une faute de frappe, replicas au lieu de replicaCount :
$ helm template s sans-schema --set replicas=3 | grep replicas
replicas: 2
Aucune erreur, aucun avertissement, et le Deployment garde ses deux répliques. La personne qui a écrit replicas=3 croit avoir trois pods. Dans un dépôt GitOps où le changement passe par une revue, la faute a toutes les chances de franchir la relecture : elle a l'air plausible.
Cette leçon règle le problème à la source, en trois gestes. D'abord, concevoir le fichier de valeurs comme une API : structure, noms, commentaires, défauts sûrs. Ensuite, la verrouiller avec un schéma JSON, que Helm applique à chaque helm lint, template, install et upgrade. Enfin, factoriser dans des fonctions d'aide tout ce qui se répète dans les modèles (noms, étiquettes, sélecteurs, image), pour qu'une règle de nommage ne vive qu'à un endroit. La leçon se termine sur la question que personne ne se pose avant le premier incident : quand vous modifiez values.yaml dans une nouvelle version du chart, qu'est-ce qui casse pour ceux qui l'utilisent ?
Le fil rouge est le chart signalements de la leçon 4, dans la version 0.3.0 : Deployment, Service, ExternalSecret, Job de migration, HTTPRoute, et un pod de test. Les mécanismes de modèles (include, toYaml, default, nindent) sont ceux de la leçon 5 ; on s'en sert ici sans les redéfinir.
Les concepts
Les valeurs comme interface
Helm construit l'objet .Values que les modèles lisent en fusionnant plusieurs sources, de la moins prioritaire à la plus prioritaire :
- le
values.yamldu chart ; - si le chart est un sous-chart, les valeurs que son parent lui donne (leçon 7) ;
- les fichiers passés avec
-f/--values, dans l'ordre de la ligne de commande (le dernier gagne) ; - les valeurs passées avec
--set,--set-string,--set-file.
La fusion se fait clé par clé, en profondeur pour les tables, mais par remplacement pour les listes : surcharger une liste, c'est la réécrire entièrement. Cette asymétrie guide plusieurs choix de conception plus bas. Une valeur à null supprime la clé de la table fusionnée, ce qui permet de retirer une valeur par défaut (la section « Sous le capot » en donne un effet observé).
Le fichier values.yaml joue alors un triple rôle. Il fournit les valeurs par défaut, il documente ce qui est configurable, et il fixe la forme attendue : un utilisateur qui veut changer le port du Service va chercher service.port parce que le fichier le lui montre. C'est pourquoi la documentation de Helm recommande que chaque clé configurable y apparaisse, même vide.
Concevoir la structure
Les recommandations de Helm (Best Practices) et l'expérience convergent sur quelques règles.
Des noms en camelCase, commençant par une minuscule : replicaCount, pullPolicy. Les noms avec tiret (replica-count) obligent à écrire index .Values "replica-count" dans les modèles, et ceux qui commencent par une majuscule se confondent avec les champs internes de Helm (.Chart, .Release).
Des tables plutôt que des clés à plat, dès qu'un groupe de réglages va ensemble : image.repository, image.tag, image.pullPolicy plutôt que imageRepository, imageTag. Une table se surcharge ou se désactive d'un bloc, et le schéma peut la décrire. Mais pas trop profond : la documentation de Helm observe que les valeurs plates sont plus simples à utiliser, et que chaque niveau d'imbrication oblige les modèles à tester l'existence du niveau précédent. Deux niveaux couvrent presque tous les cas.
Des tables plutôt que des listes quand l'utilisateur doit pouvoir modifier un élément. Avec une liste, --set env[0].value=x désigne un élément par sa position, et le moindre ajout en tête décale tout. Avec une table (env.APP_LOG_LEVEL: info), chaque entrée a un nom stable, se surcharge isolément, et se supprime par null. Les listes restent justifiées quand l'ordre compte ou que l'on remplace l'ensemble (la commande de la migration, par exemple).
Un interrupteur enabled pour tout objet optionnel. route.enabled, externalSecret.enabled, migration.enabled : le modèle correspondant commence par {{- if .Values.route.enabled }}. C'est la convention que tout le monde reconnaît.
Des booléens et des nombres vrais, des chaînes quand ce sont des chaînes. Les erreurs de typage YAML sont un classique : tag: 1.10 est lu comme le nombre 1,1, et enabled: yes comme un booléen selon l'analyseur. Mettez entre guillemets ce qui doit rester une chaîne (tag: "1.10"), et laissez le schéma vérifier le type.
Un défaut qui fonctionne et qui ne surprend pas. Un chart qui s'installe avec helm install sans aucune valeur doit produire quelque chose de sain. Cela ne veut pas dire « exposé » : route.enabled: false par défaut, parce qu'exposer une application sur Internet est une décision, pas un état initial.
Des valeurs par défaut sûres
« Sûr » répond à une question : que se passe-t-il si l'utilisateur oublie la clé ? Un défaut raisonnable (replicaCount: 2, une limite mémoire) quand une valeur sensée existe. Un défaut vide qui échoue tôt (hostname: "", avec une règle qui refuse le rendu si la route est activée sans nom d'hôte). Jamais un défaut dangereux : mot de passe changeme, image latest, securityContext désactivé. Un défaut est copié partout, y compris en production par quelqu'un qui n'a pas relu.
Le schéma de valeurs
Un schéma de valeurs est un fichier values.schema.json placé à la racine du chart, à côté de values.yaml. Il est écrit en JSON Schema, un langage standard de description de documents JSON (YAML étant lu en JSON avant validation). Helm l'applique sur les valeurs finales, une fois toutes les sources fusionnées, à chaque commande qui rend le chart : helm lint, helm template, helm install et helm upgrade.
Les mots-clés dont vous avez besoin tiennent en une courte liste :
| Mot-clé | Rôle |
|---|---|
type | object, string, integer, number, boolean, array, null |
properties | décrit chaque clé connue d'un objet |
required | liste des clés obligatoires (par défaut, aucune ne l'est) |
additionalProperties | false interdit toute clé non listée dans properties ; le garde-fou contre les fautes de frappe |
enum | liste fermée de valeurs possibles |
minimum, maximum, minLength, pattern | bornes numériques, longueur, expression régulière |
items | schéma des éléments d'un tableau |
if / then / else | règles conditionnelles : « si la route est activée, le nom d'hôte est obligatoire » |
Deux réserves, tirées de la documentation de JSON Schema. D'une part, additionalProperties ne reconnaît que les propriétés déclarées dans le même sous-schéma : on ne peut pas l'utiliser de façon fiable à travers allOf. D'autre part, required ne dit rien du contenu : une clé présente avec une chaîne vide satisfait required, d'où le minLength: 1 qui l'accompagne souvent.
Les fonctions d'aide
Les modèles d'un chart répètent les mêmes fragments : le nom des objets, le bloc d'étiquettes, la référence de l'image. Helm les rassemble dans des modèles nommés (named templates), définis avec define dans un fichier dont le nom commence par un tiret bas, par convention templates/_helpers.tpl. Les fichiers qui commencent par _ sont chargés mais ne produisent aucun manifeste. On appelle ensuite un modèle nommé avec include (qui renvoie une chaîne, que l'on peut passer à nindent), et jamais avec template (qui écrit directement, sans possibilité de filtrer le résultat).
Les noms de modèles nommés sont globaux à toute la release, sous-charts compris : deux charts qui définissent chacun un labels se marcheraient dessus. D'où la convention de les préfixer par le nom du chart, signalements.labels.
Valeurs globales
La table global est visible de tous les charts d'une release : celui du haut et chacun de ses sous-charts, sous le nom .Values.global. Une valeur ordinaire ne descend que dans le sous-chart qui porte son nom. On réserve global à ce qui est vraiment transversal (nom de l'environnement, registre d'images commun) : tout ce qui s'y trouve est un contrat entre le parent et tous ses enfants.
En pratique
Le fichier de valeurs de Signalements
Voici le values.yaml du chart, tel qu'il est dans la version 0.3.0 (les commentaires font partie de l'interface) :
# Image de l'application. Sans tag, le chart utilise appVersion.
image:
repository: ghcr.io/lyneko-formation/signalements
tag: ""
pullPolicy: IfNotPresent
# Nombre de pods. Au moins 2 : un déploiement ne doit pas couper le service.
replicaCount: 2
service:
port: 80
# Ressources par pod. Les requêtes servent à l'ordonnancement, la limite mémoire protège le nœud.
resources:
requests: {cpu: 50m, memory: 128Mi}
limits: {memory: 256Mi}
# Secret externe contenant DATABASE_URL, lu par External Secrets Operator.
externalSecret:
enabled: true
storeName: scaleway-secret-manager
remoteKey: signalements-database-url
# Exposition par Gateway API. Désactivée par défaut : on n'expose rien sans le demander.
route:
enabled: false
hostname: ""
gateway: {name: "", namespace: ""}
# Migration de base de données exécutée avant chaque mise à jour.
migration:
enabled: true
command: ["python", "-m", "signalements.migrer"]Relisez-le avec les règles ci-dessus : tables, enabled pour les objets optionnels, pas de latest (le champ tag vide retombe sur appVersion, 1.2.0), deux répliques, limite mémoire sans limite de CPU, et aucun secret : le chart ne connaît que le nom du secret distant, que l'ExternalSecret va chercher dans Secret Manager (Scaleway en pratique, leçon 8).
La commande helm show values affiche ce fichier tel quel, commentaires compris, y compris pour un chart publié que vous n'avez pas cloné. C'est la première chose à lire avant d'utiliser un chart.
$ helm show values signalements | head -5
# Image de l'application. Sans tag, le chart utilise appVersion.
image:
repository: ghcr.io/lyneko-formation/signalements
tag: ""
pullPolicy: IfNotPresent
Le schéma
Le fichier values.schema.json de Signalements. Il est un peu long, parce qu'il décrit chaque clé :
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["image", "replicaCount", "service"],
"additionalProperties": false,
"properties": {
"global": {"type": "object"},
"image": {
"type": "object",
"required": ["repository"],
"additionalProperties": false,
"properties": {
"repository": {"type": "string", "minLength": 1},
"tag": {"type": "string"},
"pullPolicy": {"enum": ["Always", "IfNotPresent", "Never"]}
}
},
"replicaCount": {"type": "integer", "minimum": 1},
"service": {
"type": "object",
"additionalProperties": false,
"properties": {"port": {"type": "integer", "minimum": 1, "maximum": 65535}}
},
"resources": {"type": "object"},
"externalSecret": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {"type": "boolean"},
"storeName": {"type": "string"},
"remoteKey": {"type": "string"}
}
},
"route": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {"type": "boolean"},
"hostname": {"type": "string"},
"gateway": {"type": "object"}
},
"if": {"properties": {"enabled": {"const": true}}},
"then": {"required": ["hostname"], "properties": {"hostname": {"minLength": 1}}}
},
"migration": {
"type": "object",
"properties": {
"enabled": {"type": "boolean"},
"command": {"type": "array", "items": {"type": "string"}}
}
}
}
}Quelques choix à relever. additionalProperties: false est posé à la racine et sur chaque table dont on connaît toutes les clés : c'est lui qui attrape replicas. Il n'est pas posé sur resources ni gateway, dont le contenu est un fragment de l'API Kubernetes que le chart ne veut pas redécrire. La clé global est déclarée : on verra plus bas pourquoi son absence casserait le chart dès qu'un parent lui passe une valeur globale. Enfin, if / then exprime la règle « si route.enabled vaut true, alors hostname est obligatoire et non vide ».
Ce que Helm refuse
Rejouons les erreurs. D'abord la faute de frappe de l'introduction, cette fois avec le schéma :
$ helm template s signalements --set replicas=3
Error: values don't meet the specifications of the schema(s) in the following chart(s):
signalements:
- (root): Additional property replicas is not allowed
Une borne :
$ helm template s signalements --set replicaCount=0
Error: values don't meet the specifications of the schema(s) in the following chart(s):
signalements:
- replicaCount: Must be greater than or equal to 1
Un type et une énumération, dans la même passe (Helm rapporte toutes les erreurs, pas seulement la première) :
$ helm template s signalements --set image.pullPolicy=Sometimes --set service.port=http
Error: values don't meet the specifications of the schema(s) in the following chart(s):
signalements:
- image.pullPolicy: image.pullPolicy must be one of the following: "Always", "IfNotPresent", "Never"
- service.port: Invalid type. Expected: integer, given: string
La règle conditionnelle :
$ helm template s signalements --set route.enabled=true
Error: values don't meet the specifications of the schema(s) in the following chart(s):
signalements:
- route: Must validate "then" as "if" was valid
- route.hostname: String length must be greater than or equal to 1
Et un cas qui surprend. --set image.tag=12 est un nombre pour Helm, pas une chaîne : le schéma l'attrape, là où un modèle sans schéma aurait produit signalements:12 sans broncher (et, avec 1.10, un tag silencieusement faux si le YAML l'avait lu comme 1.1).
$ helm template s signalements --set image.tag=12
Error: values don't meet the specifications of the schema(s) in the following chart(s):
signalements:
- image.tag: Invalid type. Expected: string, given: integer
La correction est --set-string image.tag=12, ou des guillemets dans le fichier de valeurs.
helm lint applique le même schéma et se termine par un code de sortie non nul, ce qui en fait un contrôle de CI :
$ helm lint signalements --set replicaCount=0
==> Linting signalements
[INFO] Chart.yaml: icon is recommended
[ERROR] values.yaml: - replicaCount: Must be greater than or equal to 1
[ERROR] templates/: values don't meet the specifications of the schema(s) in the following chart(s):
signalements:
- replicaCount: Must be greater than or equal to 1
Error: 1 chart(s) linted, 1 chart(s) failed
$ echo $?
1
Le schéma ne dit pas tout : pour une règle qu'il exprime mal, la fonction required ({{ required "message" .Values.cle }}) fait échouer le rendu avec le message de votre choix.
Les fonctions d'aide de Signalements
Le fichier templates/_helpers.tpl définit quatre modèles nommés :
{{/* Nom complet : le nom de release, sauf s'il contient déjà le nom du chart. */}}
{{- define "signalements.fullname" -}}
{{- if contains .Chart.Name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{/* Étiquettes de sélection : stables pour toute la vie de la release. */}}
{{- define "signalements.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{/* Étiquettes communes : sélection + informations qui peuvent changer. */}}
{{- define "signalements.labels" -}}
{{ include "signalements.selectorLabels" . }}
app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
{{- end -}}
{{/* Référence d'image : tag explicite, sinon appVersion. */}}
{{- define "signalements.image" -}}
{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
{{- end -}}Chacun répond à une raison précise.
Le nom complet. Les noms d'objets Kubernetes sont limités, et ceux des Services à 63 caractères (ils deviennent des étiquettes DNS). trunc 63 | trimSuffix "-" coupe proprement. Le nom est construit à partir du nom de la release pour que deux installations du même chart dans un même espace de noms ne se heurtent pas (recette-signalements, demo-signalements). Le test contains évite signalements-signalements quand la release s'appelle déjà signalements. Observez-le sur le rendu avec la release s :
$ helm template s signalements | grep -m1 "name: s-"
name: s-signalements
Les étiquettes de sélection, séparées des autres. C'est la distinction la plus importante du fichier. Le champ spec.selector d'un Deployment est immuable (leçon 5 du cours Kubernetes) : changez-le, et helm upgrade échoue (field is immutable). Les étiquettes de sélection ne doivent donc contenir que ce qui ne bougera jamais pendant la vie de la release : le nom du chart et le nom de l'instance. La version, elle, change à chaque mise à jour : elle va dans les étiquettes communes, jamais dans le sélecteur. Le rendu confirme la séparation :
$ helm template s signalements --show-only templates/service.yaml | sed -n 4,16p
name: s-signalements
labels:
app.kubernetes.io/name: signalements
app.kubernetes.io/instance: s
app.kubernetes.io/version: "1.2.0"
app.kubernetes.io/managed-by: Helm
helm.sh/chart: signalements-0.3.0
spec:
selector:
app.kubernetes.io/name: signalements
app.kubernetes.io/instance: s
ports:
- {name: http, port: 80, targetPort: http}
Le bloc labels porte les cinq étiquettes, le selector seulement les deux stables. Ce sont les étiquettes recommandées par Kubernetes (app.kubernetes.io/*), plus helm.sh/chart que la documentation de Helm conseille de poser sur chaque objet.
La référence d'image. Elle apparaît dans le Deployment, dans le Job de migration et dans le pod de test. Si chacun la reconstruisait, le jour où l'on passerait au déploiement par empreinte (@sha256:...), il faudrait modifier trois fichiers. Avec signalements.image, c'est un seul : le Job de migration utilise forcément la même image que l'application, ce qui est exactement la garantie recherchée.
Les modèles s'en servent ainsi, dans le Deployment :
image: {{ include "signalements.image" . | quote }}Le point après le nom est le contexte passé à la fonction. On passe . (la racine : .Values, .Chart, .Release). Oublier ce second argument, ou passer .Values à la place, est la cause d'erreur la plus courante : le modèle nommé ne voit alors plus .Chart ni .Release, et échoue avec nil pointer evaluating interface {}.Name.
Les valeurs globales
Le chart de Signalements ne déclare pas de valeurs globales, mais il doit les tolérer. Si un chart parent lui passe global.environnement, le schéma sans la clé global répond :
$ helm template s signalements --set global.environnement=recette
Error: values don't meet the specifications of the schema(s) in the following chart(s):
signalements:
- (root): Additional property global is not allowed
C'était le défaut de la première version du schéma de ce cours, avant l'ajout de "global": {"type": "object"} : un additionalProperties: false à la racine rejette global, que Helm injecte pourtant chez tous les charts. La règle est simple : tout chart qui peut un jour devenir un sous-chart, ou recevoir global, déclare la clé global dans son schéma. La leçon suivante en montre l'usage côté parent.
Documenter et faire évoluer les valeurs
Les commentaires de values.yaml sont la documentation de base : commencez chacun par le nom du paramètre (pour le retrouver avec grep), et dites pourquoi plutôt que quoi. L'outil helm-docs génère à partir de commentaires au format # -- un README.md avec le tableau des paramètres, ce qui évite que la documentation dérive.
Les valeurs sont aussi une API : les modifier a des conséquences pour ceux qui les utilisent.
Les valeurs sont une API : les modifier a des conséquences pour ceux qui les utilisent. Appliquez à values.yaml ce que la leçon 9 dit du versionnement sémantique du chart. Voici un classement pratique.
Changement dans values.yaml | Compatible ? | Version du chart |
|---|---|---|
| Ajouter une clé optionnelle avec un défaut qui conserve le comportement précédent | oui | mineure |
| Ajouter une valeur à une énumération | oui | mineure |
Changer un défaut (replicaCount de 2 à 3) | comportement différent à la prochaine mise à jour | mineure avec note, ou majeure si risqué |
Renommer une clé (replicas en replicaCount) | non : l'ancienne surcharge est ignorée, ou rejetée par le schéma | majeure |
| Supprimer une clé | non | majeure |
Changer le type (port: "80" en port: 80) | non | majeure |
Resserrer le schéma (nouveau required, minimum plus haut) | non pour qui n'avait pas la valeur | majeure |
Sous le capot
Comment la fusion se passe. Au rendu, Helm part du values.yaml du chart, fusionne par-dessus les fichiers -f puis les --set, et produit une table unique. Pour chaque sous-chart, il calcule aussi sa propre vue : les valeurs du parent situées sous la clé portant le nom (ou l'alias) du sous-chart, plus les valeurs de global, plus les valeurs par défaut du sous-chart. La valeur null d'une clé la retire de la fusion : c'est ainsi que --set image.repository=null supprime la clé, et le schéma répond alors image: repository is required, parce que required a vu la clé disparaître avant la validation.
Quand le schéma est vérifié. La validation porte sur les valeurs fusionnées, avant l'évaluation des modèles. D'après la documentation, elle s'applique à install, upgrade, lint et template, et aux valeurs finales de tous les sous-charts. Chaque chart est validé avec son schéma, sur sa vue des valeurs ; un sous-chart qui apporte un schéma strict pose donc ses règles à ce que le parent lui transmet. Le fichier voyage dans le paquet : helm package l'embarque (il figure dans la liste du .tgz, juste après values.yaml), et le destinataire du chart bénéficie des mêmes contrôles que vous.
Quelle version de JSON Schema ? Le schéma déclare draft-07, la version des exemples de la documentation de Helm ; if / then y fonctionnent, comme on l'a vu. Les mots-clés plus récents (unevaluatedProperties, du brouillon 2019-09) ne sont pas garantis : testez-les avec helm lint. Helm 4 n'est pas installé pour ce cours, vérifiez ce point sur votre version.
Le contexte d'un modèle nommé. Un modèle nommé n'hérite de rien : il reçoit ce qu'on lui passe, et seulement cela. Pour y accéder à .Chart, .Release et .Values, on passe . depuis la racine ; dans une boucle range, . est l'élément courant, et il faut utiliser $ (la racine, conservée par Go templates) : include "signalements.fullname" $. Les fonctions d'aide d'un chart de bibliothèque (leçon 7) reçoivent le contexte du chart appelant, ce qui explique pourquoi .Chart.Name y désigne le chart appelant, pas la bibliothèque.
Pièges courants
Un schéma qui rejette global. Vu plus haut : Additional property global is not allowed. Déclarez global dès le départ.
Un schéma trop strict sur ce qui est un fragment Kubernetes. Interdire des clés inconnues sur resources ou affinity vous obligerait à recopier l'API Kubernetes dans le schéma, et à le mettre à jour à chaque version. Laissez ces tables ouvertes ("type": "object"), et laissez le serveur d'API valider.
Des chaînes lues comme des nombres. tag: 1.10, version: 3.0, port: "8000" dans un champ numérique. Le schéma "type": "string" attrape la première, "type": "integer" la dernière. Sans schéma, ces erreurs passent jusqu'au cluster.
Mettre la version dans le sélecteur. app.kubernetes.io/version dans selector.matchLabels : la première mise à jour échoue avec spec.selector: Invalid value ... field is immutable. Séparez toujours selectorLabels et labels.
Oublier le point. {{ include "signalements.labels" }} sans contexte échoue avec wrong number of args for include: want 2 got 1 ; {{ include "signalements.labels" .Values }} échoue avec nil pointer evaluating interface {}.Name (réelle sortie, au passage de .Chart.Name).
Sécurité
Aucun secret dans values.yaml. Les valeurs se retrouvent dans l'historique Git, dans les journaux de la CI, dans la sortie de helm get values, et dans le secret de release stocké dans le cluster. Une valeur comme database.password y est lisible par quiconque peut lire les secrets de l'espace de noms. Le chart de Signalements ne porte que le nom d'un secret externe ; le mot de passe vit dans Secret Manager et arrive par un ExternalSecret.
Des défauts qui durcissent, pas qui ouvrent. route.enabled: false, un secret externe, un utilisateur non privilégié : l'utilisateur doit devoir choisir d'affaiblir. Le schéma peut y aider, par un pattern qui exige un tag de la forme ^[0-9]+\.[0-9]+\.[0-9]+$ (donc pas de latest).
En production
Le schéma en CI, avant toute publication. helm lint --strict et helm template avec chaque fichier de valeurs réellement déployé (recette, préproduction, production) : un schéma sans cette boucle ne protège que des erreurs imaginaires. Le dépôt de déploiement de Lyneko rend chaque environnement dans la CI avant de fusionner (leçon 8 pour les tests plus larges).
Une interface stable, versionnée avec le chart. Les environnements ne changent pas au même rythme que le chart : la production reste sur 0.3.x pendant que la recette teste 0.4.0. Une règle de compatibilité (tableau plus haut) et un journal des changements permettent de mettre à jour sans relire chaque modèle.
Exercices
Exercice 1 : le garde-fou
Ajoutez à values.yaml une clé autoscaling.enabled (booléen, faux par défaut) et autoscaling.maxReplicas (entier). Écrivez la partie du schéma qui impose maxReplicas >= 2 quand autoscaling.enabled est vrai, et qui interdit toute autre clé dans autoscaling. Quelle commande vérifie que le chart refuse --set autoscaling.enabled=true sans maxReplicas ?
Solution
Dans values.yaml : autoscaling: {enabled: false, maxReplicas: 5} (un défaut présent évite que la règle conditionnelle se déclenche avec la clé absente). Dans le schéma :
"autoscaling": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {"type": "boolean"},
"maxReplicas": {"type": "integer", "minimum": 1}
},
"if": {"properties": {"enabled": {"const": true}}},
"then": {"required": ["maxReplicas"], "properties": {"maxReplicas": {"minimum": 2}}}
}Vérification : helm template s signalements --set autoscaling.enabled=true --set autoscaling.maxReplicas=1 doit échouer avec Must validate "then" as "if" was valid, et la même commande avec maxReplicas=3 doit réussir. Notez que si le défaut dans values.yaml fournit déjà maxReplicas: 5, la clé n'est jamais absente : pour tester le cas « absente », passez --set autoscaling.maxReplicas=null.
Exercice 2 : un renommage sans casse
Vous voulez renommer replicaCount en replicas dans la version 0.4.0, sans casser les dépôts de déploiement qui utilisent encore l'ancien nom. Quels changements faites-vous dans le modèle, le schéma et le numéro de version, et quand pouvez-vous supprimer l'ancien nom ?
Solution
Dans le Deployment : replicas: {{ .Values.replicas | default .Values.replicaCount | default 2 }}. Dans values.yaml, la nouvelle clé n'a pas de défaut (elle est documentée en commentaire, # replicas: 2), et replicaCount n'en a plus non plus : si replicas avait un défaut, il l'emporterait toujours sur l'ancienne clé, et les dépôts qui surchargent encore replicaCount seraient ignorés en silence. Le default 2 final porte la valeur par défaut. Dans le schéma, les deux clés sont déclarées, l'ancienne avec une description « dépréciée, utilisez replicas ». Version : 0.4.0 (mineure) car l'ancien nom reste accepté. L'ancien nom est supprimé, du modèle et du schéma, dans la version majeure suivante (1.0.0), après avoir annoncé la date dans le journal des changements et vérifié par recherche dans les dépôts de déploiement qu'aucun n'utilise plus replicaCount. À cette version majeure, le schéma rejette replicaCount avec Additional property replicaCount is not allowed, ce qui transforme l'oubli en erreur claire.
Récapitulatif
values.yamlest l'interface du chart : tables en camelCase,enabledpour tout objet optionnel, commentaires, défauts qui fonctionnent et qui n'affaiblissent rien.- Sans schéma, une clé inconnue est ignorée en silence (
replicasau lieu dereplicaCount).values.schema.jsonla rejette, contrôle types, bornes, énumérations et dépendances entre clés (if/then), à chaquelint,template,installetupgrade. additionalProperties: falseattrape les fautes de frappe, mais doit prévoirglobal; on laisse ouverts les fragments de l'API Kubernetes._helpers.tplfactorise le nom (borné à 63 caractères), les étiquettes de sélection (stables, immuables), les étiquettes communes (avec la version) et la référence d'image. On appelle avecincludeet le contexte..globaltraverse tous les sous-charts ; tout le reste ne descend que sous le nom du sous-chart.- Les valeurs sont une API versionnée : ajouter est compatible, renommer, supprimer, changer un type ou resserrer le schéma impose une version majeure.
- Aucun secret dans les valeurs : un chart porte le nom d'un secret externe, pas son contenu.
Pour aller plus loin
- Helm, Best Practices : Values et Labels and Annotations, pour les conventions complètes.
- JSON Schema, Understanding JSON Schema :
if/then,enum,pattern,$defspour factoriser un schéma. - helm-docs, pour générer le tableau des paramètres.
- Dans ce cours : Dépendances et bibliothèques (valeurs passées aux sous-charts, chart de bibliothèque pour partager ces fonctions d'aide) et Publier et signer un chart (versionnement sémantique d'un chart).
Sources
- Helm, Values Files (Chart Template Guide)
- Helm, Charts : schema files, valeurs globales, dépendances
- Helm, Best Practices : Values (nommage, structure, documentation)
- Helm, Best Practices : Labels and Annotations
- Helm, Named Templates (_helpers.tpl, include, partials)
- JSON Schema, Understanding JSON Schema : object (properties, required, additionalProperties)
- Kubernetes, Recommended Labels
- helm-docs, génération de la documentation des valeurs