Lyon, le 11 mars 2026

CloudNativePG ou comment embarquer un éléphant sur un porte-conteneurs !

Jusqu’à présent, nous avons abordé différents sujets liés à l’administration d’instances PostgreSQL avec CloudNativePG. On nous a récemment posé une question concernant les possibilités pour exécuter des requêtes dès la création d’un Cluster. Regardons ce que propose l’opérateur.

moteur

Le besoin

Le besoin rencontré est le suivant : les Clusters vont tous être créés sur le même modèle (RAM, CPU, etc) et seront utilisés par une application pour laquelle nous possédons déjà le modèle de données.

Nous voulons donc créer toute la structure d’une nouvelle base dès la création du Cluster. Une fois notre YAML appliqué, l’instance et la base de données seront directement utilisables par nos développeurs qui pourront connecter l’application dessus.

Les trois sections postInit

L’opérateur donne la possibilité d’exécuter des ordres SQL lors de la phase de bootstrap d’un Cluster via les sections postInitXXX. Elles sont localisées dans la section initdb.

Il en existe trois :

  • pour la base postgres : la section postInit;
  • pour la base template1 : la section postInitTemplate;
  • pour la base app : la section postInitApplication.

Les ordres SQL, indiqués dans l’une ou l’autre des sections, s’exécuteront dans la base correspondante dès la fin de la création de l’instance.

Ces ordres là sont lancés par le rôle postgres, qui est SUPERUSER. Attention donc aux requêtes que vous lancerez et à qui vous laisserez la possibilité d’utiliser ces sections.

Exemple d’un ordre SQL

Reprenons notre exemple où le modèle de données doit être créé dans une base de données, disons mybase qui sera utilisée par la future application. Prenons le cas simple où nous souhaitons créer une table avec la définition suivante :

CREATE TABLE employes (id serial, nom text, paye numeric(8,2));

Cet ordre doit être rajouté à la section spec.bootstrap.initdb.postInitApplicationSQL pour que l’ordre en question soit exécuté dans la base de données applicative mybase.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-sql
spec:
  instances: 2
  storage:
    size: 1Gi
  bootstrap:
    initdb:
      database: mybase
      owner: mybase
      postInitApplicationSQL: 
      -  CREATE TABLE employes (id serial, nom text, paye numeric(8,2)) ;

Le Cluster peut-être créé avec la commande kubectl create ou kubectl apply. On vous laisse créer cette ressource et vérifier que la table existe bien 😏.

Pour des raisons d’organisation et de lisibilité il est pertinent de séparer correctement chaque action SQL. Pour une dizaine d’ordres SQL, cette solution est tout à fait viable, mais au-delà, cela deviendrait très compliqué à utiliser et à maintenir.

Une autre solution est proposée par l’opérateur avec l’utilisation de ressources ConfigMap ou Secret.

Exemple via ConfigMap

Pour le test, nous n’utiliserons que des ConfigMap. Nous vous laissons le loisir de tester les étapes qui suivent avec un objet Secret.

L’un des intérêts d’un ConfigMap est de regrouper des requêtes SQL et de les appeler au besoin lors des créations de Cluster. Ce sont des ressources qui peuvent être réutilisées.

Un autre avantage est de pouvoir mettre un grand nombre d’ordres SQL sans que cela ne rende la définition du Cluster illisible. Voici les quelques étapes à suivre pour mettre en place cette méthode.

Tout d’abord, écrire la définition du ConfigMap. La section data contient un champ sql qui, vous le verrez, sera utilisé plus tard lors de la création du Cluster. Le mot sql a été choisi pour qu’il corresponde sémantiquement au contenu, mais vous pouvez l’appeler comme vous voulez.

$ cat cm.yaml

apiVersion: v1 
kind: ConfigMap
metadata: 
  name: my-configmap
data:
  sql: | 
    CREATE TABLE liste (i integer) ; 
    INSERT INTO liste SELECT i from generate_series(1,100) as i ;

Créer cette ressource dans le cluster Kubernetes.

kubectl apply -f cm.yaml

Penchons-nous maintenant sur la création de la ressource Cluster afin d’utiliser ce ConfigMap.

Ce qui nous intéresse le plus est la partie postInitApplicationSQLRefs. Vous l’aurez remarqué, il s’agit ici d’une liste de références à des objets, un ou des ConfigMap donc, et non plus une liste d’ordres SQL. Le nom de la section change également, avec l’ajout du suffixe Refs. Vous l’aurez compris il existe là aussi trois nouveaux paramètres correspondant aux trois sections précédentes

  • postInitSQLRefs ;
  • postInitTemplateSQLRefs ;
  • postInitApplicationSQLRefs.

Reprenons l’exemple de notre Cluster :

...
  bootstrap:
    initdb:
      database: mybase
      owner: mybase
      postInitApplicationSQLRefs:
        configMapRefs:
        - name: my-configmap
          key: sql

Il est nécessaire de cibler le ConfigMap avec l’attribut name et la clé présente dans celui-ci, en l’occurrence : sql.

Créons ce Cluster et vérifions que la table et les données sont bien présentes.

kubectl cnpg psql cluster-cm -- -d mybase -c "select count(*) from liste"
 count 
-------
   100
(1 row)

Bien évidement, dans votre cas, le contenu de la ConfigMap sera bien plus conséquent.

Et si une erreur survient ?

L’erreur est humaine. Un administrateur aurait tendance à dire que PEBKAC … bref, regardons ce qu’il se passe si une erreur survient lors de l’exécution des ordres SQL.

Modifions une requête SQL (suppression de INTO) de notre ConfigMap et appliquons cette modification. Vous voyez au passage, qu’il n’y a pas de vérification du contenu d’un ConfigMap.

apiVersion: v1 
kind: ConfigMap
metadata: 
  name: my-configmap
data:
  sql: | 
    CREATE TABLE configmaps (i integer) ; 
    INSERT configmaps SELECT i from generate_series(1,100) as i ; 

Pour le test, il faut détruire et recréer le Cluster existant. Et oui, rappelez-vous que les opérations ne sont exécutées que lors du bootstrap, c’est à dire lors de la création du Cluster.

Une fois que ceci est fait, vous devriez voir, en lançant la commande kubectl get pod, un certain nombre de Pods avec le statut Error. L’opérateur essaye plusieurs fois de créer les Pods initdb.

kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
cluster-cm-1-initdb-k8qrx   0/1     Error     0          11m
cluster-cm-1-initdb-ksb9l   0/1     Error     0          13m
cluster-cm-1-initdb-p4t4p   0/1     Error     0          8m22s
cluster-cm-1-initdb-sdkmn   0/1     Error     0          13m
cluster-cm-1-initdb-tb87p   0/1     Error     0          2m58s
cluster-cm-1-initdb-wvn7h   0/1     Error     0          12m
cluster-cm-1-initdb-zqvd2   0/1     Error     0          13m
cluster-sql-1               1/1     Running   0          33m
cluster-sql-2               1/1     Running   0          32m

Il y a vraisemblablement une erreur. Regardons maintenant dans les traces du Pod, quelle en est la cause. Il est possible de cibler un conteneur spécifique avec l’option -c de la commande kubectl logs. Utilisons là pour cibler le conteneur initdb.

kubectl logs cluster-cm-1-initdb-zqvd2 -c initdb | jq

...

{
  "level": "info",
  "ts": "2026-03-09T09:30:09.800909636Z",
  "logger": "pg_ctl",
  "msg": "server stopped",
  "pipe": "stdout",
  "logging_pod": "cluster-cm-1-initdb"
}

L’instance PostgreSQL est arrêtée par l’opérateur. Pourquoi ?

{
  "level": "error",
  "ts": "2026-03-09T09:30:09.801691453Z",
  "msg": "Error while bootstrapping data directory",
  "logging_pod": "cluster-cm-1-initdb",
  "error": "while configuring new instance: could not execute post init application SQL refs: could not execute queries: ERROR: syntax error at or near \"liste\" (SQLSTATE 42601)",
  "stacktrace": "github.com/cloudnative-pg/machinery/...
  ...
}

L’ordre SQL renvoyant une erreur, l’opérateur le capte et arrête le processus initdb du Pod. Le cluster reste dans l’état Setting up Primary :

kubectl get cluster
NAME          AGE   INSTANCES   READY   STATUS                     PRIMARY
cluster-cm    15m   1                   Setting up primary     

Si le ConfigMap est corrigé assez rapidement, la boucle de réconciliation de CloudNativePG entrant en jeu, lors de la nouvelle tentative de création d’un Pod initdb, la ConfigMap est relue et la création du Cluster se fait sans encombre.

kubectl get pod

NAME                        READY   STATUS      RESTARTS   AGE
cluster-cm-1                1/1     Running     0          27s
cluster-cm-1-initdb-5z9d5   0/1     Error       0          58s
cluster-cm-1-initdb-6kb9n   0/1     Error       0          77s
cluster-cm-1-initdb-cxmkh   0/1     Completed   0          34s

Un Podd’initdb finit par être Completed et le Pod de l’instance cluster-cm-1 est créé.

Conclusion

Nous venons de voir comment demander à l’opérateur d’exécuter un certain nombre d’ordres SQL pour nous.

Vous avez deux possibilités, soit en indiquant des ordres SQL directement, soit en faisant référence à une ressource ConfigMap ou Secret. Cette dernière est d’autant plus pratique qu’elle vous permet de réutiliser des ConfigMap pour la création de vos Clusters.

Grâce à cette fonctionnalité de l’opérateur, vous n’avez plus besoin d’utiliser un autre outil pour déclencher la création de schémas, de tables ou d’autres ressources dans vos bases de données.

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.