Aller au contenu
Conteneurs & Orchestration medium

Manifests Kubernetes : produire, valider et corriger vite (CKAD)

28 min de lecture

logo kubernetes

Produire des manifests Kubernetes rapidement, c'est savoir générer un squelette avec kubectl, le compléter avec les bons champs, et valider avant d'appliquer. Ce guide vous donne les réflexes CKAD : kubectl explain, --dry-run=client -o yaml, correction rapide, et validation côté serveur.

  • Workflow examen : générer → compléter → valider → appliquer
  • Deux approches : génération assistée vs écriture manuelle
  • Namespace et contexte : éviter les erreurs de cible
  • Snippets fréquents : resources, probes, command, envFrom
  • Correction rapide : kubectl edit, kubectl patch
  • Validation complète : --dry-run=client, --dry-run=server, kubectl diff

En examen, chaque seconde compte. Voici le workflow le plus efficace :

  1. Configurer le namespace de travail

    Fenêtre de terminal
    kubectl create namespace ckad
    kubectl config set-context --current --namespace=ckad

    En changeant le namespace courant, vous évitez de répéter -n à chaque commande.

  2. Générer un squelette

    Fenêtre de terminal
    kubectl create deployment web --image=nginx:stable --dry-run=client -o yaml > web.yaml
  3. Compléter le manifest

    Fenêtre de terminal
    vim web.yaml

    Ajoutez les champs manquants : resources, probes, labels...

  4. Valider avant application

    Fenêtre de terminal
    kubectl apply --dry-run=client -f web.yaml
    kubectl diff -f web.yaml # si la ressource existe déjà
  5. Appliquer et vérifier

    Fenêtre de terminal
    kubectl apply -f web.yaml
    kubectl get all
    kubectl describe deploy web

Il existe deux façons d'obtenir un fichier YAML valide : le faire générer par kubectl ou l'écrire à la main. Les deux mènent au même résultat, mais elles n'ont pas le même coût en temps ni la même valeur pédagogique. La génération évite les fautes d'indentation et les mauvaises apiVersion ; l'écriture manuelle vous apprend où se placent réellement les champs.

Utile pour : gagner du temps, éviter les erreurs de base, partir d'un squelette valide.

Fenêtre de terminal
kubectl create deployment web --image=nginx:stable --dry-run=client -o yaml > web.yaml

Le YAML généré est un squelette. Vous devez presque toujours l'ajuster : labels, ports, resources, probes...

Utile pour : comprendre la structure, corriger un YAML existant, maîtriser les champs importants.

Quand vous connaissez bien la structure, vous pouvez écrire directement. Mais en examen, la génération assistée reste plus rapide.

Kubernetes utilise un langage déclaratif : vous décrivez l'état souhaité, et Kubernetes se charge de l'atteindre. Chaque manifest YAML suit une structure commune avec quatre champs obligatoires.

Ces quatre clés sont présentes dans tous les manifests, quel que soit le type de ressource ; seul le contenu de spec change d'un kind à l'autre.

apiVersion: v1
kind: Pod
metadata:
name: web
namespace: ckad
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.30
ports:
- containerPort: 80
ChampDescriptionExemples
apiVersionVersion de l'API Kubernetesv1, apps/v1, networking.k8s.io/v1
kindType de ressourcePod, Deployment, Service, ConfigMap
metadataIdentité de l'objetname, namespace, labels, annotations
specSpécification détailléeVarie selon le kind

Un namespace est un cloisonnement logique du cluster : deux ressources peuvent porter le même nom dans deux namespaces différents sans entrer en conflit. kubectl travaille toujours dans un namespace, celui du contexte courant si vous ne passez pas -n. Fixer ce namespace une fois pour toutes est le geste qui évite le plus d'erreurs, en examen comme en production.

Trois commandes suffisent : créer le namespace, l'attacher au contexte courant, puis vérifier que le changement a bien été pris en compte.

Fenêtre de terminal
# Créer le namespace
kubectl create namespace monprojet
# Définir comme namespace courant
kubectl config set-context --current --namespace=monprojet
# Vérifier le contexte actuel
kubectl config get-contexts

Une ressource créée dans le mauvais namespace n'échoue pas : elle est acceptée, elle démarre, et elle reste invisible depuis l'endroit où vous la cherchez. Pire, une ConfigMap ou un Secret ne sont jamais lisibles depuis un autre namespace, donc le Pod qui les référence reste bloqué au démarrage.

ProblèmeConséquenceSolution
Oublier -nRessource créée dans defaultChanger le namespace courant
Mauvais namespaceRessource introuvablekubectl get pods -A pour chercher
ConfigMap/Secret ailleursnot found au démarrage du PodCréer dans le même namespace

Vous n'avez pas besoin de mémoriser la structure de chaque ressource : le cluster la décrit lui-même. Deux commandes suffisent pour retrouver n'importe quel champ, kubectl api-resources pour l'inventaire des types et kubectl explain pour le détail des champs. Cette documentation embarquée est autorisée en examen, contrairement à vos notes personnelles.

Cette liste donne aussi les abréviations (deploy, svc, cm) qui raccourcissent toutes les commandes suivantes.

Fenêtre de terminal
kubectl api-resources | head -15
NAME SHORTNAMES APIVERSION NAMESPACED KIND
configmaps cm v1 true ConfigMap
namespaces ns v1 false Namespace
pods po v1 true Pod
secrets v1 true Secret
services svc v1 true Service
deployments deploy apps/v1 true Deployment

Cette commande liste tous les types de ressources avec leur abréviation, leur apiVersion et si elles sont namespacées.

kubectl explain documente chaque champ directement depuis le cluster :

Fenêtre de terminal
kubectl explain pod.spec.containers
KIND: Pod
VERSION: v1
FIELD: containers <[]Container>
DESCRIPTION:
List of containers belonging to the pod.
FIELDS:
args <[]string>
command <[]string>
env <[]EnvVar>
image <string>
name <string> -required-
ports <[]ContainerPort>
resources <ResourceRequirements>
...

Au lieu d'écrire un YAML de zéro, générez-le avec --dry-run=client -o yaml :

Fenêtre de terminal
kubectl run web --image=nginx:stable --dry-run=client -o yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: web
name: web
spec:
containers:
- image: nginx:stable
name: web
restartPolicy: Always

Ces champs reviennent constamment en CKAD. Gardez-les en mémoire.

command remplace l'ENTRYPOINT de l'image et args remplace sa CMD : déclarer l'un sans l'autre suffit rarement, car l'image perd alors la moitié de sa ligne de démarrage.

containers:
- name: app
image: busybox:1.36
command: ["sh", "-c"]
args: ["echo hello && sleep 3600"]

Une valeur d'environnement est toujours une chaîne de caractères : les booléens et les nombres doivent être entre guillemets, sinon l'API server rejette le manifest.

env:
- name: APP_ENV
value: production
- name: APP_DEBUG
value: "false"

Chaque clé de la ConfigMap devient une variable portant le même nom, ce qui évite d'énumérer les entrées une par une. Les clés dont le nom n'est pas un identifiant d'environnement valide sont ignorées au démarrage.

envFrom:
- configMapRef:
name: app-config

Les requests servent au scheduler pour choisir un nœud capable d'accueillir le Pod ; les limits sont appliquées par le kubelet pendant l'exécution. Dépasser la limite CPU provoque un throttling, dépasser la limite mémoire provoque la mort du conteneur (OOMKilled).

resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"

Les deux sondes n'ont pas du tout les mêmes conséquences. Un échec de la readinessProbe retire le Pod des endpoints du Service, sans le redémarrer ; un échec de la livenessProbe fait redémarrer le conteneur. Une liveness trop agressive sur une application lente à démarrer déclenche des redémarrages en boucle.

readinessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 15
periodSeconds: 20

Déclarer un containerPort est purement informatif : cela n'ouvre ni ne ferme rien. L'intérêt est ailleurs, le name donné ici peut être réutilisé comme targetPort dans un Service ou comme port dans une sonde.

ports:
- containerPort: 80
name: http
- containerPort: 443
name: https

Le montage se fait en deux temps et le lien entre les deux est le nom : volumes déclare la source au niveau du Pod, volumeMounts indique où la monter dans le conteneur. Une faute de frappe entre les deux bloque le Pod au démarrage.

containers:
- name: app
image: nginx:1.30
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-config

Un Pod est l'unité de base dans Kubernetes. Il exécute un ou plusieurs conteneurs.

Ce manifest reprend les snippets précédents : identité, image, port, quotas de ressources et sonde de disponibilité. C'est le niveau de complétude attendu sur une question d'examen, pas seulement image et name.

apiVersion: v1
kind: Pod
metadata:
name: web
namespace: ckad
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.30
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
Fenêtre de terminal
kubectl apply -f pod.yaml
kubectl get pods -l app=nginx --show-labels
NAME READY STATUS RESTARTS AGE LABELS
web 1/1 Running 0 19s app=nginx

Un Deployment gère un ensemble de Pods identiques et permet les mises à jour progressives.

Par rapport au Pod précédent, la définition du conteneur descend d'un cran : elle vit désormais dans spec.template, le modèle que le Deployment reproduit autant de fois que replicas l'indique.

apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: ckad
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.30
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
Fenêtre de terminal
kubectl apply -f deployment.yaml
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
web 3/3 3 3 10s

Un Service expose un ensemble de Pods sur le réseau.

Le champ type détermine jusqu'où le Service est joignable. En son absence, Kubernetes applique ClusterIP, donc une exposition strictement interne au cluster. Les deux autres types élargissent progressivement la portée, NodePort en ouvrant un port sur chaque nœud, LoadBalancer en réclamant une IP publique au fournisseur cloud.

TypeUsageAccessibilité
ClusterIPTrafic interneDans le cluster uniquement
NodePortTests, accès externe simple<NodeIP>:<nodePort>
LoadBalancerProduction cloudIP externe via load balancer

Deux champs décident de tout : le selector, qui doit correspondre aux labels des Pods, et le couple port (celui du Service) et targetPort (celui du conteneur). Un selector qui ne matche rien produit un Service sans endpoints, joignable mais muet.

apiVersion: v1
kind: Service
metadata:
name: web
namespace: ckad
spec:
type: ClusterIP
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80

Une ConfigMap stocke des configurations non sensibles.

Une ConfigMap accepte deux usages dans le même objet : des paires clé-valeur courtes, destinées aux variables d'environnement, et des fichiers de configuration entiers introduits par le bloc | de YAML, destinés à être montés en volume.

apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: ckad
data:
APP_ENV: production
APP_DEBUG: "false"
config.json: |
{
"database": "postgres",
"port": 5432
}

Le Pod ne cite plus les clés une par une, il référence la ConfigMap entière. Attention, l'injection a lieu au démarrage du conteneur : modifier la ConfigMap ensuite n'a aucun effet tant que le Pod n'est pas recréé.

apiVersion: v1
kind: Pod
metadata:
name: app
namespace: ckad
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
envFrom:
- configMapRef:
name: app-config

Toutes les clés de la ConfigMap deviennent des variables d'environnement.

Un Secret stocke des données sensibles. Utilisez stringData pour écrire en clair dans le manifest.

Le champ stringData accepte des valeurs en clair et l'API server les encode lui-même en base64 dans le champ data. C'est un champ d'écriture seule : une relecture avec kubectl get -o yaml ne renverra que data.

apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: ckad
type: Opaque
stringData:
username: admin
password: S3cr3tP@ssw0rd!

secretKeyRef désigne une clé précise d'un Secret et suppose que celui-ci existe déjà dans le même namespace. Sinon le Pod reste en CreateContainerConfigError jusqu'à ce que le Secret apparaisse.

apiVersion: v1
kind: Pod
metadata:
name: app
namespace: ckad
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: password

Regroupez plusieurs ressources liées dans un seul fichier avec --- :

apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: ckad
data:
APP_ENV: production
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: ckad
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: nginx:1.30
envFrom:
- configMapRef:
name: app-config
---
apiVersion: v1
kind: Service
metadata:
name: app
namespace: ckad
spec:
selector:
app: myapp
ports:
- port: 80
Fenêtre de terminal
kubectl apply -f app-stack.yaml

Toutes les ressources sont créées en une seule commande.

Réécrire un fichier puis le réappliquer coûte du temps quand il n'y a qu'un champ à changer. Trois commandes modifient la ressource directement dans le cluster, de la plus souple à la plus expéditive. Le revers est connu : ces modifications ne se retrouvent pas dans vos fichiers YAML, donc le prochain kubectl apply les écrasera.

C'est l'option la plus permissive : tout le manifest est éditable, y compris les champs que vous n'aviez pas prévu de toucher.

Fenêtre de terminal
kubectl edit deployment web

Ouvre le manifest dans votre éditeur. Les changements sont appliqués à la sauvegarde.

Le patch vise un chemin précis dans l'objet, sans ouvrir d'éditeur, ce qui le rend scriptable. Avec --type merge, la portion JSON fournie est fusionnée dans la ressource existante ; le reste est laissé intact.

Fenêtre de terminal
# Changer le nombre de réplicas
kubectl patch deployment web --type merge -p '{"spec":{"replicas":5}}'
# Changer l'image
kubectl patch deployment web --type merge \
-p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.25"}]}}}}'

Pour les modifications les plus fréquentes, kubectl set évite d'écrire le JSON à la main. Sur un Deployment, changer l'image déclenche immédiatement un rolling update.

Fenêtre de terminal
# Changer l'image
kubectl set image deployment/web nginx=nginx:1.25
# Changer les resources
kubectl set resources deployment/web -c=nginx --limits=cpu=500m,memory=256Mi

Trois niveaux de vérification existent, du moins coûteux au plus complet, et ils ne détectent pas les mêmes erreurs. La validation côté client attrape les fautes de syntaxe, la validation côté serveur attrape les champs inconnus et les refus d'admission, kubectl diff montre ce qui va changer sur une ressource déjà déployée.

Rien n'est créé et le manifest n'atteint jamais l'étape d'admission du cluster.

Fenêtre de terminal
kubectl apply --dry-run=client -f mon-manifest.yaml

Vérifie la syntaxe YAML et la structure de base. Rapide mais limité.

Le manifest fait cette fois l'aller-retour complet, mais rien n'est persisté dans etcd.

Fenêtre de terminal
kubectl apply --dry-run=server -f mon-manifest.yaml

Envoie le manifest à l'API server pour validation complète :

  • Vérifie les admission controllers
  • Détecte les champs inconnus
  • Valide les contraintes (quotas, policies...)

Cette commande n'a de sens que si la ressource existe déjà dans le cluster ; sur une ressource inconnue, elle affiche simplement une création complète.

Fenêtre de terminal
kubectl diff -f mon-manifest.yaml

Compare le manifest local avec la ressource existante :

replicas: 3
replicas: 5

Ces erreurs ont un point commun : elles ne produisent pas de message clair. La ressource est acceptée, le Pod démarre parfois, et c'est le comportement observé qui ne correspond pas à l'attendu. Les reconnaître d'un coup d'œil vaut plusieurs minutes gagnées par question.

ErreurConséquenceSolution
Oublier le namespaceRessource dans defaultChanger le namespace courant
Mauvais label Service/PodService sans EndpointsAligner les selectors
port vs targetPort inversésConnexion impossibleport = Service, targetPort = conteneur
Indentation YAML incorrecteErreur de parsingUtiliser 2 espaces, pas de tabs
Modifier selector d'un DeploymentErreur "immutable"Supprimer et recréer
Champ au mauvais niveauChamp ignorékubectl explain pour vérifier
ConfigMap dans un autre namespacenot foundMême namespace que le Pod

Le selector d'un Deployment est immuable après création : aucun patch, aucun apply ne le fera changer. Le seul chemin est la suppression suivie d'une recréation, ce qui interrompt le service le temps du remplacement.

Fenêtre de terminal
# Impossible de modifier le selector d'un Deployment existant
kubectl patch deployment web --type merge -p '{"spec":{"selector":{"matchLabels":{"app":"newapp"}}}}'
# Erreur: spec.selector is immutable

Solution : supprimer et recréer le Deployment.

Fenêtre de terminal
kubectl delete deployment web
kubectl apply -f web.yaml # avec le nouveau selector

Avant chaque kubectl apply :

  • Namespace : suis-je dans le bon namespace ?
  • Labels : sont-ils cohérents entre Deployment, Pod et Service ?
  • Image : nom et tag corrects ?
  • Ports : port et targetPort alignés ?
  • Resources : requests ≤ limits ?
  • Probes : path et port corrects ?
  • Volumes : même nom entre volumes et volumeMounts ?

Voici les cinq ressources manipulées dans ce guide, avec les champs sur lesquels se concentrer quand il faut écrire vite. La colonne « Champs clés » correspond à ce qu'un correcteur d'examen vérifie en premier.

RessourceRôleChamps clés
PodExécuter des conteneurscontainers[].image, ports, resources
DeploymentGérer des réplicasreplicas, selector, template
ServiceExposer des Podsselector, ports, type
ConfigMapConfiguration non sensibledata (clé/valeur ou fichier)
SecretDonnées sensiblesstringData (écriture) ou data (base64)

Ce bloc rassemble les commandes du guide dans l'ordre du workflow : explorer, générer, valider, corriger, déboguer. Entraînez-vous à les taper sans les relire, la vitesse de frappe fait partie de l'épreuve.

Fenêtre de terminal
# Explorer l'API
kubectl api-resources
kubectl explain deployment.spec.template.spec.containers
# Générer des manifests
kubectl create deployment web --image=nginx --dry-run=client -o yaml > web.yaml
kubectl expose deployment web --port=80 --dry-run=client -o yaml > svc.yaml
# Valider
kubectl apply --dry-run=client -f manifest.yaml
kubectl apply --dry-run=server -f manifest.yaml
kubectl diff -f manifest.yaml
# Corriger rapidement
kubectl edit deployment web
kubectl patch deployment web --type merge -p '{"spec":{"replicas":5}}'
kubectl set image deployment/web nginx=nginx:1.25
# Débugger
kubectl get pods -A # tous les namespaces
kubectl describe pod web
kubectl logs web
kubectl exec -it web -- sh

Sept questions pour vérifier que les réflexes sont acquis : structure d'un manifest, cohérence des labels et choix de la bonne commande de validation.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  1. Workflow examen : namespace → générer → compléter → valider → appliquer
  2. kubectl explain documente chaque champ depuis le cluster
  3. --dry-run=client -o yaml génère un squelette à compléter
  4. kubectl expose crée un Service avec les bons labels automatiquement
  5. Labels cohérents entre Deployment, Pod template et Service selector
  6. --dry-run=server valide contre l'API et les admission controllers
  7. kubectl edit et kubectl patch pour corriger rapidement
  8. Multi-document YAML (---) pour regrouper les ressources liées
  9. Namespace courant : le changer évite les erreurs de cible
  10. stringData dans les Secrets évite l'encodage base64 manuel

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn