Module 3y · Le déclaratif - écrire ses manifestes (YAML)¶
Objectifs
- Lire du YAML sans l'avoir jamais appris : trois briques, pas une de plus.
- Connaître les rubriques d'un manifeste et savoir dire à quoi sert chaque ligne.
- Écrire un Pod, un Deployment + Service, un ConfigMap injecté - en partant d'un squelette, comme dans la vraie vie.
- Savoir lire et corriger un manifeste cassé (le quotidien de l'ops).
- Durée : ~1 h 30 · Pré-requis : Module 3.
Pourquoi le déclaratif¶
Jusqu'ici on a tout fait à la main (kubectl run, create, scale…) : c'est l'impératif,
parfait pour explorer vite. Mais rien n'en reste : ce que tu as déployé n'existe que dans ta
tête et dans ton historique de commandes.
En production on DÉCRIT l'état voulu dans un fichier qu'on applique : c'est le déclaratif. Le fichier devient la référence - reproductible, versionnable, relisible en revue de code, et la base de GitOps (module 4e).
| Impératif | Déclaratif | |
|---|---|---|
| Tu donnes | un ordre (« crée 3 pods ») | une cible (« je veux 3 pods ») |
| Ça vit | dans l'historique du shell | dans un fichier, dans Git |
| Rejouable ? | à la main, en espérant se souvenir | kubectl apply -f, à l'identique |
| Sert à | explorer | livrer |
Les deux servent : on ne remplace pas l'un par l'autre.
La boucle : écris → applique → vérifie¶
kubectl apply -f mon-pod.yaml # enregistre l'état désiré (crée OU met à jour)
kubectl get -f mon-pod.yaml # vérifie ce que ça a donné
graph LR
F[fichier YAML<br/>état DÉSIRÉ] -->|kubectl apply| API[(API server<br/>etcd)]
API -->|contrôleurs| R[état RÉEL<br/>du cluster]
R -.écart corrigé en continu.-> API
apply ne « lance » rien : il enregistre l'état désiré. Ce sont les contrôleurs qui
font converger le réel vers ce qui est écrit, en permanence - exactement la réconciliation
du module 1, mais cette fois la source est un fichier que tu
versionnes.
create fabrique, apply réconcilie
kubectl create dit « fabrique cet objet » : si l'objet existe déjà, il refuse.
kubectl apply dit « fais en sorte que cet objet soit comme ça » : il le crée s'il
manque, le met à jour s'il existe, et ne se plaint jamais. On appelle ça
l'idempotence : dix apply d'affilée sur le même fichier donnent le même résultat,
sans erreur. C'est exactement ce qu'il faut pour un fichier versionné qu'on ré-applique
en boucle - create pour bricoler, apply pour livrer.
YAML, avant même de parler de Kubernetes¶
Un manifeste s'écrit en YAML. Ce n'est pas une technologie Kubernetes : c'est juste une façon d'écrire de l'information structurée en texte, faite pour être lue par un humain. On en trouve partout (GitHub Actions, Ansible, docker compose…). Si tu n'en as jamais vu, deux minutes suffisent : il n'y a que trois briques.
prenom: Antoine # (1)!
langues: # (2)!
- français
- anglais
adresse: # (3)!
ville: Lille
code_postal: "59000" # (4)!
- Brique 1 - clé : valeur. Un nom, deux points, un espace, la valeur. C'est l'essentiel de tout fichier YAML.
- Brique 2 - une liste. Chaque élément commence par un tiret. Ici
languesen contient deux. - Brique 3 - l'imbrication. Tout ce qui est décalé vers la droite est à
l'intérieur de la ligne du dessus.
villeetcode_postalsont dansadresse. - Les guillemets forcent la lecture « texte ». Sans eux,
59000serait un nombre - et un code postal qui commence par un zéro y perdrait son zéro.
| Brique | Ça s'écrit | Ça veut dire |
|---|---|---|
| Clé / valeur | nom: web |
l'information nom vaut web |
| Imbrication | 2 espaces de décalage | la ligne décalée est dans celle du dessus |
| Liste | - français |
un élément parmi plusieurs |
Il n'y a pas d'accolades ni de balises : le décalage EST la hiérarchie. C'est ce qui rend YAML agréable à lire… et impitoyable sur les espaces.
Comment on lit un YAML
Pose ton doigt sur une ligne et remonte vers la gauche : la première ligne moins décalée au-dessus est son « parent ». C'est tout le secret. Dans ta sandbox VS Code, l'extension indent-rainbow colore les niveaux : tu vois la hiérarchie sans compter les espaces.
Les trois pièges de forme (tous les débutants les prennent)¶
- Jamais de tabulation. La spécification YAML l'interdit pour l'indentation, point. 2 espaces par niveau, et c'est déjà réglé dans l'éditeur de ta sandbox. Une tabulation donne une erreur au parsing, avec la ligne et la colonne fautives : c'est tout ce dont tu as besoin pour la trouver.
- Quote tes valeurs ambiguës. En YAML,
yes,no,on,offnon entourés de guillemets sont lus comme des booléens (vrai/faux), etversion: 1.20est lu comme le nombre 1.2. Si une valeur doit rester du texte :"no","1.20". Quand tu vois des guillemets dans un YAML de production, ce n'est pas de la décoration. -
Une liste d'objets, ça se lit en deux temps. Le motif qui déroute au début :
containers: # une liste… - name: web # …dont le 1ᵉʳ élément est un objet : le tiret ouvre l'élément, image: nginx # et tout ce qui est aligné sous « name » appartient au MÊME élément - name: sidecar # 2ᵉ élément image: busyboxLe tiret et la clé qui le suit sont sur la même ligne ; les autres clés du même élément s'alignent sous cette clé, pas sous le tiret.
Deux détails qui font gagner du temps
- Tout JSON valide est du YAML valide (YAML en est un sur-ensemble) : on peut donc
écrire
matchLabels: { app: web }sur une ligne, et tu croiseras les deux styles. - L'API Kubernetes, elle, ne parle que JSON : ton YAML est converti par
kubectlavant l'envoi. Le YAML n'existe que pour tes yeux.
Anatomie d'un manifeste, ligne par ligne¶
Un manifeste, c'est un formulaire : les mêmes rubriques, toujours dans le même ordre, quel que soit l'objet. Voici le plus simple de tous - un Pod :
apiVersion: v1 # (1)!
kind: Pod # (2)!
metadata: # (3)!
name: mon-pod
spec: # (4)!
containers: # (5)!
- name: web
image: nginx:1.27-alpine
- Dans quel catalogue on pioche. Kubernetes range ses objets par familles et par
versions.
v1est le catalogue historique (Pod, Service, ConfigMap) ; les objets applicatifs, eux, vivent dansapps/v1(Deployment, ReplicaSet, DaemonSet). - Quel objet on veut. Toujours avec une majuscule :
Pod,Deployment,Service,ConfigMap. - Sa carte d'identité : son
name(obligatoire), sonnamespace, et surtout ses labels - les étiquettes par lesquelles les autres objets le retrouveront. - Ce que tu VEUX. C'est la seule rubrique qui change vraiment d'un type d'objet à
l'autre : le
specd'un Pod ne ressemble pas à celui d'un Service. - Une liste (le tiret) : un Pod peut porter plusieurs conteneurs. Ici un seul, nommé
web, qui tourne sur l'imagenginx:1.27-alpine.
| Rubrique | La question à laquelle elle répond |
|---|---|
apiVersion |
dans quel catalogue d'objets je pioche ? |
kind |
quel objet je crée ? |
metadata |
comment il s'appelle, et quelles étiquettes il porte ? |
spec |
ce que je veux qu'il soit |
C'est la grille de lecture à retenir : elle vaut pour un Pod de 8 lignes comme pour un manifeste de 200 lignes trouvé sur internet. Un manifeste n'est jamais plus que ça, empilé.
La 5ᵉ rubrique, celle que tu n'écris jamais : status
Quand tu lis un objet existant (kubectl get pod mon-pod -o yaml), tu vois une
rubrique en plus : status. C'est l'état réel, écrit par Kubernetes - jamais par
toi. spec = ce que tu veux, status = ce qui est : le thermostat du
module 1, noir sur blanc dans chaque objet. Tout le travail
des contrôleurs consiste à réduire l'écart entre les deux.
« Presque » tous les objets
Quelques objets remplacent spec par une rubrique à eux : un ConfigMap et un
Secret portent leurs valeurs sous data, un Role ses permissions sous rules.
Les trois premières rubriques, elles, ne bougent jamais.
Dans metadata : labels vs annotations¶
Deux dictionnaires qui se ressemblent et qu'on confond tout le temps :
- les labels servent à sélectionner : c'est par eux qu'un Service trouve ses pods,
qu'un Deployment reconnaît les siens, que
kubectl get -l app=webfiltre ; - les annotations ne servent qu'à porter de l'information : une description, un propriétaire, un réglage lu par un outil (l'ingress, par exemple). Aucun selector ne les lit jamais.
La règle : si quelque chose doit retrouver ton objet, c'est un label ; si c'est juste une note collée dessus, c'est une annotation.
Comment on écrit un manifeste (pour de vrai)¶
La question qui angoisse tout le monde : « je dois retenir tout ça par cœur ? » Non. Personne n'écrit un manifeste de zéro, pas même les gens dont c'est le métier depuis dix ans. Le geste professionnel tient en quatre étapes, toujours les mêmes.
① Partir d'un squelette (jamais de la page blanche)¶
Trois façons d'en obtenir un, de la plus universelle à la plus courante :
La méthode qui marche partout, même en SSH sur un serveur sans éditeur. On reprend une commande impérative qu'on connaît déjà, et on ajoute deux options :
--dry-run=client: « fais semblant, ne crée rien » ;-o yaml: « montre-moi le YAML que tu aurais envoyé ».
Le résultat est un manifeste complet et valide, qu'il n'y a plus qu'à retravailler. C'est le réflexe n°1 du module : ton impératif du module 3 devient ta machine à fabriquer du YAML.
Dans la sandbox VS Code, tape le début d'un type d'objet dans un fichier .yaml
(pod, deploy, svc, cm, ingress…) puis Tab : le squelette complet
s'insère, et Tab te fait sauter d'un champ à remplir au suivant.
Ctrl+Espace propose à tout moment les champs valides à
l'endroit où tu es, parce que l'éditeur connaît le schéma Kubernetes.
C'est, de très loin, ce qui se fait le plus en entreprise : le dépôt contient déjà quarante manifestes qui marchent. On copie le plus proche du besoin, on renomme, on adapte. Ce n'est pas de la triche - à condition de comprendre chaque ligne qu'on garde, ce qui est exactement l'objet de ce module.
② Nettoyer ce que le générateur a ajouté¶
Un squelette généré contient du bruit : des rubriques vides et des rubriques que le cluster remplit lui-même. Voici la sortie brute de la commande ci-dessus, telle quelle :
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
strategy: {} # (1)!
template:
metadata:
labels:
app: web
spec:
containers:
- image: nginx:1.27-alpine
name: nginx
resources: {} # (2)!
status: {} # (3)!
- Vide (
{}= « rien dedans ») : à supprimer. Sans stratégie déclarée, Kubernetes applique la sienne par défaut - c'est très bien. - Vide aussi : à supprimer, ou à remplir sciemment (requests/limits, module 4).
status, c'est l'état réel, écrit par le cluster : il n'a rien à faire dans un fichier que tu écris. À supprimer, toujours.
La règle : un manifeste ne décrit que ce que tu veux ; tout le reste, Kubernetes le remplit. Un bon manifeste est court - et tu dois pouvoir expliquer chacune de ses lignes.
Le champ mystère creationTimestamp: null
Tu le croiseras dans des exemples en ligne et dans les sorties des kubectl plus
anciens que le tien : c'est le même bruit, à supprimer de la même façon.
③ Compléter, avec le dictionnaire ouvert¶
Deux aides, sans jamais quitter ta sandbox :
kubectl explain deployment.spec # tous les champs possibles, expliqués
kubectl explain deployment.spec.template.spec.containers
kubectl api-resources # tous les types d'objets + leur apiVersion
Et dans l'éditeur : Ctrl+Espace pour la liste des champs valides, le survol pour lire la doc d'un champ, et les erreurs affichées en bout de ligne dès que tu écris quelque chose que le schéma refuse.
kubectl explain répond aussi à « c'est quoi cet objet ? »
kubectl explain deployment sans suffixe donne la description de l'objet lui-même.
C'est la doc officielle, à la version exacte de ton cluster - plus fiable qu'un article
de blog écrit trois versions plus tôt.
④ Vérifier avant de subir¶
Trois filets, du moins cher au plus cher :
kubectl apply -f web.yaml --dry-run=server # l'API VALIDE pour de vrai, sans rien créer
kubectl diff -f web.yaml # ce que ça CHANGERAIT sur le cluster actuel
kubectl apply -f web.yaml # on y va
kubectl get -f web.yaml # et on vérifie le résultat
--dry-run=client |
--dry-run=server |
|
|---|---|---|
| Qui juge | kubectl, sur ton poste |
l'API server du cluster |
| Détecte | le YAML mal formé | en plus : champ inconnu, quota dépassé, refus d'admission |
| Crée quelque chose ? | non | non plus |
kubectl diff est le réflexe qui sauve en production : il montre exactement ce que ton
apply va modifier, avant de le faire.
La boucle à graver
Squelette → nettoie → complète → vérifie → applique → contrôle. Tant que cette boucle tourne, écrire du YAML n'a rien d'un exercice de mémoire.
Et kubectl edit, alors ?
L'édition à chaud (module 3) écrit déjà du YAML… mais directement dans le cluster :
pas de fichier, pas d'historique, et ton correctif disparaît au prochain apply de
l'ancien manifeste. edit dépanne, le fichier livre - les deux gestes ouvrent le
même YAML, seule la destination change.
De 8 lignes à une vraie application¶
Le manifeste ne devient jamais « plus compliqué » : il empile les mêmes briques. On monte l'escalier marche par marche.
Marche 1 - le Pod nu (ce qu'on ne livre pas)¶
Le Pod du début de module marche… et ne survit à rien : si son nœud tombe, personne ne le recrée (module 1). On ne pilote pas un Pod directement, on pilote un Deployment.
Marche 2 - le Deployment : un manifeste dans un manifeste¶
C'est le point qui bloque tout le monde, et il n'y a qu'une chose à comprendre :
spec.template, c'est un Pod
Le template d'un Deployment est un manifeste de Pod complet, moins ses deux
premières lignes (apiVersion et kind - inutiles, on sait déjà que ce sera un Pod).
C'est le moule : le Deployment fabrique autant de pods identiques que demandé.
apiVersion: apps/v1 # (1)!
kind: Deployment
metadata:
name: web
spec: # le spec du DEPLOYMENT
replicas: 3 # (2)!
selector: # (3)!
matchLabels:
app: web
template: # (4)!
metadata:
labels:
app: web # (5)!
spec: # le spec du POD
containers:
- name: web
image: nginx:1.27-alpine
apps/v1, et nonv1: un Deployment vit dans la familleapps. Se tromper ici donneno matches for kind "Deployment" in version "v1"- le message le plus fréquent du module.- Combien de copies je veux. Le contrôleur s'occupe du reste, en permanence.
- Quels pods ce Deployment considère comme les siens. Il les reconnaît à leur étiquette, pas à leur nom.
- Le moule : à partir d'ici et jusqu'à la fin, c'est un manifeste de Pod.
- L'étiquette collée sur chaque pod fabriqué. Elle doit être identique à celle du
selectorjuste au-dessus, sinon l'API refuse le manifeste.
Le spec d'un Deployment répond donc à trois questions : combien (replicas), qui
est à moi (selector), à quoi ils ressemblent (template).
Marche 3 - le Service : tout tient à une étiquette¶
Le Service ne connaît pas le Deployment. Il ne connaît que des labels. On peut
écrire les deux objets dans un même fichier, séparés par une ligne --- :
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector: # (1)!
app: web
ports:
- port: 80 # (2)!
- Le Service adresse tous les pods qui portent ce label, d'où qu'ils viennent.
- Le port sur lequel le Service écoute. (Le port du conteneur,
targetPort, est identique par défaut - les variantes sont au module 4r.)
graph LR
SEL[Deployment<br/>selector.matchLabels<br/>app: web] -. doivent être identiques .- TPL[template.labels<br/>app: web]
SVC[Service<br/>selector: app: web] -->|sélectionne| TPL
Trois flèches, une seule étiquette. Le Deployment reconnaît ses pods par app: web, le
Service les trouve par app: web. Une majuscule de trop dans l'un des trois, et le
manifeste est refusé (Deployment) ou le Service ne route nulle part, sans le moindre
message d'erreur (Service). C'est la panne réseau n°1 - et celle que tu répareras toi-même
dans les TP de debug.
Plusieurs objets dans un fichier
Une ligne --- seule sépare deux documents YAML. Un fichier app.yaml qui contient le
Deployment et son Service s'applique d'un seul kubectl apply -f app.yaml : les
objets qui vivent ensemble se livrent ensemble.
Les pièges qui mangent du temps¶
- L'indentation : 2 espaces par niveau, jamais de tabulation. Apprends à la lire (chaque niveau = un objet imbriqué) plutôt qu'à la deviner.
selector≠labels: dans un Deployment,spec.selector.matchLabelsdoit correspondre àspec.template.metadata.labels; dans un Service,spec.selectordoit correspondre aux labels des pods visés. Sinon : rejet à l'apply, ou service qui ne route nulle part.apiVersionerronée : un Deployment est dansapps/v1, pasv1. Le message (no matches for kind …) le dit - encore faut-il le lire. En cas de doute :kubectl api-resources | grep -i deployment.- L'unité des ressources :
memory: 64Mi(pas64M),cpu: 250m= 0,25 CPU, etrequests≤limits(sinon le pod est refusé - c'est un TP de debug). - Le nom de l'objet : minuscules, chiffres et tirets uniquement (norme DNS). Pas
d'accent, pas d'espace, pas de majuscule -
mon-podpasse,Mon Podnon. - Le
---oublié : deux objets collés dans un fichier sans séparateur, etkubectlne voit qu'un manifeste bancal.
Lire un manifeste que tu n'as pas écrit¶
C'est ce que tu feras le plus souvent : en mission, on hérite du YAML des autres bien plus qu'on n'en crée. Quatre questions, dans l'ordre, et tu as compris n'importe quel fichier :
kind+metadata.name: c'est quoi, et ça s'appelle comment ?spec: combien de copies, quelle image, quels ports ?- Les labels : qui va retrouver cet objet, et par quelle étiquette ?
- Ce que je ne comprends pas →
kubectl explain <kind>.<champ>, tout de suite.
Et pour comparer le fichier au réel : kubectl get <kind> <nom> -o yaml affiche l'objet tel
que le cluster le voit, status compris.
Exercice 3y.1 - Lis-moi ce manifeste
Un objet tourne déjà dans ta sandbox et tu n'as pas écrit son manifeste. Va le chercher et réponds aux quatre questions à voix haute, dans l'ordre, sans rien modifier :
kubectl get deploy -A # choisis-en un que tu ne connais pas
kubectl get deploy <nom> -n <namespace> -o yaml
- Quel
kind, quel nom ? 2. Que demande sonspec(replicas, image, ports) ? - Quels labels porte-t-il, et qui s'en sert ? 4. Un champ que tu ne reconnais pas →
kubectl explain deployment.<le champ>.
Puis repère la frontière : à partir d'où commence le status - donc ce que tu n'aurais
pas écrit si tu avais produit ce fichier toi-même ?
Gestes vus en séance
L'injection d'un ConfigMap dans un pod (envFrom) se montre en séance - le TP
yaml-3-config te la fera écrire, et la
cheat-sheet kubectl en garde le squelette.
Pratique¶
Dans ta sandbox (VS Code conseillée pour écrire ; sinon nano), une fois la lecture
faite - tu écris tes manifestes :
tp start yaml-1-pod # générer un squelette, le comprendre, l'adapter
tp start yaml-2-deploy-service # un Deployment + un Service (lien par les labels)
tp start yaml-3-config # un ConfigMap + l'injecter dans un pod
Puis le geste le plus réaliste du module : on te donne un YAML qui ne marche pas, à toi de le diagnostiquer et de le corriger. Un piège par TP, du message d'erreur franc au bug silencieux :
tp start yaml-debug-1 # apiVersion/selector : l'apply refuse - lire l'erreur
tp start yaml-debug-2 # service sans endpoint : tout est « vert », rien ne répond
tp start yaml-debug-4 # requests > limits : le pod est rejeté
(Un quatrième manifeste cassé - une probe fausse - t'attend au module 4c, là où les probes s'enseignent.)
La méthode de réparation est toujours la même : lis le message (apply ou Events), compare le manifeste aux rubriques que tu connais, corrige, réapplique, vérifie.
fais la manip puis tp check (ou tp watch) · tp hint / tp solution si tu bloques.
Ce qu'il faut retenir¶
- Impératif pour explorer, déclaratif (YAML +
apply -f) pour livrer. - YAML = clé/valeur, imbrication par l'indentation, listes à tirets. Rien d'autre.
- Les rubriques de tout objet :
apiVersion/kind/metadata/spec(+status, que le cluster écrit). - Personne n'écrit de zéro : squelette (
--dry-run=client -o yaml, modèle de l'éditeur, manifeste voisin) → nettoie → complète (kubectl explain) → vérifie → applique. spec.templated'un Deployment = un manifeste de Pod ; c'est le moule.- Le lien Deployment ↔ Service ↔ Pods passe par les labels : c'est là que ça casse.
- Un YAML cassé se lit : message d'apply d'abord, Events ensuite.
Quiz éclair¶
Teste-toi : réponds de tête, puis déplie.
1. kubectl apply -f sur un objet qui existe déjà : que se passe-t-il ?
L'objet est mis à jour pour correspondre au manifeste (apply est idempotent). C'est toute la force du déclaratif : le même fichier crée ou corrige.
2. Dans un Deployment, à quoi correspond spec.template ?
À un manifeste de Pod complet, moins apiVersion et kind : c'est le moule à
partir duquel le Deployment fabrique ses pods. D'où l'imbrication de deux spec dans le
même fichier.
3. Ton Service est créé sans erreur mais ne route vers rien. Premier réflexe ?
kubectl get endpoints <svc> : s'il est vide, le selector du Service ne matche
pas les labels des pods. Compare-les champ à champ.
4. Comment générer un squelette de manifeste sans tout taper ?
kubectl create … --dry-run=client -o yaml > fichier.yaml - rien n'est créé sur le
cluster, mais le YAML est complet et valide. Puis on nettoie (status: {}, les
rubriques vides) avant de l'adapter.
5. --dry-run=client et --dry-run=server : la différence ?
client : kubectl valide seul, sur ton poste - il attrape le YAML mal formé.
server : le manifeste part à l'API server, qui le valide pour de vrai (champ
inconnu, quota, admission) sans rien créer. C'est le filet à passer avant un apply
en production - avec kubectl diff -f, qui montre ce qui changerait.
➡️ Suite : Module 4 - Gouvernance du cluster