Chaque objet Kubernetes porte un champ resourceVersion, incrémenté à chaque écriture réussie. Ce champ n’est pas une métadonnée décorative : c’est le mécanisme qui empêche deux écritures concurrentes sur le même objet de s’écraser silencieusement l’une l’autre, un risque réel dès qu’un contrôleur ou un script lit un objet, le modifie localement, puis tente de le réécrire.
Le problème que resourceVersion résout
Un scénario classique : un processus lit un objet (resourceVersion: 100), modifie une valeur en mémoire, puis tente d’écrire la version modifiée. Si un autre processus a modifié et réécrit ce même objet entre-temps (resourceVersion passé à 101), la modification du premier processus se base sur un état déjà obsolète. Sans protection, cette écriture écraserait silencieusement le changement du second processus, une perte de données invisible jusqu’à ce que quelqu’un remarque qu’un changement a disparu.
# Le resourceVersion capturé à la lecture
kubectl get configmap my-config -o jsonpath='{.metadata.resourceVersion}'
# 100
Le 409 Conflict : un échec explicite, pas un écrasement silencieux
Le serveur API Kubernetes exige que toute écriture (update, pas patch) inclue le resourceVersion lu au moment de la lecture précédente. Si ce resourceVersion ne correspond plus à la version actuelle de l’objet (parce qu’une autre écriture a eu lieu entre-temps), le serveur API rejette explicitement l’écriture avec une erreur 409 Conflict, plutôt que de l’accepter silencieusement et d’écraser le changement concurrent.
# Une écriture basée sur un resourceVersion obsolète
# échoue explicitement, elle n'écrase jamais en silence
kubectl apply -f my-config.yaml
# error: Operation cannot be fulfilled on configmaps "my-config":
# the object has been modified; please apply your changes
# to the latest version and try again
Pourquoi c’est appelé « concurrence optimiste »
Le modèle est dit optimiste parce qu’il ne verrouille jamais l’objet pendant sa lecture : n’importe qui peut lire et tenter d’écrire à tout moment, sans attendre. La détection du conflit se fait uniquement à l’écriture, au moment où le resourceVersion fourni est comparé à la version réelle. Ce choix privilégie le débit (pas de verrou qui bloque les lecteurs concurrents) au prix d’un échec occasionnel qui exige une nouvelle tentative, plutôt qu’un modèle pessimiste qui bloquerait chaque lecture jusqu’à la fin de l’écriture suivante.
Le pattern correct : relire, puis réessayer
Un conflit 409 n’est jamais une erreur permanente : la réponse correcte relit l’objet (obtenant son resourceVersion actuel), réapplique la modification voulue sur cet état à jour, puis retente l’écriture.
# Pattern générique : relire, réappliquer la modification,
# réessayer, jamais abandonner sur un simple 409
for attempt in range(5):
obj = api.read_namespaced_config_map(name, namespace)
obj.data["key"] = "new-value"
try:
api.replace_namespaced_config_map(name, namespace, obj)
break
except ApiException as e:
if e.status == 409:
continue
raise
Ce pattern de retry explique pourquoi les client libraries Kubernetes (client-go, les SDK officiels) intègrent souvent une logique de retry automatique sur 409, un détail d’implémentation qui masque ce mécanisme à qui ne l’a jamais rencontré directement.
Pourquoi kubectl patch évite souvent ce piège
kubectl patch (et le merge à trois voies de kubectl apply) ne fournit généralement pas de resourceVersion, ce qui lui permet d’appliquer une modification sans jamais se soucier de l’état intermédiaire de l’objet, au prix d’un risque différent : un patch qui modifie un champ sans jamais vérifier si ce champ a changé depuis la dernière lecture peut écraser un changement concurrent sur ce champ précis, une classe de risque distincte du conflit 409 explicite d’un update classique.
À retenir
Chaque objet Kubernetes porte un resourceVersion qui protège contre l’écrasement silencieux de deux écritures concurrentes : une écriture basée sur un resourceVersion obsolète échoue explicitement avec un 409 Conflict, plutôt que d’écraser un changement concurrent sans avertissement. Ce modèle de concurrence optimiste privilégie le débit en ne verrouillant jamais la lecture, au prix d’un pattern de retry (relire, réappliquer, réessayer) à implémenter dans tout code qui lit-modifie-écrit un objet. Comprendre ce mécanisme évite de traiter un 409 comme un bug plutôt que comme le comportement attendu, un détail qui compte dès qu’une industrialisation CI/CD écrit son propre contrôleur ou script d’automatisation Kubernetes.