Lyon, le 9 septembre 2025
CloudNativePG ou comment embarquer un éléphant sur un porte-conteneurs !
Les extensions font partie des nombreux avantages de PostgreSQL. Elles
permettent de rajouter des fonctionnalités à nos bases de données en chargeant des
modules complémentaires comme PostGIS
ou
encore PostgreSQL Anonymizer
.
CloudNativePG embarque déjà quelques extensions. Mais si l’on veut en rajouter
une, comment cela se passe-t-il 🤔 ?
C’est ce que nous allons voir avec une nouvelle fonctionnalité de la version
1.27 de l’opérateur et de l’utilisation du nouveau paramètre
extension_control_path
de la version 18 de PostgreSQL.
Les extensions disponibles
Au moment de la rédaction de ce post, une quarantaine d’extensions sont disponibles dans une instance PostgreSQL déployée avec CloudNativePG. Cela peut facilement être vu avec la commande suivante :
postgres=# SELECT COUNT(*) FROM pg_available_extensions;
count
-------
46
(1 row)
Ces extensions-là sont disponibles, car elles se trouvent être ajoutées à l’image lors de sa construction. Certains opérateurs en embarquent plus, d’autres beaucoup moins. Il est donc intéressant de vérifier celles qui sont déjà déployées et dans quelles versions est disponibles.
Jusqu’à présent, si nous souhaitions utiliser une extension qui ne faisait pas partie de cette liste, il était nécessaire de modifier l’image utilisée pour l’intégrer. Bien que tout à fait faisable, cela n’était pas très flexible.
La version 18 de PostgreSQL apporte une nouveauté avec le paramètre
extension_control_path
qui permet d’indiquer d’autres emplacements où peuvent
être trouvées des extensions. L’idée des développeurs de CloudNativePG est de
mettre à jour ce paramètre en fonction des nouvelles extensions que nous
ajoutons pour prendre en compte dynamiquement les ajouts. Nous verrons plus tard
sur quel mécanisme de Kubernetes se repose cette fonctionnalité.
L’idée est de présenter notre nouvelle extension via un nouveau conteneur à
notre Pod
. Et qui dit conteneur, dit image 📦.
Création d’une extension
Bon pour commencer, il nous faut une extension. Pour les plus curieux, vous
pouvez trouver des explications sur comment faire dans cette suite d’articles.
Je me suis clairement inspiré de celui-ci, et ai donc deux fichiers dans un
dossier /share/
:
$ cat ./share/monextension--1.0.sql
\echo Ne pas exécuter ce script, mais passer par CREATE EXTENSION
CREATE OR REPLACE FUNCTION incremente(int)
RETURNS int
LANGUAGE sql
AS 'SELECT $1+1';
$ cat ./share/monextension.control
comment = 'Mon extension'
default_version = '1.0'
Lorsque cette extension sera installée dans la base de données, la
fonction incremente()
sera disponible.
Création de l’image
Les fichiers de l’extension seront ajoutés au Pod
PostgreSQL via le mécanisme
d’ImageVolume
de Kubernetes. Cette fonctionnalité doit être activée sur votre
cluster. Elle est toujours en version
beta
dans la version 1.33 de Kubernetes et notez que tous les container runtime ne
supportent pas encore cela. Elle permet d’attacher un volume à partir du contenu
d’une image sans devoir modifier l’image principale. Ici, nous allons empaqueter
nos fichiers d’extensions.
Le Dockerfile
de notre exemple est ultra simpliste :
FROM debian:bookworm
RUN mkdir -p /share/extension
COPY ./share /share/extension
L’image se repose sur debian:bookworm
et ne fait que créer un dossier
/share/extension
en y incluant les fichiers de notre extension. Le dossier
/share/extension
est un pré-requis que doit respecter notre image.
La commande docker build -t monextension:1.0
permet de créer notre image
localement :
sudo docker build -t monextension:1.0 .
[+] Building 1.6s (8/8) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 120B 0.0s
=> [internal] load metadata for docker.io/library/debian:bookworm 1.5s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/3] FROM docker.io/library/debian:bookworm@sha256:731dd1380d6a8d170a695dbeb17fe0eade0e1c29f654cf0a3a07f372 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 119B 0.0s
=> CACHED [2/3] RUN mkdir -p /share/extension 0.0s
=> CACHED [3/3] COPY ./share /share/extension 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:f267ee02248f74f371a68ebd37287f4727639342f5b4305e5d21670149195b72 0.0s
=> => naming to docker.io/library/monextension:1.0 0.0s
Il nous reste à rendre cette image disponible pour notre cluster Kubernetes.
Vous pouvez passer par une registry publique. Ici, pour faire beaucoup plus
simple, et comme kind
est utilisé, nous poussons l’image sur le Node
avec :
kind load docker-image monextension:1.0
Maintenant que l’image est prête, voyons comment la rajouter à notre Cluster
PostgreSQL.
Définition du Cluster
La définition du Cluster
est la suivante :
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:18beta2
instances: 1
storage:
size: 1Gi
postgresql:
extensions:
- name: monextension
image:
reference: monextension:1.0
La fin de ce fichier nous intéresse particulièrement avec la partie
spec.postgresql.extensions
qui liste les extensions à rajouter et les images
associées. Ici, l’extension monextension
dans sa version 1.0 va être rajoutée
en même temps que le Cluster
sera créé.
$ kubectl apply -f cluster.yaml
cluster.postgresql.cnpg.io/postgresql created
À la création, trois images seront désormais récupérées : deux pour le fonctionnement de PostgreSQL avec CloudNativePG et la troisième correspondant à l’extension.
kubectl describe pod postgresql-1 | grep image
Normal Pulled 29s kubelet Container image "ghcr.io/cloudnative-pg/cloudnative-pg:1.27.0" already present on machine
Normal Pulled 28s kubelet Container image "ghcr.io/cloudnative-pg/postgresql:18beta2" already present on machine
Normal Pulled 28s kubelet Container image "monextension:1.0" already present on machine
L’extension est désormais bien disponible dans notre instance :
kubectl cnpg psql postgresql
psql (18beta2 (Debian 18~beta2-1.pgdg110+1))
Type "help" for help.
postgres=# SELECT * FROM pg_available_extensions WHERE name = 'monextension';
name | default_version | installed_version | comment
--------------+-----------------+-------------------+---------------
monextension | 1.0 | | Mon extension
(1 row)
L’ordre CREATE EXTENSION
peut être passé pour l’installer et retrouver notre
fonction incremente
:
postgres=# CREATE EXTENSION monextension ;
CREATE EXTENSION
postgres=# SELECT incremente(41);
incremente
------------
42
(1 row)
CloudNativePG a procédé à la modification du paramètre extension_control_path
pour que l’instance puisse trouver la nouvelle extension dans le volume qui
vient d’être monté.
postgres=# show extension_control_path ;
extension_control_path
----------------------------------------
$system:/extensions/monextension/share
Cette nouvelle fonctionnalité est très intéressante et les quelques étapes ci-dessus montrent comment parvenir à l’exploiter. L’exemple est rudimentaire certes, mais bien fonctionnel et permet également d’aborder certaines choses qui fâchent 😆.
Quelques limites tout de même
L’ajout d’extension de manière déclarative dans Kubernetes a des avantages. L’avantage majeur est de ne pas devoir modifier l’image principale. Elle a également certaines limites. Celles-ci ne discréditent pas cette nouvelle fonctionnalité, mais il faut en avoir conscience !
La première des choses est la taille de l’image. Pour cet exemple, l’idée était
d’aller au plus simple et de ne pas trop se préoccuper de cette notion. Mais
avouez qu’utiliser une image d’environ 120 Mo pour ajouter deux fichiers de
quelques centaines octets… 🌱 écologiquement ce n’est pas terrible. Évidemment,
la clause FROM
du Dockerfile
peut être adaptée pour partir d’une image
plus légère et, soyons honnêtes il sera difficile de faire pire comme exemple
avec uniquement deux fichiers texte. À garder en tête tout de même.
Plus subtile cette fois-ci, je vous laisse essayer de rajouter une extension à
un Cluster
déjà en fonctionnement. La commande kubectl apply -f cluster.yaml
renvoie bien le message cluster.postgresql.cnpg.io/postgresql configured
.
Notre Cluster
est donc mis à jour, l’extension est disponible. Au passage,
vous aurez remarqué que l’instance a été redémarrée 💥. Et oui, un Rolling Update
de tous les Pods
liés au Cluster
est déclenché pour que le nouveau volume
puisse être monté sur les Pods
. C’est une petite différence avec la manière de faire sur
un environnement virtuel ou physique. Assurez-vous que l’ajout d’une extension
fonctionne bien sur un environnement de test avant de le faire en production.
Conclusion
Avec la dernière version de CloudNativePG, vous pouvez ajouter dynamiquement, au
démarrage des instances, de nouvelles extensions. Cette nouveauté, rendue
possible par le nouveau paramètre extension_control_path
de la version 18 de
PostgreSQL et le concept d’ImageVolume
de Kubernetes, ravira les utilisateurs
qui n’auront pas à modifier l’image principale du conteneur.
Cette nouvelle manière de faire rendra l’usage d’instance PostgreSQL dans Kubernetes plus flexible en ce qui concerne la gestion des extensions. Reste à attendre que toutes fonctionnalités deviennent stables, mais l’évolution est très intéressante ✨.
Des questions, des commentaires ? Écrivez-nous !