Lyon, le 2 octobre 2026
CloudNativePG ou comment embarquer un éléphant sur un porte-conteneurs !
Il faut s’attendre à de nombreux changements lorsqu’une instance est déployée dans Kubernetes. L’une d’entre elles concerne la gestion de la mémoire et plus spécifiquement le mécanisme d’éviction en cas de surconsommation mémoire. Regardons cela de plus près !

Good old days
L’installation sur serveur physique ou machine virtuelle était, jusqu’à peu, la méthode privilégiée pour déployer PostgreSQL. Dans la majorité des cas, les machines utilisées étaient, et sont encore, dédiées à nos bases de données.
Une machine = une instance PostgreSQL. Le principe KISS à l’état pur.
Sur ces environnements dédiés, il est communément admis que la désactivation de
la surallocation mémoire
(paramètres vm.overcommit_memory et vm.overcommit_ratio)
est une bonne pratique. La documentation officielle de PostgreSQL
explique pourquoi.
Pour les détails, voir notre base de connaissance.
Et maintenant ?
Les besoins évoluent et justifient parfois le déploiement de centaines, voire de milliers d’instances. Un environnement conteneurisé, et à fortiori un environnement Kubernetes, peut être le socle de la stack technique pour répondre à ce besoin.
Mais les règles du jeu changent :
- La désactivation de l’overcommit memory n’est pas systématique, voire impossible (par exemple le mode CIS Hardening de RKE2 et le fichier rke2-cis-sysctl.conf);
- Votre hébergeur ne vous laissera pas accéder aux
Nodespour faire de telles configurations ; - D’autres mécanismes du noyau rentrent en jeu (cgroups) ;
- Vos bases de données peuvent cohabiter avec des
Podsapplicatifs ; - …
Tout cela pour dire qu’il faut revoir notre copie et adapter nos pratiques avec les possibilités offertes par Kubernetes.
QoS des Pods
Dans Kubernetes, il existe le concept de QoS. À chaque Pod est associée une
classe de service qui, selon la configuration en ressources, modifie les
règles de déploiement du Pod et est utilisée en cas d’éviction de
Pods. L’idée de cet article n’est pas de détailler ce concept. Pour cela, nous
vous renvoyons à la
documentation officielle.
Ce qu’il faut savoir, c’est qu’il en existe trois qui sont attribuées selon les
requests et limits que vous définissez sur la ressource Pod.
- Best Effort ;
- Burstable ;
- et Guaranteed.
Selon la QoS, des ajustements sont faits au niveau des processus et la politique d’éviction change.
Surconsommation et OOM Killer
Sur un système Linux, si un processus consomme trop de mémoire, l’OOM Killer peut être appelé pour chercher à en libérer afin d’éviter de saturer le système. Le meilleur moyen de soulager le système est d’arrêter un programme en cours de fonctionnement… un arrêt que l’on pourrait qualifier de « brutal ».
L’OOM Killer sélectionne le processus à cibler en fonction d’un score calculé par le noyau. Pour faire simple, le processus avec le score le plus élevé est évincé. Il existe des moyens pour ajuster le score d’un processus en particulier.
L’OOM Killer n’a pas connaissance du traitement effectué par tel ou tel
processus. Concrètement, avec PostgreSQL, il faut à tout prix éviter un kill du
service postmaster, donc le rendre moins susceptible à être ciblé.
Trois Clusters, trois QoS et des comportements différents
Déployons trois Clusters avec des QoS différentes (dépend des requests et
limits) et voyons quels sont les changements visibles.
Notez qu’ici, la désactivation de l’overcommit n’a pas été faite.
cluster-besteffort:- Pas de section
resources;
- Pas de section
cluster-burstable:requests: memory: "300Mi" cpu: "50m"cluster-guaranteed:requests: memory: "2Gi" cpu: "0.2" limits: memory: "2Gi" cpu: "0.2"
Regardons notamment les paramètres oom_score et oom_score_adj des processus
PostgreSQL postmaster de chaque Cluster.
| Cluster | oom_score | oom_score_adj |
|---|---|---|
cluster-besteffort |
1333 | 1000 |
cluster-burstable |
1321 | 1000 |
cluster-guaranteed |
3 | -997 |
Pour information, voici une manière de trouver le
piddupostmaster:kubectl exec -it cluster-besteffort-1 -c postgres -- cat /var/lib/postgresql/data/pgdata/>postmaster.pid | head -n1 36
Ce qui ressort de ce tableau est que Kubernetes modifie les valeurs du oom_score_adj des
processus (ici postmaster, mais ce serait pareil pour d’autres processus
présents dans le Pod) en fonction de la QoS du Pod. Cela a pour conséquence
de modifier le oom_score qui lui est calculé à partir de plusieurs paramètres
(voir la
formule)
ajustée par oom_score_adj.
Ainsi, si ces trois Clusters sont sur un même Node Kubernetes et qu’une
pression au niveau de la mémoire a lieu, le noyau fait appel à
l’OOM Killer pour retrouver de la mémoire. Ce dernier cible donc en priorité
un processus issu de cluster-besteffort ou cluster-burstable.
Là encore, il n’y a pas moyen de savoir ce que fait vraiment le processus ciblé et s’il est préférable d’arrêter tel ou tel processus parmi les cibles potentielles.
Avec PostgreSQL, on préférait que des processus enfants (par exemple un backend
client) soient ciblés plutôt que postmaster. C’est possible avec la variable
d’environnement PG_OOM_ADJUST_VALUE.
CloudNativePG en rajoute une couche
Ainsi, lors de la création d’un Cluster CloudNativePG qui a une QoS
positionnée à guaranteed, l’opérateur effectue une opération supplémentaire
en ajustant la variable d’environnement PG_OOM_ADJUST_VALUE à 0. Ceci est
fait uniquement pour la QoS guaranteed.
postgres@cluster-guaranteed-1:/$ ps -aux
postgres 3749 0.0 0.0 226404 13200 ? Ss 10:40 0:00 postgres: cluster-guaranteed: postgres postgres [local] idle
[...]
postgres 3890 0.0 0.0 6412 3768 pts/0 R+ 10:48 0:00 ps -aux
postgres@cluster-guaranteed-1:/$ cat /proc/3749/oom_score_adj
0
postgres@cluster-guaranteed-1:/$ cat /proc/3749/oom_score
666
Dans cet exemple, le backend client au pid 3749 a bien son score
d’ajustement (oom_score_adj) à 0 et donc un oom_score bien plus élevé que
postmaster, en l’occurrence, 666 (score généralement rencontré pour des
processus consommant peu de mémoire).
OOM Killer en action
Comprendre pourquoi telle ou telle valeur est affectée et comment cela influence
l’éviction de processus et de Pod est nécessaire. Mais concrètement, qu’est-ce
que cela change dans ma vie de DBA, SRE ou DevOps ?
Faisons en sorte de déclencher une pression mémoire et observons ce qu’il se passe. Pour cela, exécutons l’ordre SQL de tri suivant en veillant à augmenter drastiquement la mémoire de travail utilisable. Il ne faut évidemment pas tenter ceci en production !
SET work_mem = '1000GB';
EXPLAIN (ANALYZE) SELECT i FROM generate_series (1,3e9) i ORDER BY i DESC ;
(Noter que le DBA n’a aucun moyen d’empêcher un utilisateur de faire cela !)
Cet ordre est lancé dans chacun des trois Clusters via psql. Les traces
sont remontées des traces noyau du système par sudo journalctl --utc -fke.
cluster-besteffort
Lors du test, le kube-controller a fait appel au OOM Killer du système car
une allocation mémoire lui a été impossible.
kernel: kube-controller invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP)…
Un peu plus loin, le process ciblé est indiqué task=postgres,pid=613477. Il
consommait environ 7,8 Go de mémoire (8176716kB). L’OOM Killer global a été
invoqué : global_oom; car il s’agit d’une allocation impossible. Notez
également que le oom_score_adj du processus tué se trouve mentionné à la fin
de la ligne (1000).
kernel: oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=cri-containerd-e51b72c22b5f0f9bd976a36b7e33183d364d6562623c0eb8d1e68ee1eaa99321.scope,mems_allowed=0,global_oom,task_memcg=/system.slice/docker-0d3ac421a88f3823ef43f68cfc… ,task=postgres,pid=613477,uid=26
kernel: Out of memory: Killed process 613477 (postgres) total-vm:9011764kB, anon-rss:8176716kB, file-rss:160kB, shmem-rss:4308kB, UID:26 pgtables:16212kB oom_score_adj:1000
Le système a donc cherché, trouvé et arrêté un processus consommateur sur le
Node : Killed process 613477 (postgres).
Détail qui a son importance, le processus était « rattaché » à d’autres
processus. On peut notamment le voir avec le paramètre task_memcg (tronqué
dans l’exemple de trace) et
are going to be killed due to memory.oom.group set. La description faite dans
le message du
commit
est très claire. L’idée est de traiter le cgroup comme une entité unique.
kernel: Tasks in /system.slice/docker-0d3ac421a88f3823ef43f68cfcad6de7739069b01ff51d2ed918f8659e93702e.scope/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-besteffort.slice/kubelet-kubepods-besteffort-pod6e7d6a9d_2946_4aa6_9c74_c866aadf17e1.slice/cri-containerd-342c7d2864fbb3eed1c4645e39db632ae0d0ec41676780b6f6af047e252ab011.scope are going to be killed due to memory.oom.group set
Ces autres processus ne sont ni plus ni moins que ceux présents dans le
conteneur, autrement dit, ceux associés au même
cgroup… donc le postmaster
lui-même.
kernel: Out of memory: Killed process 611483 (manager) …
kernel: Out of memory: Killed process 611551 (postgres) …
kernel: Out of memory: Killed process 611552 (postgres) …
kernel: Out of memory: Killed process 611553 (postgres) …
kernel: Out of memory: Killed process 611554 (postgres) …
kernel: Out of memory: Killed process 611555 (postgres) …
kernel: Out of memory: Killed process 611556 (postgres) …
kernel: Out of memory: Killed process 611557 (postgres) …
kernel: Out of memory: Killed process 611559 (postgres) …
kernel: Out of memory: Killed process 611560 (postgres) …
kernel: Out of memory: Killed process 611561 (postgres) …
kernel: Out of memory: Killed process 611562 (postgres) …
kernel: Out of memory: Killed process 613471 (psql) …
kernel: Out of memory: Killed process 613477 (postgres) …
Ce qui explique que le Pod a été redémarré. D’un point de vue PostgreSQL, on
peut dire que l’instance a connu un crash. Elle redémarre donc en mode
recovery. Dans les traces de PostgreSQL, lors du redémarrage, la ligne
suivante est visible.
"message": "database system was not properly shut down; automatic recovery in progress
cluster-burstable
Lors du test sur le deuxième Cluster, les mêmes observations sont faites.
- Un processus (ici
python3) sur leNodedemande de la mémoire. - Comme l’allocation ne peut pas être faite, ce même programme invoque l’OOM
Killer. Le programme
python3qui tourne sur la machine, n’est pas responsable du manque de mémoire ; - Le processus ciblé est le processus PostgreSQL qui exécute le tri. Il consomme environ 8 Go de RAM , trop pour les capacités de la machine ;
- Tous les processus du cgroup sont évincés ;
- L’instance redémarre en mode recovery puis redevient accessible.
Ceci n’est pas surprenant, puisque nous avons vu que rien ne diffère d’un point
de vue mémoire et sur la configuration oom_*.
cluster-guaranteed
Pour ce dernier cas, l’OOM Killer n’est pas invoqué par n’importe quel
processus, mais par un processus postgres, processus interne au conteneur
où est exécuté le tri.
kernel: postgres invoked oom-killer: gfp_mask=0xcc0(GFP_KERNEL), order=0, oom_score_adj=0
kernel: CPU: 3 UID: 26 PID: 614252 Comm: postgres …
Notons le pid du processus : 614252.
On retrouve dans les traces la quantité de mémoire et la limite imposée atteint par le cgroup.
kernel: memory: usage 2097152kB, limit 2097152kB, failcnt 871
Cette limite correspond bien aux 2 Go indiqués dans la définition des resources
du Cluster cluster-guaranteed. Le journal de traces remonte les processus en
cours au moment de l’invocation de l’OOM Killer.
kernel: Tasks state (memory values in pages):
kernel: [ pid ] uid tgid total_vm rss rss_anon rss_file rss_shmem pgtables_bytes swapents oom_score_adj name
kernel: [ 577430] 26 577430 257 1 0 1 0 40960 10 -998 pause
kernel: [ 607179] 26 607179 347284 4631 4622 9 0 266240 0 -997 manager
kernel: [ 607224] 26 607224 56071 4675 643 428 3604 172032 0 -997 postgres
kernel: [ 607225] 26 607225 17249 640 638 2 0 118784 0 0 postgres
kernel: [ 607226] 26 607226 56104 1317 657 2 658 163840 0 0 postgres
kernel: [ 607227] 26 607227 56104 728 653 2 73 135168 0 0 postgres
kernel: [ 607228] 26 607228 56104 873 657 2 214 163840 0 0 postgres
kernel: [ 607229] 26 607229 56106 2172 669 2 1501 151552 0 0 postgres
kernel: [ 607230] 26 607230 56104 1278 655 209 414 143360 0 0 postgres
kernel: [ 607237] 26 607237 56104 1890 656 105 1129 139264 0 0 postgres
kernel: [ 607238] 26 607238 56472 1806 722 758 326 159744 0 0 postgres
kernel: [ 607239] 26 607239 56104 1021 656 264 101 131072 0 0 postgres
kernel: [ 607240] 26 607240 56472 939 716 2 221 143360 0 0 postgres
kernel: [ 614238] 26 614238 4002 1380 278 1102 0 73728 0 -997 psql
kernel: [ 614252] 26 614252 597541 514260 511082 2136 1042 4300800 0 0 postgres
En dernière ligne, on retrouve le processus 614252 à l’initiative de l’appel
au OOM Killer. C’est ce processus qui consomme le plus de mémoire, un peu
moins de 2 Go. Au passage, notez que tous les processus enfants à postmaster
ont bien hérité du paramètre PG_OOM_ADJUST_VALUE à 0.
Le kill est initié, mais cette fois-ci avec une contrainte différente
constraint=CONSTRAINT_MEMCG. Celle-ci indique que l’éviction d’un processus ne
peut se faire que dans le conteneur en question. Ceci est logique : quel serait
l’intérêt d’arrêter des processus en dehors de ce cgroup s’il est nécessaire de
retrouver de la mémoire au sein de ce cgroup ?
sept. 21 06:52:05 lenovo-pro kernel: oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=cri-containerd-683…,mems_allowed=0,oom_memcg=/system.slice/docker-0d3ac421a88f3823ef43f68cfcad6de7739069b01ff51d2ed918f8659e93702e.scope/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-pod34c24b1b_f063_4779_b778_1a490b0cab1b.slice,task_memcg=/system.slice/docker-0d3ac…e2eaa7.scope,task=postgres,pid=614252,uid=26
Le processus 614252 est donc arrêté.
kernel: Memory cgroup out of memory: Killed process 614252 (postgres) total-vm:2390164kB, anon-rss:2044328kB, file-rss:8544kB, shmem-rss:4168kB, UID:26 pgtables:4200kB oom_score_adj:0
Notez que le début de la ligne de trace diffère d’un OOM Kill global. Ici il
s’agit d’un Memory cgroup out of memory alors que, dans les deux autres
exemples, il s’agissait d’un Out of memory.
Là aussi, tous les processus du cgroup sont évincés comme memory.oom.group
est utilisé. L’instance redémarre en mode recovery puis redevient accessible.
Par ailleurs, il est à noter que dans cet exemple, comme dans ceux d’avant,
tous les processus redémarrent, postmaster compris.
En effet, la disparition brusque d’un backend est suspecte et le processus
principal préfère préserver l’intégrité des données en redémarrant tout.
Là aussi, toutes les
connexions autres qui pouvaient exister sont coupées brutalement et devront être
re-initiées après le redémarrage de l’instance.
Une différence notable est à souligner. Vous l’avez peut-être compris, mais dans
le cas d’un Cluster en QoS Guaranteed, la consommation RAM n’a pas pu
dépasser les 2 Go (limits.memory), là où avec les autres QoS, la limite de
consommation par le processus de tri a atteint les 8 Go.
La consommation de 8 Go de RAM a eu pour conséquence d’épuiser les ressources du système qui étaient de 16 Go.
Conclusion
Nous l’avons vu, le déploiement de PostgreSQL sur Kubernetes implique des
changements de fonctionnement à très bas niveau, notamment sur la gestion
mémoire. Le diagnostic d’un out of memory dépend également de la QoS du Pod.
Cette QoS dépend directement de la configuration des ressources allouées à
celui-ci. CloudNativePG gère une préconisation des développeurs de PostgreSQL en
ce qui concerne le score d’ajustement oom_score_adj pour les processus
enfants pour une QoS Guaranteed.
Plusieurs choses sont à retenir :
-
Si l’
overcommit_memoryduNoden’est pas désactivé, l’arrêt d’un processuspostgresde type backend client aura pour conséquence l’arrêt et le redémarrage de l’instance, peu importe la QoS choisie. La QoS Guaranteed limite le risque d’un arrêt brutal depostmaster. -
Cette même QoS vous permet de garder le contrôle sur la consommation mémoire du
Pod; -
Et enfin, vous évitez une surconsommation qui pourrait avoir des effets de bord sur le reste du système. D’ailleurs, nous l’avons vu dans les exemples, les deux premiers appels OOM Killer ont été faits par deux outils sans rapport avec PostgreSQL qui se trouvaient sur le système :
python3etkube-controller.
Des questions, des remarques ?
** Écrivez-nous !**
- PostgreSQL (488) ,
- Dalibo (205) ,
- CloudNativePG (21) ,
- SQL (14) ,
- Kubernetes (5) ,
- 2026 (5) ,
- planetpgfr (56)