Lyon, le 19 mai 2025

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

Si vous envisagez d’utiliser CloudNativePG pour déployer certaines de vos instances PostgreSQL, vous aurez sûrement besoin de rapatrier certaines bases de données dans ces nouvelles instances. Différentes techniques existent. Regardons deux méthodes d’import que propose l’opérateur 📥.

moteur

spec.bootstrap.initdb.import

spec.bootstrap.initdb.import est LA section YAML qui nous intéresse dans la définition de notre Cluster. La section spec.bootstrap.initdb permet de définir comment notre instance va être créée. La partie importpermet de définir quelle(s) base(s) de donnée(s) doi(ven)t être importée(s). On parle bien ici d’imports logiques de bases qui existeraient déjà dans une autre instance PostgreSQL. Cette instance doit d’ailleurs être accessible par la nouvelle instance qui sera créée par CloudNativePG.

Deux méthodes d’import existent :

  1. la méthode microservice 📦;
  2. et la méthode monolith 🪨.

La première méthode ne vous permet d’importer les données que d’une seule base. Ses données seront importées dans la base par défaut de l’instance (donc app). Si vous souhaitez conserver le même nom de base, n’oubliez pas de modifier le paramètre spec.bootstrap.initdb.database de la définition du Cluster. Cette méthode d’import est intéressante si vous souhaitez diviser une instance multi-bases en plusieurs instances mono-base.

La seconde méthode vous permet d’importer autant de bases que vous le souhaitez ainsi que des rôles PostgreSQL qui seraient existant dans l’instance source. C’est très pratique si vous souhaitez faire une migration complète de votre instance. Petite astuce, vous pouvez utiliser le caractère wildcard * pour signifier que vous voulez importer toutes les bases ou tous les rôles.

Quelque soit la méthode utilisée, l’utilitaire pg_dump est utilisé pour récupérer les données de la base et créer un dump dans le volume associé à la nouvelle instance. Attention donc à l’espace de stockage lors du déclenchement de l’import 🖴.

Comme j’aime à le dire, des exemples valent souvent mieux que de longs discours. Prenons donc le cas d’un service Recherche et Développement qui aurait à sa disposition trois bases de données rd_a, rd_b et rd_c dans une même instance.

Import en mode microservice

Dans cet exemple, seule la base de rd_a va être importée car l’équipe considère que les autres ne seront pas utilisées. En mode microservices, une seule base peut être importée dans l’instance.

Construisons notre fichier YAML pas à pas. Tout d’abord, le Cluster se basera sur une image en version 17.0 et il n’y aura pas de réplication mise en place. Restons simple et ne touchons pas à la configuration de l’instance.

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

Il nous faut ensuite ajouter la section spec.bootstrap.initdb.import à cette définition de notre nouveau Cluster. On en profite au passage pour définir le nom de la base où seront importées les données (pour cet exemple, il n’est plus question d’utiliser de tiret bas présent dans l’ancien nom), le propriétaire de celle-ci et activer les sommes de contrôle (une bonne pratique 🪄!) sur l’instance.

[...]
  bootstrap:
    initdb:
      database: rda
      owner: monrole
      dataChecksums: true
      import:
        type: microservice
        databases:
          - rd_a
        source:
          externalCluster: old-rd-pg

La partie import contient les informations de la base à importer, notamment :

  • son nom, dans la liste databases;
  • et où la trouver avec le paramètre source.

Le paramètre source contient le nom d’un externalCluster qui fait référence à la définition de l’instance PostgreSQL externe décrite dans ce même YAML, dont voici le contenu :

[...]
  externalClusters:
    - name: old-rd-pg
      connectionParameters:
        host: 51.159.74.201
        user: dalibo
        dbname: postgres
        port: "5432"
      password:
        name: local-pg-secret
        key: password

On y retrouve très logiquement les informations de connexions (ip, port, utilisateur, …) et également le mot de passe associé au compte dalibo qui se trouve être le propriétaire de la base rd_a. Le mot de passe doit être renseigné dans un Secret. Cela permet à l’opérateur de le récupérer sans devoir le renseigner en clair.

Voici la définition du Secret :

apiVersion: v1
kind: Secret
data:
  password: ZGFsaWJvCg==
metadata:
  name: local-pg-secret
type: kubernetes.io/basic-auth

Le fichier YAML complet du Cluster est donc le suivant :

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: postgresql-rda
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:17.0
  instances: 1
  storage:
    size: 20Gi
  bootstrap:
    initdb:
      database: rda
      owner: monrole
      dataChecksums: true
      import:
        type: microservice
        databases:
          - rd_a
        source:
          externalCluster: old-rd-pg
  externalClusters:
    - name: old-rd-pg
      connectionParameters:
        host: 51.159.74.201
        user: dalibo
        dbname: postgres
        port: "5432"
      password:
        name: local-pg-secret
        key: password

Il ne reste plus qu’à appliquer ce fichier pour créer notre instance. Automatiquement, CloudNativePG va créer le dump dans un dossier du PGDATA puis, lorsqu’il sera créé, débutera la restauration avec pg_restore. Vérifiez qu’il ait suffisamment d’espace de stockage.

Import en mode monolith

La deuxième solution possible est l’import dit monolith qui permet d’importer une ou plusieurs base de données ainsi que un ou plusieurs rôles dans une même instance.

⚠️ Faites attention au point suivant qui peut avoir son importance ! ⚠️

Veillez à utiliser un rôle qui puisse accéder à tous ces objets. Par exemple, l’import des rôles se fait en récupérant les informations depuis la vue pg_catalog.pg_authid. Utilisez donc un rôle SUPERUSER qui a le droit d’y accéder… autrement :

ERROR: permission denied for table pg_authid (SQLSTATE 42501)

J’imagine déjà des cas où cela pourrait poser problème, notamment avec les offres PostgreSQL managées qui ne donnent généralement pas accès à des rôles SUPERUSER 😏. Des rôles prédéfinis comme pg_read_all_data pourraient vous aider dans ce cas là.

Ceci étant dit, regardons un exemple de YAML.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-monolith
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:17.0
  instances: 1
  storage:
    size: 20Gi
  bootstrap:
    initdb:
      owner: dalibo
      import:
        type: monolith
        databases:
          - rd_a
          - rd_c
        source:
          externalCluster: local-pg
  externalClusters:
    - name: local-pg
      connectionParameters:
        host: 51.159.74.201
        user: dalibo
        dbname: postgres
        port: "15834"
      password:
        name: local-pg-secret
        key: password

Peu d’éléments changent par rapport à l’import de type microservice. import.type est passé à monolith et une liste de bases est renseignée. Ici, seules les bases rda et rdc vont être récupérées, nos collègues estimant inutiles de récupérer rdb.

Versions différentes

Je ne vous ai pas tout dit ! La version de l’instance source était la version 16.8. Les instances créées avec CloudNativePG se basent quant à elles sur la version 17.0 de PostgreSQL. Il est donc tout à fait possible d’importer une base d’une ancienne version dans une instance en version plus récente aussi “facilement”. Cela ne devrait pas surprendre les DBA PostgreSQL 😏, qui savent bien que c’est une possibilité qu’offrent pg_dump/pg_restore. Ce n’est en rien une nouveauté apportée par CloudNativePG.

Conclusion

Nous venons de voir deux méthodes de CloudNativePG qui permettent d’importer des bases de données existantes lors de la création d’une nouvelle instance dans Kubernetes. Toute la configuration se fait dans la section bootstrap.initdb.import de l’objet Cluster.

Dans les deux cas (monolith ou microservice, la copie du dump se fera dans le Persistant Volume associé au nouveau Cluster, nécessitant donc de la place de stockage. Attention donc aux dumps volumineux. Si vous avez déjà des dumps présents, cette solution semble peu pertinente.

Cela étant dit, ces deux méthodes sont très simples d’utilisation et permettent de gérer un export / import de manière déclaratif pour initier une instance avec des données déjà existantes. Simple et efficace 🔥 !

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.