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.

moteur

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 !


DALIBO

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