Lyon, le 19 février 2026
CloudNativePG ou comment embarquer un éléphant sur un porte-conteneurs !
Lorsque l’on associe PostgreSQL et mise à jour, la première chose qui vient en tête est la mise à jour des binaires PostgreSQL. Si les instances sont déployées avec CloudNativePG, la gestion des versions de l’opérateur est également à prendre en compte.
Et Kubernetes dans tout ça ? Quel est l’impact d’une mise à jour de l’orchestrateur sur nos instances ? Voyons tout cela dans ce nouvel article.

Versions de Kubernetes et drain
Les nouvelles versions de Kubernetes sont disponibles très fréquemment. Seules les trois dernières versions mineures sont maintenues (actuellement 1.33, 1.34 et 1.35) et leur durée de vie est assez courte, généralement autour d’un an pour les dernières versions. Pour vous donner un exemple, la version 1.35 est sortie en décembre 2025 et ne sera plus supportée en février 2027.
Le cycle de vie est relativement rapide et il vous faudra mettre à jour vos
Nodes … où se trouveront vos instances PostgreSQL. Cela aura donc un impact
sur celles-ci et sur les applications qui s’y connectent.
La
documentation
Kubernetes donne les grandes lignes d’une mise à jour d’un
cluster Kubernetes. Sans rentrer dans les détails, les Pods de vos instances
PostgreSQL seront nécessairement évincés pour être redéployés sur d’autres
nœuds. On parle de drain. Une opération de drain marque le nœud comme
unschedulable et évince (comprendre détruit) les Pods.
Prenons un Cluster postgresql-prod avec deux instances.
$ kubectl get cluster postgresql-prod
NAME AGE INSTANCES READY STATUS PRIMARY
postgresql-prod 174m 2 2 Cluster in healthy state postgresql-prod-1
Les Pods postgresql-prod-1 et postgresql-prod-2 vont être supprimés des
nœuds sur lesquels ils fonctionnent pour pouvoir mettre à jour le nœud.
Supprimer les Pods PostgreSQL ? Heu comment ça ?… Peut-on au
moins avoir la main sur l’opération ? C’est ici que rentre en jeu les ressources
PodDisruptionBudget, ou PDB, ressource native de Kubernetes.
Ressource PodDisruptionBudget
Un PodDisruptionBudget est créé par défaut pour chaque Cluster. C’est
CloudNativePG qui se charge de cela. Cette ressource, native de Kubernetes,
indique la politique d’éviction des Pods que Kubernetes doit
respecter en cas de drain de Nodes. En l’occurrence, elle permet d’indiquer,
à Kubernetes, combien de Pods, respectant certains critères, doivent être
actifs lorsqu’une perturbation volontaire a lieu (ici la montée de version d’un
nœud).
CloudNativePG crée donc une règle spécifique pour nos Cluster PostgreSQL. Le
nom est suffixé de -primary. Pour notre exemple, on a donc un PDB
postgresql-prod-primary dont voici la définition.
$ kubectl describe pdb postgresql-prod-primary
Name: postgresql-prod-primary
Namespace: default
Min available: 1
Selector: cnpg.io/cluster=postgresql-prod,cnpg.io/instanceRole=primary
Status:
Allowed disruptions: 0
Current: 1
Desired: 1
Total: 1
Passons en revue les informations disponibles :
Name: est le nom de cette ressource ;Namespace: leNamespaceoù elle se trouve. Le même que leCluster;Min available: indique combien dePodsrespectant leSelectordoivent exister ;Selector: la liste des labels que doit avoir unPod, il faut comprendre ici :- le primaire (
cnpg.io/instanceRole) duClusterpostgresql-prod(cnpg.io/cluster) ;
- le primaire (
Ce PDB impose donc qu’il y ait toujours une instance primaire pour le
Cluster. Il suffit d’un PDB qui ne soit pas respecté pour que, par exemple,
le drain du Node ne puisse pas se faire.
Testons cela
Lorsqu’un drain est déclenché sur un Node, CloudNativePG va capter cette
information et va déclencher certaines opérations.
Prenons le cas du drain du Node kind-worker où se trouve l’instance
primaire postgresql-prod-1.
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
postgresql-prod-1 1/1 Running 0 3h 10.244.1.5 kind-worker
postgresql-prod-2 1/1 Running 0 179m 10.244.2.6 kind-worker2
Le PDB impose qu’il y ait toujours une instance primaire disponible.
Allons-y, lançons la commande de drain :
kubectl drain kind-worker --delete-emptydir-data --ignore-daemonsets
node/kind-worker already cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-h6hpf, kube-system/kube-proxy-swjbl
evicting pod default/postgresql-prod-1
error when evicting pods/"postgresql-prod-1" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod default/postgresql-prod-1
pod/postgresql-prod-1 evicted
node/kind-worker drained
Une première tentative de destruction du Pod postgresql-prod-1 a été
infructueuse. Le message d’erreur Cannot evict pod as it would violate the pod's disruption budget
est plus que parlant. Quelques instants plus tard, le
Pod a bien été évincé :
pod/postgresql-prod-1 evicted.
Entre les deux tentatives, une opération de switchover a été opérée par
l’opérateur. Dans les traces du controller, on peut notamment trouver cette
trace et l’explication dans le msg :
{
"level": "info",
"ts": "2026-02-16T20:12:34.942361272Z",
"msg": "Current primary is running on unschedulable node, triggering a switchover",
"controller": "cluster",
"controllerGroup": "postgresql.cnpg.io",
"controllerKind": "Cluster",
"Cluster": {
"name": "postgresql-prod",
"namespace": "default"
},
"namespace": "default",
"name": "postgresql-prod",
"reconcileID": "8c0bc954-a723-4aa6-95f6-2ddf4c5f173e",
"currentPrimary": "postgresql-prod-1",
"currentPrimaryNode": "kind-worker",
"targetPrimary": "postgresql-prod-2",
"targetPrimaryNode": "kind-worker2"
}
L’instance primaire fonctionne sur un nœud unschedulable (à cause du drain).
C’est la cause du switchover déclenché par l’opérateur. Celui-ci va faire en
sorte d’élire le Pod secondaire postgresql-prod-2 comme nouveau primaire
(targetPrimary). Quelques temps après, on peut voir que l’instance primaire
est désormais postgresql-prod-2.
kubectl get cluster
NAME AGE INSTANCES READY STATUS PRIMARY
postgresql-prod 3h12m 2 1 Waiting for the instances to become active postgresql-prod-2
À cet instant, un primaire existe bien dans le Cluster postgresql-prod. Il
se trouve sur un autre Node que celui qui est en cours de drain. Le PDB étant
respecté, l’opération de drain du Node peut se poursuivre avec l’éviction du
Pod postgresql-prod-1.
C’est à ce moment-là que vous pourrez faire la montée de version du Node
Kubernetes.
Avant de déclencher un drain d’un Node, nous vous suggérons de vérifier
qu’il n’existe pas de retard trop important entre vos instances secondaires et
l’instance primaire.
Et après ?
Pour les personnes les plus attentives, vous aurez remarqué que le Cluster
attend que toutes les instances soient actives.
La commande kubectl cnpg status postgresql-prod retourne le Status suivant :
Waiting for the instances to become active Some instances are not yet active. Please wait.
Bien que fonctionnel, le Cluster est en mode dégradé, car l’instance
postgresql-prod-1 doit toujours être re-déployée. Le problème ici est notamment
dû à la co-localité du PV et du Pod qui n’est pas respectée. L’instance
PostgreSQL doit se trouver sur le même Node que celui où se trouve son volume.
La co-localité des volumes et des instances doit être prise en compte lors du
choix du CSI et de la configuration associée.
Ce n’est pas le cœur de notre article de blog. On vous laissera donc vous rapprocher d’experts Kubernetes, mais gardez en tête cette contrainte de co-localité qui peut exister selon votre choix de système de stockage.
Supposons que la mise à jour du Node se soit bien déroulée, il est possible de
redéployer des Pods sur le Node.
Pour cela, l’opération de uncordon doit être lancée pour indiquer que le
Node peut être de nouveau utilisé.
$ kubectl uncordon kind-worker
node/kind-worker uncordoned
Quelques temps après, le Pod est redéployé et le Cluster est complètement
fonctionnel (healthy state).
$ kubectl get cluster
NAME AGE INSTANCES READY STATUS PRIMARY
postgresql-prod 19h 2 2 Cluster in healthy state postgresql-prod-2
Cas d’un Cluster mono-instance
Dans le cas où un drain est effectué sur un Node qui héberge un
Cluster mono-instance (instances: 1 dans le YAML) l’éviction du Pod ne se
fera pas et le message d’erreur suivant sera affiché en boucle.
error when evicting pods/"postgresql-prod-2" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod default/postgresql-prod-2
error when evicting pods/"postgresql-prod-2" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod default/postgresql-prod-2
error when evicting pods/"postgresql-prod-2" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
Il est possible de désactiver l’utilisation de PDB avec le champ
spec.enablePDB dans la définition du Cluster :
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-no-pdb
spec:
instances: 1
enablePDB: false
Pour autant que ce soit possible, il n’est pas recommandé de l’utiliser en production.
Conclusion
Nous venons de voir comment CloudNativePG utilise la ressource Kubernetes
PodDisruptionBudget pour protéger un Cluster PostgreSQL en cas de drain
sur un Node.
Le principe est d’éviter que Kubernetes n’évince une instance primaire de façon brutale lors d’une opération volontaire. Si une instance secondaire existe, une bascule est automatiquement effectuée sur celle-ci.
C’est un mécanisme qui est intéressant de connaître, notamment pour la mise à jour de vos nœuds Kubernetes.
Des questions, des commentaires ? Écrivez-nous !