Pour modifier un objet Kubernetes déjà déployé, kubectl offre trois
gestes : edit ouvre l'objet dans un éditeur, patch change un champ précis,
replace renvoie l'objet entier depuis un fichier. Cette leçon, destinée
aux administrateurs qui exploitent un cluster, vous apprend à choisir entre
les trois, à prévoir l'effet d'un patch sur une liste, et à ne pas laisser
le cluster diverger du dépôt Git. C'est la première leçon de la
sous-partie « Maintenance et changements » : une maintenance commence souvent
par une modification de ce genre, avant le drain d'un nœud qu'enseigne la
leçon suivante.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Choisir entre
kubectl edit,kubectl patchetkubectl replaceselon l'urgence et la portée du changement - Écrire un patch des trois types (strategic, merge, json) et prévoir son
effet sur une liste comme
containers - Repérer la dérive entre le cluster et le manifeste versionné, puis la résorber
- Prévoir ce que fait
replacesans--force(conflit de version, champs immuables) et avec--force(suppression puis recréation)
Prérequis
Section intitulée « Prérequis »Cette leçon suppose acquis deux gestes enseignés plus tôt dans la formation :
déployer un manifeste avec kubectl apply
(create et apply),
et suivre ou annuler un rollout de Deployment
(rolling updates et rollbacks).
La comparaison d'un fichier au cluster avec kubectl diff est expliquée
dans cette leçon, à l'endroit où elle sert ; la leçon facultative
diff et wait
l'approfondit.
Il vous faut aussi un cluster de test, par exemple le cluster kind de la
formation, et les droits d'écriture sur un namespace.
Toutes les commandes ont été jouées sur Kubernetes 1.37 avec kubectl
1.37. Les exemples modifient un Deployment web du namespace boutique, qui
porte deux conteneurs : le serveur web et un agent de journaux
log-agent. Ce second conteneur n'est pas décoratif, il rend visible le
piège des listes décrit plus bas.
# web.yaml : la version versionnée dans GitapiVersion: apps/v1kind: Deploymentmetadata: name: web namespace: boutiquespec: replicas: 2 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: web image: nginx:1.27@sha256:6784fb0834aa7dbbe12e3d7471e69c290df3e6ba810dc38b34ae33d3c1c05f7d ports: - containerPort: 80 - name: log-agent image: busybox:1.37@sha256:bdf57e528e45e4433820e045b29b4597825a1c9e38353532d90a01445013f82e command: ["sh", "-c", "sleep 3600"]Pour reproduire, créez le namespace avec kubectl create namespace boutique,
puis déployez le manifeste avec kubectl apply -f web.yaml. La commande
kubectl -n boutique get deploy web doit afficher 2/2 dans la colonne
READY : les deux réplicas tournent.
Quelle commande choisir pour modifier un objet vivant ?
Section intitulée « Quelle commande choisir pour modifier un objet vivant ? »La règle tient en une phrase : apply depuis Git au quotidien, patch pour
un changement ciblé et scriptable, edit pour une correction d'urgence,
replace quand un fichier complet doit faire autorité. Les trois commandes
de cette leçon écrivent directement dans le cluster, sans passer par le
dépôt Git : elles sont rapides, et chacune laisse une dérive à
résorber ensuite, sauf si le fichier que vous envoyez vient lui-même de Git.
| Besoin | Commande | Ce qu'elle envoie au serveur |
|---|---|---|
| Corriger pendant un incident | kubectl edit | l'objet entier, après votre passage dans l'éditeur |
| Changer un champ, dans un script | kubectl patch | seulement les champs du patch |
| Imposer un fichier complet | kubectl replace -f | l'objet entier, tel qu'écrit dans le fichier |
| Gérer l'objet au quotidien | kubectl apply -f | la différence calculée avec la dernière version appliquée |
Ce tableau se lit par la dernière colonne. Un patch ne touche que ce qu'il
nomme ; edit et replace renvoient l'objet entier, donc tout champ
absent ou modifié par erreur part avec. C'est ce qui rend replace plus
dangereux qu'il n'en a l'air, et patch plus sûr dans un script.
kubectl edit : corriger en direct pendant un incident
Section intitulée « kubectl edit : corriger en direct pendant un incident »kubectl edit ouvre l'objet dans un éditeur de texte et l'envoie au serveur
dès que vous enregistrez et quittez. C'est l'outil de l'incident : pas de
fichier à écrire, pas de pull request à attendre. Le prix est la
traçabilité : la modification n'existe que dans le cluster.
kubectl -n boutique edit deploy webL'éditeur utilisé est celui de la variable KUBE_EDITOR, à défaut celui
d'EDITOR, et à défaut vi sous Linux. Pour un éditeur graphique, la commande
doit attendre la fermeture du fichier, sinon kubectl croit que vous avez
quitté sans rien changer : export KUBE_EDITOR="code --wait". Précisez
toujours le namespace avec -n : sans lui, kubectl ouvre l'objet du
namespace courant, qui n'est pas forcément celui de l'incident.
Trois issues sont possibles, et kubectl les distingue par son message :
deployment.apps/web edited # changement envoyé et acceptéEdit cancelled, no changes made. # fichier enregistré sans modificationerror: deployments.apps "web" is invalid # modification refusée par l'APIA copy of your changes has been stored to "/tmp/kubectl-edit-4114702328.yaml"error: Edit cancelled, no valid changes were saved.Quitter l'éditeur sans enregistrer annule donc tout, sans risque. Une
modification invalide, comme une image vide, est refusée par le serveur
API : rien n'est appliqué, et votre saisie est conservée dans un fichier
temporaire de /tmp pour que vous ne la perdiez pas.
Comment éviter la dérive entre le cluster et Git ?
Section intitulée « Comment éviter la dérive entre le cluster et Git ? »La dérive (drift) est l'écart entre ce qui tourne dans le cluster et ce que
décrit le manifeste du dépôt Git. Un edit ou un patch la crée à coup sûr.
Elle n'est pas grave tant qu'elle est connue : elle le devient au prochain
kubectl apply du manifeste, ou à la prochaine synchronisation d'un outil
GitOps, qui écrase votre correction sans prévenir.
Le scénario se reproduit en deux commandes. Un opérateur monte le Deployment à quatre réplicas pendant un pic de charge, puis la comparaison avec le fichier Git révèle l'écart :
kubectl -n boutique patch deploy web -p '{"spec":{"replicas":4}}'kubectl diff -f web.yaml- replicas: 4+ replicas: 2La sortie de kubectl diff, raccourcie ici aux lignes utiles, montre en - la
valeur vivante et en + celle que le fichier imposerait. Le code de sortie
vaut 1 quand un écart existe et 0 quand le cluster est conforme, ce qui
permet de détecter la dérive dans un script ou une CI. Si personne ne
reporte le changement, le prochain kubectl apply -f web.yaml ramène le
Deployment à deux réplicas, en plein pic.
Le geste complet, quand une urgence impose de modifier hors de Git :
-
Vérifiez le contexte et sauvegardez l'état actuel
Fenêtre de terminal kubectl config current-contextkubectl -n boutique get deploy web -o yaml > web.avant.yaml -
Modifiez avec
kubectl editoukubectl patch, puis vérifiez le résultat aveckubectl -n boutique rollout status deploy/web. -
Mesurez l'écart avec le manifeste versionné
Fenêtre de terminal kubectl diff -f web.yaml -
Reportez le changement dans
web.yaml, faites-le relire et fusionner comme n'importe quelle pull request.kubectl diff -f web.yamlne doit plus rien afficher et rendre le code 0 : la dérive est résorbée.
La sauvegarde de l'étape 1 n'est pas un manifeste propre : elle contient des
champs écrits par le serveur (status, uid, resourceVersion). Elle sert à
comparer et, en dernier recours, à restaurer, jamais à remplacer le
fichier du dépôt.
kubectl patch : modifier un champ précis
Section intitulée « kubectl patch : modifier un champ précis »kubectl patch envoie au serveur un fragment d'objet et ne modifie que les
champs qu'il nomme. Le reste de l'objet n'est pas renvoyé, donc pas exposé à
une erreur de recopie. C'est la commande des scripts et des
runbooks : la même ligne produit le même effet à chaque exécution.
kubectl -n boutique patch deploy web -p '{"metadata":{"annotations":{"incident-ref":"INC-2026-0142"}}}'deployment.apps/web patchedLe patch s'écrit en JSON entre guillemets simples, pour que le shell
ne touche pas aux guillemets doubles qu'il contient. Rejouée à l'identique, la
commande répond deployment.apps/web patched (no change) : ce message n'est
pas une erreur, il confirme que l'objet était déjà dans l'état demandé. Un
JSON mal formé, lui, est refusé (invalid JSON patch) avant toute
modification.
Quel type de patch choisir ?
Section intitulée « Quel type de patch choisir ? »kubectl patch accepte trois formats, choisis par --type : strategic
(le défaut), merge et json. Ils diffèrent surtout par leur traitement
des listes, et c'est là que se produisent les accidents.
| Type | Option | Effet sur une liste | Cas d'usage |
|---|---|---|---|
| Strategic merge | --type=strategic (défaut) | fusionne les éléments selon leur clé, par exemple name pour containers | ressources natives |
| JSON merge (RFC 7386) | --type=merge | remplace la liste entière par celle du patch | ressources personnalisées, champs simples |
| JSON patch (RFC 6902) | --type=json | opère sur un index précis : add, replace, remove | ajout en fin de liste, suppression ciblée |
Le strategic merge est propre à Kubernetes : chaque type natif déclare
comment fusionner ses listes, containers par le nom du conteneur, ports par
le numéro de port. Le JSON merge ignore ces règles et traite toute liste
comme une valeur unique. Le JSON patch ne fusionne rien : il décrit des
opérations sur des chemins.
Le piège des listes : strategic conserve, merge remplace
Section intitulée « Le piège des listes : strategic conserve, merge remplace »Le même patch, qui ne cite que le conteneur web, garde log-agent en
strategic et le supprime en merge. C'est la différence la plus importante de
cette leçon, et elle se vérifie en deux commandes. D'abord en strategic,
le type par défaut, pour passer web en nginx 1.30 :
kubectl -n boutique patch deploy web -p '{"spec":{"template":{"spec":{"containers":[{"name":"web","image":"nginx:1.30@sha256:9bf97bd7714f5e24c1ccd545ecb9eb5435cb6d109c97cebb15e7e455e0239edb"}]}}}}'kubectl -n boutique get deploy web -o jsonpath='{range .spec.template.spec.containers[*]}{.name}{"\n"}{end}'weblog-agentLe strategic merge a retrouvé le conteneur web par son nom, changé
son image, et laissé log-agent intact. Le même fragment envoyé avec
--type=merge donne un tout autre résultat :
kubectl -n boutique patch deploy web --type=merge -p '{"spec":{"template":{"spec":{"containers":[{"name":"web","image":"nginx:1.27@sha256:6784fb0834aa7dbbe12e3d7471e69c290df3e6ba810dc38b34ae33d3c1c05f7d"}]}}}}'kubectl -n boutique get deploy web -o jsonpath='{range .spec.template.spec.containers[*]}{.name}{"\n"}{end}'webLe JSON merge a remplacé la liste entière par celle du patch :
log-agent a disparu, et le Deployment a lancé un rollout pour recréer
tous les Pods sans lui. Le serveur n'a rien refusé, puisque l'objet obtenu
reste valide. Retenez la règle : sur une liste d'une ressource native,
restez en strategic ou passez en json, jamais en merge.
Pour l'image seule, kubectl set image deploy/web web=<image> produit le
même effet que le patch strategic, en plus lisible. Le patch garde
l'avantage pour tout autre champ, et pour un script qui modifie plusieurs
champs d'un coup.
JSON patch : viser un index de liste
Section intitulée « JSON patch : viser un index de liste »Le JSON patch désigne un champ par un chemin, comme
/spec/template/spec/containers/0/env, et lui applique une opération. Les
segments sont séparés par des /, les index de liste commencent à 0,
et le segment spécial - désigne la fin d'une liste existante. Ajouter une
variable d'environnement au premier conteneur se fait en deux temps, selon que
la liste existe déjà ou non :
# La liste env n'existe pas encore : on la créekubectl -n boutique patch deploy web --type=json \ -p '[{"op":"add","path":"/spec/template/spec/containers/0/env","value":[{"name":"LOG_LEVEL","value":"debug"}]}]'
# Elle existe : on ajoute un élément à la finkubectl -n boutique patch deploy web --type=json \ -p '[{"op":"add","path":"/spec/template/spec/containers/0/env/-","value":{"name":"FEATURE_X","value":"on"}}]'
kubectl -n boutique get deploy web -o jsonpath='{.spec.template.spec.containers[0].env}'[{"name":"LOG_LEVEL","value":"debug"},{"name":"FEATURE_X","value":"on"}]L'opération remove retire un élément par son index
(/spec/template/spec/containers/0/env/1), et replace change une valeur
existante. Une opération replace sur un chemin absent échoue : le serveur
répond seulement The request is invalid: the server rejected our request due to an error in our request,
sans nommer le chemin fautif. Avant d'écrire un chemin, lisez la structure
réelle avec kubectl get deploy web -o json : un index se décale dès qu'un
élément est ajouté ou retiré.
Patcher une ressource personnalisée
Section intitulée « Patcher une ressource personnalisée »Le strategic merge n'existe que pour les types natifs : sur une ressource
personnalisée (CRD), utilisez --type=merge ou --type=json. Une ressource
personnalisée ne porte pas les règles de fusion de listes qui font le
strategic merge, et le serveur refuse ce format avec un message explicite :
error: application/strategic-merge-patch+json is not supported by demo.k-operer.io/v1, Kind=Koperer: the body of the request was in an unknown format - accepted media types include: application/json-patch+json, application/merge-patch+json, application/apply-patch+yamlLe même fragment passé avec --type=merge est accepté. Pensez-y avec les
objets d'un opérateur (certificats, bases de données, politiques) : ce sont
presque tous des ressources personnalisées, et le type par défaut de
kubectl patch y échoue.
kubectl replace : remplacer l'objet entier
Section intitulée « kubectl replace : remplacer l'objet entier »kubectl replace -f remplace l'objet existant par le contenu du fichier :
c'est une mise à jour, l'objet garde son identité et ses Pods ne sont pas
supprimés. Tout ce que le fichier ne mentionne pas reprend sa valeur par
défaut, ou disparaît s'il n'en a pas, comme une annotation. L'objet doit exister : sur un objet absent, la commande échoue avec
NotFound au lieu de le créer.
Que fait replace sans --force ?
Section intitulée « Que fait replace sans --force ? »Sans --force, replace ne supprime rien : l'objet garde le même uid, et
les Pods en place continuent de tourner. Mesuré sur le Deployment web en
passant replicas de 2 à 3 dans un export frais :
kubectl -n boutique get deploy web -o yaml > web.export.yaml# modifier replicas: 2 en replicas: 3 dans le fichierkubectl replace -f web.export.yamlkubectl -n boutique get pods -l app=webdeployment.apps/web replacedNAME READY STATUS RESTARTS AGEweb-66597f58f5-d88sg 2/2 Running 0 6sweb-66597f58f5-lnhst 2/2 Running 0 6sweb-66597f58f5-m9b9v 0/2 ContainerCreating 0 0sLes deux Pods existants sont restés, le troisième s'ajoute. Si le fichier
change le template des Pods, une image par exemple, le Deployment applique
sa stratégie habituelle : un rolling update, Pod par Pod, avec une
nouvelle révision dans kubectl rollout history. replace n'interrompt
donc pas plus le service qu'un apply portant le même changement.
Le danger est ailleurs : un champ absent du fichier est remis à sa valeur
par défaut. Envoyé avec un manifeste qui ne déclare pas replicas, replace
a ramené le Deployment de trois réplicas à un seul, la valeur par défaut,
sans avertissement. C'est le sens de l'aide de la commande : « the complete
resource spec must be provided ».
Pourquoi replace refuse-t-il un fichier exporté trop tôt ?
Section intitulée « Pourquoi replace refuse-t-il un fichier exporté trop tôt ? »Un fichier exporté par kubectl get -o yaml porte la resourceVersion de
l'objet à cet instant, et le serveur refuse de remplacer un objet qui a changé
depuis. Ce mécanisme, la concurrence optimiste, empêche d'écraser sans
le savoir la modification d'un collègue ou d'un contrôleur. Rejouer le même
export une seconde fois suffit à le déclencher :
Error from server (Conflict): error when replacing "web.export.yaml": Operation cannot be fulfilled on deployments.apps "web": the object has been modified; please apply your changes to the latest version and try againSur un Deployment, l'objet change sans que personne n'y touche : le
contrôleur met à jour son status, ce qui change la resourceVersion.
Pendant un rollout, un export vieux de quelques secondes est déjà périmé. Le
remède est de réexporter, réappliquer la modification et relancer. Un
fichier sans resourceVersion, comme un manifeste de Git, n'est pas
contrôlé : il remplace l'objet quel que soit son état, ce qui écrase aussi la
modification concurrente que le conflit aurait signalée.
Que faire d'un champ immuable ?
Section intitulée « Que faire d'un champ immuable ? »Certains champs ne changent plus après la création, comme le selector d'un
Deployment, et replace les refuse comme patch ou apply. Le serveur
nomme le champ en cause :
The Deployment "web" is invalid: spec.selector: Invalid value: {"matchLabels":{"app":"web2"}}: field is immutableAucune mise à jour ne contourne cette règle : il faut supprimer puis
recréer l'objet. C'est exactement ce que fait replace --force, avec ses
conséquences.
replace --force : supprimer puis recréer
Section intitulée « replace --force : supprimer puis recréer »kubectl replace --force supprime l'objet puis le recrée depuis le fichier :
ce n'est plus une mise à jour, et tous les Pods d'un Deployment sont arrêtés
d'un coup. La sortie l'annonce en deux lignes :
kubectl replace --force -f web.yamlkubectl -n boutique get pods -l app=webdeployment.apps "web" deleted from boutique namespacedeployment.apps/web replacedNAME READY STATUS RESTARTS AGEweb-66597f58f5-b8pnx 0/2 Pending 0 0sweb-66597f58f5-d88sg 2/2 Terminating 0 15sweb-66597f58f5-lnhst 2/2 Terminating 0 15sweb-66597f58f5-tzfsh 0/2 ContainerCreating 0 0sLes anciens Pods passent tous en Terminating pendant que les nouveaux
démarrent à peine : aucun n'est prêt, et un Service qui vise ces Pods n'a
plus de cible. L'objet
recréé porte un nouvel uid et repart de la révision 1, son historique de
rollout est perdu. Réservez --force au seul cas qui l'exige, un champ
immuable à changer, dans une fenêtre où l'interruption est acceptée.
replace ou apply ?
Section intitulée « replace ou apply ? »apply calcule une différence et ne touche que les champs qu'il gère ;
replace envoie l'objet entier et réinitialise tout ce que le fichier omet.
C'est pour cette raison qu'apply reste le choix par défaut dès qu'un autre
acteur, un autoscaler ou un opérateur, écrit sur le même objet.
| Critère | kubectl replace | kubectl apply |
|---|---|---|
| Objet absent | échec NotFound | création |
| Champ absent du fichier | remis à sa valeur par défaut | laissé tel quel s'il n'a jamais été appliqué |
| Fichier exporté périmé | conflit de resourceVersion | sans objet, apply ne la lit pas |
| Usage | imposer un fichier complet, ponctuellement | gestion déclarative au quotidien |
Un replace depuis un fichier qui ne porte pas l'annotation
kubectl.kubernetes.io/last-applied-configuration la retire de l'objet : le
kubectl apply suivant affiche alors un avertissement et la repose. Ce
message n'a rien de grave, il signale qu'une commande impérative est
passée par là.
Comment revenir en arrière après une modification ratée ?
Section intitulée « Comment revenir en arrière après une modification ratée ? »Pour un Deployment, kubectl rollout undo revient à la révision précédente
du template de Pods ; pour tout autre objet, réappliquez la version de Git.
Le rollback natif est enseigné dans la leçon
rolling updates et rollbacks :
il ne couvre que le template, pas replicas ni les annotations, et ne
fonctionne pas après un replace --force, qui a effacé l'historique.
Pour un objet sans historique, comme un ConfigMap ou un Service, la source
fiable reste le manifeste versionné : kubectl apply -f. À défaut, la
sauvegarde prise avant l'intervention se rejoue avec kubectl replace -f,
après avoir retiré sa ligne resourceVersion, sans quoi le serveur répond par
le conflit vu plus haut.
Dépannage
Section intitulée « Dépannage »Les messages de ces trois commandes viennent soit du serveur API, qui refuse un objet invalide ou une écriture concurrente, soit de kubectl lui-même. Le tableau reprend les messages exacts relevés en 1.37, pour les reconnaître du premier coup d'œil.
| Symptôme | Cause | Solution |
|---|---|---|
Edit cancelled, no changes made. | fichier enregistré sans modification | rien à faire, aucun changement envoyé |
Edit cancelled, no valid changes were saved. | modification refusée par l'API | relancer kubectl edit et corriger, la saisie refusée reste lisible dans /tmp |
the object has been modified; please apply your changes to the latest version | resourceVersion périmée | réexporter, refaire la modification, relancer |
field is immutable | champ non modifiable, comme spec.selector | replace --force dans une fenêtre d'interruption acceptée |
NotFound sur un replace | l'objet n'existe pas | kubectl apply -f ou kubectl create -f |
The request is invalid sur un patch json | chemin absent ou index faux | lire la structure avec kubectl get -o json |
strategic-merge-patch+json is not supported | ressource personnalisée | --type=merge ou --type=json |
| un conteneur a disparu après un patch | --type=merge sur une liste | repatcher en strategic, ou kubectl rollout undo |
| la correction est annulée le lendemain | dérive écrasée par un apply ou par GitOps | reporter le changement dans le manifeste |
À retenir
Section intitulée « À retenir »kubectl editest l'outil de l'incident : sauvegardez l'objet avant, reportez le changement dans Git après.kubectl patchne touche que les champs qu'il nomme ; le type par défaut est le strategic merge.- Sur une liste, strategic fusionne par clé (le nom du conteneur), merge remplace la liste entière, json opère sur un index.
- Une ressource personnalisée refuse le strategic merge :
--type=mergeou--type=json. kubectl diff -frévèle la dérive : code 1 tant que le cluster diffère du manifeste versionné.kubectl replacesans--forcemet à jour l'objet en place, mais remet à leur valeur par défaut les champs absents du fichier et refuse uneresourceVersionpérimée.replace --forcesupprime puis recrée : tous les Pods tombent d'un coup, l'historique de rollout est perdu.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Ce quiz vérifie que vous savez choisir la commande, prévoir l'effet d'un
type de patch sur une liste, et anticiper ce que fait replace avec et
sans --force.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
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
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Préparer une maintenance
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications