Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Modifier une ressource en production : edit, patch, replace

Par mis à jour le

Améliorer cette page

Logo Kubernetes

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.

  • Choisir entre kubectl edit, kubectl patch et kubectl replace selon 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 replace sans --force (conflit de version, champs immuables) et avec --force (suppression puis recréation)

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 Git
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: boutique
spec:
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.

BesoinCommandeCe qu'elle envoie au serveur
Corriger pendant un incidentkubectl editl'objet entier, après votre passage dans l'éditeur
Changer un champ, dans un scriptkubectl patchseulement les champs du patch
Imposer un fichier completkubectl replace -fl'objet entier, tel qu'écrit dans le fichier
Gérer l'objet au quotidienkubectl apply -fla 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.

Fenêtre de terminal
kubectl -n boutique edit deploy web

L'é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 modification
error: deployments.apps "web" is invalid # modification refusée par l'API
A 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 :

Fenêtre de terminal
kubectl -n boutique patch deploy web -p '{"spec":{"replicas":4}}'
kubectl diff -f web.yaml
- replicas: 4
+ replicas: 2

La 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 :

  1. Vérifiez le contexte et sauvegardez l'état actuel

    Fenêtre de terminal
    kubectl config current-context
    kubectl -n boutique get deploy web -o yaml > web.avant.yaml
  2. Modifiez avec kubectl edit ou kubectl patch, puis vérifiez le résultat avec kubectl -n boutique rollout status deploy/web.

  3. Mesurez l'écart avec le manifeste versionné

    Fenêtre de terminal
    kubectl diff -f web.yaml
  4. Reportez le changement dans web.yaml, faites-le relire et fusionner comme n'importe quelle pull request. kubectl diff -f web.yaml ne 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 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.

Fenêtre de terminal
kubectl -n boutique patch deploy web -p '{"metadata":{"annotations":{"incident-ref":"INC-2026-0142"}}}'
deployment.apps/web patched

Le 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.

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.

TypeOptionEffet sur une listeCas d'usage
Strategic merge--type=strategic (défaut)fusionne les éléments selon leur clé, par exemple name pour containersressources natives
JSON merge (RFC 7386)--type=mergeremplace la liste entière par celle du patchressources personnalisées, champs simples
JSON patch (RFC 6902)--type=jsonopère sur un index précis : add, replace, removeajout 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 :

Fenêtre de terminal
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}'
web
log-agent

Le 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 :

Fenêtre de terminal
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}'
web

Le 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.

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 :

Fenêtre de terminal
# La liste env n'existe pas encore : on la crée
kubectl -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 fin
kubectl -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é.

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+yaml

Le 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 -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.

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 :

Fenêtre de terminal
kubectl -n boutique get deploy web -o yaml > web.export.yaml
# modifier replicas: 2 en replicas: 3 dans le fichier
kubectl replace -f web.export.yaml
kubectl -n boutique get pods -l app=web
deployment.apps/web replaced
NAME READY STATUS RESTARTS AGE
web-66597f58f5-d88sg 2/2 Running 0 6s
web-66597f58f5-lnhst 2/2 Running 0 6s
web-66597f58f5-m9b9v 0/2 ContainerCreating 0 0s

Les 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 again

Sur 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.

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 immutable

Aucune 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.

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 :

Fenêtre de terminal
kubectl replace --force -f web.yaml
kubectl -n boutique get pods -l app=web
deployment.apps "web" deleted from boutique namespace
deployment.apps/web replaced
NAME READY STATUS RESTARTS AGE
web-66597f58f5-b8pnx 0/2 Pending 0 0s
web-66597f58f5-d88sg 2/2 Terminating 0 15s
web-66597f58f5-lnhst 2/2 Terminating 0 15s
web-66597f58f5-tzfsh 0/2 ContainerCreating 0 0s

Les 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.

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èrekubectl replacekubectl apply
Objet absentéchec NotFoundcréation
Champ absent du fichierremis à sa valeur par défautlaissé tel quel s'il n'a jamais été appliqué
Fichier exporté périméconflit de resourceVersionsans objet, apply ne la lit pas
Usageimposer un fichier complet, ponctuellementgestion 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.

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ômeCauseSolution
Edit cancelled, no changes made.fichier enregistré sans modificationrien à faire, aucun changement envoyé
Edit cancelled, no valid changes were saved.modification refusée par l'APIrelancer kubectl edit et corriger, la saisie refusée reste lisible dans /tmp
the object has been modified; please apply your changes to the latest versionresourceVersion périméeréexporter, refaire la modification, relancer
field is immutablechamp non modifiable, comme spec.selectorreplace --force dans une fenêtre d'interruption acceptée
NotFound sur un replacel'objet n'existe paskubectl apply -f ou kubectl create -f
The request is invalid sur un patch jsonchemin absent ou index fauxlire la structure avec kubectl get -o json
strategic-merge-patch+json is not supportedressource personnalisée--type=merge ou --type=json
un conteneur a disparu après un patch--type=merge sur une listerepatcher en strategic, ou kubectl rollout undo
la correction est annulée le lendemaindérive écrasée par un apply ou par GitOpsreporter le changement dans le manifeste
  • kubectl edit est l'outil de l'incident : sauvegardez l'objet avant, reportez le changement dans Git après.
  • kubectl patch ne 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=merge ou --type=json.
  • kubectl diff -f révèle la dérive : code 1 tant que le cluster diffère du manifeste versionné.
  • kubectl replace sans --force met à jour l'objet en place, mais remet à leur valeur par défaut les champs absents du fichier et refuse une resourceVersion périmée.
  • replace --force supprime puis recrée : tous les Pods tombent d'un coup, l'historique de rollout est perdu.

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

6 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

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn