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.

moteur

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 : le Namespace où elle se trouve. Le même que le Cluster ;
  • Min available : indique combien de Pods respectant le Selector doivent exister ;
  • Selector : la liste des labels que doit avoir un Pod, il faut comprendre ici :
    • le primaire (cnpg.io/instanceRole) du Cluster postgresql-prod (cnpg.io/cluster) ;

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 !


DALIBO

DALIBO est le spécialiste français de PostgreSQL®. Nous proposons du support, de la formation et du conseil depuis 2005.