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.

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 sectionpostInit; - pour la base
template1: la sectionpostInitTemplate; - pour la base
app: la sectionpostInitApplication.
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 !