Lyon, le 24 mars 2025
CloudNativePG ou comment embarquer un éléphant sur un porte-conteneurs !
Beaucoup de choses vous ont été présentées dans les derniers articles de cette série. Bien qu’assez techniques et pratiques, ils ne présentaient pas vraiment de cas concrets, pouvant être rencontrés dans la vraie vie comme on dit. C’est chose faite avec le présent article qui s’attarde sur un problème bien connu des administrateurs PostgreSQL : la saturation de l’espace disque 💥 !
Cluster Kubernetes
Cette fois-ci, nous passons dans la cour des grands en utilisant un vrai cluster Kubernetes chez un fournisseur de cloud. Bon … presque la cour des grands, comme on se contentera d’utiliser un cluster managé, mais c’est déjà ça. Une fois déployé, nous installons l’opérateur et créons un cluster PostgreSQL avec un primaire et un secondaire à partir de ce fichier.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:17.0
instances: 2
storage:
size: 20Gi
walStorage:
size: 4Gi
affinity:
enablePodAntiAffinity: true
topologyKey: kubernetes.io/hostname
podAntiAffinityType: preferred
Une bonne pratique dans PostgreSQL souvent recommandée mais peu appliquée est d’avoir au moins deux espaces de stockage bien distincts :
- Un pour les données ;
- Un pour les journaux de transactions (WAL).
CloudNativePG respecte cette bonne pratique avec la spécification
spec.walStorage
. Lorsque le cluster est créé, deux Persistant Volumes
distincts seront créés automatiquement. Nous ne rentrerons pas dans les détails,
mais sachez qu’il est possible de les configurer plus finement, avec
par exemple le choix d’une Storage Class
.
Au passage, la version 17.0 de PostgreSQL date de quelques mois et il existe même la version 17.4 depuis quelques semaines 💡. Un article sur la montée de version mineure arrive, c’est pour cela que la version 17.0 est toujours utilisée.
Créons ce cluster. Attendons quelques minutes et voilà nos instances prêtes avec
pour chacune d’elles deux Persistent Volumes
. Les noms des CLAIMS
permettent
de savoir à quoi ils correspondent.
$ kubectl get persistentvolume -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage,CLAIM:.spec.claimRef.name
NAME CAPACITY CLAIM
pvc-09fe198a-ba85-440c-94b4-8de3d32af7dc 10Gi postgresql-1-wal
pvc-22e4f5c6-f9b0-4bf9-b375-49e7be2e528b 20Gi postgresql-1
pvc-9e8d0c70-c6b7-4941-86d1-7cc53fd67fbd 20Gi postgresql-2
pvc-d878ce9c-f85e-4e1b-85a6-f40296a4a318 10Gi postgresql-2-wal
Saturation de l’espace de stockage
Utilisons l’outil pg_bench
pour créer suffisamment de données et saturer
l’espace de stockage. Le paramètre --scale 2000
permet de générer une
trentaine de Go de données.
$ kubectl cnpg pgbench postgresql -- --initialize --scale 2000
job/postgresql-pgbench-876082 created
En regardant les traces du Pod
postgresql-1
(le primaire), nous voyons le
déclenchement de nombreux CHECKPOINT
. Un peu trop d’ailleurs, PostgreSQL nous
donne même une astuce :
"message": "checkpoints are occurring too frequently (15 seconds apart)",
"hint": "Consider increasing the configuration parameter \"max_wal_size\"."
Si vous souhaitez en savoir un peu plus, allez jeter un œil à notre article de
blog sur les CHECKPOINTS
📰.
Revenons à nos traces PostgreSQL. Un peu plus loin, nous trouvons le message de
mauvais augure No space left on device
.
"error_severity": "PANIC",
...
"message": "could not write to file \"pg_wal/xlogtemp.537\": No space left on device",
"context": "writing block 2081579 of relation base/16385/16403\nCOPY pgbench_accounts, line 127099000",
C’est à ce moment là que votre outil de surveillance vous remonte l’alerte
CRITICAL - Plus de place !
!
Plongeons dans les traces
Ne paniquons pas, regardons ce que nous disent les traces et essayons de comprendre ce qu’il se passe.
La copie de données par pgbench
ne peut plus se faire comme l’espace disque
est saturé. Le backend associé est arrêté :
"message": "server process (PID 537) was terminated by signal 6: Aborted",
"detail": "Failed process was running: copy pgbench_accounts from stdin with (freeze on)",
"backend_type": "postmaster",
À la suite de quoi, l’instance est arrêtée :
"message": "shutting down because \"restart_after_crash\" is off",
"backend_type": "postmaster",
Le paramètre restart_after_crash
étant à off
, l’instance PostgreSQL ne va pas
redémarrer automatiquement. Cela laisse la possibilité à l’opérateur de gérer
convenablement l’instance. Ce paramètre est fixé par l’opérateur, il n’est pas
possible de le changer. Il fait partie des Fixed Parameters
imposés par CloudNativePG.
L’instance-manager
donne également quelques indications :
{
"level": "info",
"ts": "2025-03-11T07:44:45.138680326Z",
"msg": "Checking for free disk space for WALs after PostgreSQL finished",
"logger": "instance-manager",
"logging_pod": "postgresql-1"
}
{
"level": "info",
"ts": "2025-03-11T07:44:45.157419701Z",
"msg": "Detected low-disk space condition",
"logger": "instance-manager",
"logging_pod": "postgresql-1"
}
L’opérateur va tenter de redémarrer lui-même l’instance. Avant de faire cela, il vérifie s’il reste de l’espace disque pour les journaux de transactions.
{
"level": "info",
"ts": "2025-03-11T07:44:45.202130923Z",
"msg": "Checking for free disk space for WALs before starting PostgreSQL",
"logger": "instance-manager",
"logging_pod": "postgresql-1"
}
Comme ce n’est pas le cas, l’instance ne sera pas redémarrée.
{
"level": "info",
"ts": "2025-03-11T07:44:45.222583176Z",
"msg": "Detected low-disk space condition, avoid starting the instance",
"logger": "instance-manager",
"logging_pod": "postgresql-1"
}
Nous nous retrouvons donc avec une instance primaire arrêtée car son espace
disque dédié aux journaux de transactions est saturé 😨. L’instance secondaire
attend quant à elle toujours un journal de transactions
"message": "waiting for WAL to become available at 2/5283F7F3"
.
Notez donc qu’en cas de saturation du volume de stockage, l’opérateur ne déclenche pas de bascule sur le secondaire. Avant toute chose, il faut résoudre ce problème d’espace disque.
Agrandissement du volume
Pour revenir à une situation normale, nous devons faire deux choses. La
première est d’agrandir l’espace défini dans le PVC
associé au volume des
journaux de transactions de l’instance postgresql-1
. La seconde est de faire
correspondre le paramètre spec.walStorage.size
de la définition du cluster
PostgreSQL à cette nouvelle taille.
Modifier la taille d’un PVC
à chaud n’est pas forcément possible ! Il est
nécessaire que la Storage Class
utilisée le permette via la fonctionnalité
de Volume Expansion
. Par défaut, la Storage Class
utilisée est
sbs-default
. C’est une classe de stockage fournie par le CSI
du fournisseur cloud. En
regardant ses informations, nous voyons qu’elle implémente bien cette capacité :
$ kubectl describe sc sbs-default | grep AllowVolumeExpansion
AllowVolumeExpansion: True
Comprenez donc qu’il s’agit d’un critère essentiel à prendre en compte lors du
choix du CSI
⚠️.
La modification du paramètre spec.resources.requests.storage
du PVC
postgresql-1-wal
est donc possible à chaud. 1 Go de plus devrait suffire.
Il ne reste plus qu’à modifier la valeur de spec.walStorage.size
à 5 Go et de
réappliquer la définition de notre cluster.
Le Pod
postgresql-1
va redémarrer et, contrairement à tout à l’heure, la
vérification de l’espace disque ne bloquera plus. L’instance-manager
va être
démarré suivi par tout le processus de redémarrage de l’instance PostgreSQL
jusqu’au fameux message de trace "database system is ready to accept connection"
.
Durant tout le procédé, l’instance postgresql-1
a gardé son rôle de primaire,
et l’instance postgresql-2
son rôle de secondaire. Nous nous retrouvons alors
dans la situation initiale.
Conclusion
Si vous recevez une alerte CRITICAL
sur un volume dédié aux WALs, vous savez
maintenant de quoi il en retourne. Vous serez très probablement confrontés à la
saturation de l’un ou l’autre de vos Persistant Volumes
. Préparez-vous à cela
et ayez en tête les différentes étapes à suivre pour résoudre ce problème.
Avant de déployer des instances dans Kubernetes, prenez le temps de bien
choisir le CSI
que vous utiliserez notamment sur les fonctionnalités qu’il
possède. La fonctionnalité de Volume Expansion
est un must have !
Des questions, des commentaires ? Écrivez-nous !