Lyon, le 24 avril 2025

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

Dans le premier article, nous avons eu un aperçu de ce que l’opérateur CloudNativePG permet de faire. Le déploiement des instances se déroulait sans encombre … mais que se passe-t-il vraiment lorsque la commande kubectl apply -f ~/cluster.yaml est utilisée ? C’est ce que nous allons voir avec ce nouvel article consacré à l’opérateur CloudNativePG.

moteur

La séquence de démarrage 🚀

L’objectif aujourd’hui est de comprendre les mécanismes qui entrent en jeu lors de la création d’un cluster PostgreSQL avec CloudNativePG. Je pars du principe qu’un cluster Kubernetes est en cours de fonctionnement et que l’opérateur est installé. Reprenons la définition du cluster du premier article de notre série :

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: postgresql
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:17.0
  instances: 1
  storage:
    size: 20Gi

Décortiquons ce qui se passe lorsque kubectl apply -f ~/cluster.yaml est utilisé. La commande kubectl get pod --watch est très utile pour comprendre cela.

k get pod --watch       
NAME                        READY   STATUS            RESTARTS   AGE
postgresql-1-initdb-n4c5n   0/1     PodInitializing   0          1s
postgresql-1-initdb-n4c5n   0/1     Completed         0          2s
postgresql-1                0/1     Pending           0          0s
postgresql-1                0/1     Init:0/1          0          0s
postgresql-1                0/1     PodInitializing   0          1s
postgresql-1                0/1     Running           0          10s
postgresql-1                1/1     Running           0          10s

Pod initdb

Un premier Pod est créé, ici postgresql-1-initdb-n4c5n. Le mot initdb doit normalement vous dire quelque chose (pssst, si jamais vous avez oublié, il s’agit d’un utilitaire qui permet de préparer l’instance en créant notamment les répertoires de l’arborescence du PGDATA, les tables du catalogue PostgreSQL et des bases de données comme template1). Lorsque celui-ci a terminé de s’exécuter, un autre Pod est lancé, qui lui correspondra à notre instance PostgreSQL.

Revenons sur le premier Pod. Il contient en réalité deux conteneurs, nommés bootstrap-controller 📦 et initdb 📦. Le conteneur bootstrap-controller est ce qu’on appelle un init-container. C’est un type de conteneur spécial qui est démarré avant le ou les conteneurs applicatifs (ici initdb).

Création du premier `Pod`

En regardant les détails de ce conteneur avec kubectl describe postgresql-1-initdb-n4c5n, on peut relever plusieurs éléments intéressants :

  1. Il se base sur la même image que l’opérateur ghcr.io/cloudnative-pg/cloudnative-pg:1.25.1 ;
  2. et il exécute la commande /manager bootstrap /controller/manager --log-level=info.
[...]
Init Containers:
  bootstrap-controller:
    Container ID:    docker://2f6f2d7fa4e64f739113805827235a629ffeea78d740265b44dcb0474e4a7784
    Image:           ghcr.io/cloudnative-pg/cloudnative-pg:1.25.1
[...]
    Command:
      /manager
      bootstrap
      /controller/manager
      --log-level=info
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Tue, 22 Apr 2025 17:31:42 +0200
      Finished:     Tue, 22 Apr 2025 17:31:43 +0200
[...]

La commande a pour but de mettre à disposition l’opérateur dans le second conteneur. Lorsqu’il a terminé de s’exécuter, le conteneur passe à l’état Terminated et la séquence de démarrage continue par le lancement du conteneur applicatif, ici initdb.

Suivant le même principe, ce conteneur est créé à partir d’une image, ici ghcr.io/cloudnative-pg/postgresql:17.0.

Containers:
  initdb:
    Container ID:    docker://af0bf7b2d64e7cc88a1bd0034a5f46a3644a41523c0d18eaa8719cf963a5acad
    Image:           ghcr.io/cloudnative-pg/postgresql:17.0

Là aussi, la commande à exécuter est spécifiée. En l’occurrence il s’agit de /controller/manager instance init ... qui va déclencher l’initialisation de l’instance PostgreSQL. Cette commande utilitaire manager qui est ni plus ni moins qu’une partie de l’opérateur CloudNativePG. Cet utilitaire est écrit en Go et on peut retrouver son code sur le projet Github.

Les arguments passés à cette commande sont ceux par défaut. Ils peuvent être surchargés dans le manifest du Cluster. Aussi, à la lecture de ceux-ci, on comprend qu’une base de données app et un rôle qui porte le même nom seront automatiquement créés.

    Command:
      /controller/manager
      instance
      init
      --initdb-flags
      --encoding=UTF8 --lc-collate=C --lc-ctype=C
      --app-db-name
      app
      --app-user
      app
      --log-level=info

On peut également voir certaines variables d’environnement positionnées dans la définition du conteneur et qui seront utilisées par cette commande.

    Environment:
      PGDATA:        /var/lib/postgresql/data/pgdata
      POD_NAME:      postgresql-1-initdb
      NAMESPACE:     default
      CLUSTER_NAME:  postgresql
      PSQL_HISTORY:  /controller/tmp/.psql_history
      PGPORT:        5432
      PGHOST:        /controller/run
      TMPDIR:        /controller/tmp

Lorsque cette commande aura fini de s’exécuter, le Persistant Volume sera prêt à être utilisé par le second Pod.

Pod PostgreSQL

Quand les premiers conteneurs au sein du Pod initdb auront terminé leurs tâches, le controller CloudNativePG va déployer un second et dernier Pod qui contiendra notre instance PostgreSQL.

Création du second `Pod`

Le même principe d’init-container est utilisé avec le conteneur boostrap-controller qui permet d’utiliser l’opérateur CloudNativePG dans le Pod PostgreSQL. Ce Pod réutilise le Persistant Volume créé précédemment.

Cette fois-ci, le second conteneur est nommé postgres. Il se base lui aussi sur l’image ghcr.io/cloudnative-pg/postgresql:17.0 et exécute la commande suivante :

Command:
      /controller/manager
      instance
      run
      --status-port-tls
      --log-level=info

L’instance PostgreSQL est démarrée par l’intermédiaire de l’outil /controller/manager présent dans le conteneur. Notre éléphant 🐘 est embarqué sur notre porte-conteneurs ⛴️!

Images utilisées

Les init-containers ou les conteneurs applicatifs (ici initdb et postgres) se basent sur des images spécifiques. Ces images doivent se trouver sur le nœud Kubernetes qui va héberger l’instance PostgreSQL. Si elles ne se trouvent pas localement sur la machine, elles devront être téléchargées. Par défaut, elles le seront depuis la registry Github associée au projet CloudNativePG. C’est ce que l’on peut voir dans le nom des images avec la partie ghcr.io/cloudnative-pg.

Il faut donc s’assurer que votre cluster Kubernetes ait accès à internet pour pouvoir les récupérer… si ce n’est pas le cas, vous devrez mettre en place une registry interne comme Harbor, Zot, ou Distribution… mais cela ne nous regarde pas. On laissera ce choix être fait par l’équipe DevOps ou Système 😇.

Conclusion

Nous venons de découvrir ce qu’il se passe lorsqu’un Cluster PostgreSQL est créé. Le concept Kubernetes d’Init Container est utilisé par l’opérateur CloudNativePG lors de la création des différents Pods. La séquence de démarrage se fait en deux étapes : d’abord la préparation de l’instance et du volume par un Pod qui utilise initdb puis le déploiement du Pod PostgreSQL sur ce même volume.

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.