Introduction

Bucardo est un système de réplication pour PostgreSQL, il permet d’avoir plusieurs serveurs source et serveurs cible. Il s’agit d’un service asynchrone et basé sur des triggers.

Selon Wikipedia, Bucardo est une sous espèce du Bouquetin ibérique, nommée en français “Bouquetin des Pyrénées”. Malheureusement, l’espèce s’est éteinte dans les années 2000 en augurant la fin du logiciel homonyme. En effet, le logiciel a reçu son dernier commit en janvier 2023. Cette nouvelle, quoique bien moins triste que la disparition d’une espèce animale, n’est pas sans conséquences.

Chez Worteks, nous utilisions Bucardo pour synchroniser des bases de données. Ces bases de données identiques permettent ensuite d’assurer un service de haute disponibilité : il suffit de basculer d’une base à l’autre lorsqu’un problème arrive.

Pour mettre en place cette haute disponibilité, nous utilisons aujourd’hui la réplication logique native bidirectionnelle de PostgreSQL qui est disponible depuis la version 16. Nous avons déjà présenté ce procédé dans un billet de blog, mais cela ne s’applique que dans le cas d’une nouvelle installation.

Lorsque que Bucardo a été précédemment installé, des manipulations supplémentaires sont requises, notamment si les modifications doivent être faites en production ce qui implique de réduire au maximum le temps d’indisponibilité du service.

Ce billet de blog vise à décrire la migration de Bucardo vers le module natif de réplication logique de PostgreSQL, l’objectif étant de garder une configuration multi-maîtres.

Architecture

Pour ce tutoriel, l’architecture de départ est composée de deux machines Debian 11 avec PostgreSQL installé depuis les paquets Debian :

  • Serveur Herc (19.139.184.12)
  • Serveur Bouquetin (19.139.184.13)

Bucardo est installé et actif.

La bascule entre les serveurs se fait via une adresse virtuelle, VIP (19.139.184.10). La bascule est gérée par le logiciel Keepalived.

Notre cas d’usage est la base de données du serveur WebSSO LemonLDAP::NG.

Architechture de départ

Le serveur principal est Herc. C’est lui qui détient les données à jour si Bucardo n’est plus fonctionnel.

Dans un premier temps, nous allons copier les données depuis Herc vers une nouvelle instance de PostgreSQL dans Bouquetin. Puis nous ferons la bascule du serveur Herc vers Bouquetin et enfin nous ferons l’installation de la nouvelle instance de PostgreSQL dans Herc et la synchronisation depuis Bouquetin. Finalement nous pourrons revenir vers Herc et mettre en place la synchronisation bidirectionnelle.

Allons voir plus en détail !

Tutoriel

Pré-requis

Avant de commencer les manipulations, nous faisons une sauvegarde de la base de données et de l’instance complète de PostgreSQL :

# Seulement la base de données lemonldap
su - postgres -c 'pg_dump lemonldap -Fc > lemonldap.out'
# Toute l'instance Postgresql
su - postgres -c 'pg_dumpall > full_instance.out'

La sauvegarde de l’instance ne contient pas seulement la base de données mais aussi les différents utilisateurs avec leurs droits.

Les sauvegardes sont créées dans le dossier $HOME de l’utilisateur postgres, /var/lib/postgresql dans Debian.

Serveur Herc : désactiver Bucardo

Pour éviter l’import des données et un configuration inutile, nous désactivons bucardo.

Nous commençons par enlever les tables du service bucardo :

# Lister les tables
bucardo list tables
# 7. Table: public.cassessions    DB: source_db  PK: id (varchar)
# 1. Table: public.lmconfig       DB: source_db  PK: cfgnum (integer)                        
# 2. Table: public.notifications  DB: source_db  PK: date|uid|ref (timestamp|varchar|varchar)
# 6. Table: public.oidcsessions   DB: source_db  PK: id (varchar)                            
# 4. Table: public.psessions      DB: source_db  PK: id (varchar)                            
# 5. Table: public.samlsessions   DB: source_db  PK: id (varchar)                            
# 3. Table: public.sessions       DB: source_db  PK: id (varchar)                            

# Les effacer de bucardo
bucardo remove table public.lmconfig public.notifications public.oidcsessions public.psessions public.samlsessions public.sessions public.cassessions

Nous faisons de même pour les bases de données :

bucardo list databases
# Database: source_db  Status: active  Conn: psql -U bucardo -d lemonldap -h 127.0.0.1
# Database: target_db  Status: active  Conn: psql -U bucardo -d lemonldap -h 19.139.184.13

# Enlever les bases de données
bucardo remove database source_db target_db
# Arrêter bucardo
bucardo stop

Ensuite, dans l’instance PostgreSQL, nous effaçons la base de données bucardo, les schémas présents dans la base de donnée lemonldap et l’utilisateur bucardo :

su - postgres -c psql
# Drop la base de donnée bucardo
DROP DATABASE bucardo;
# Ainsi que son schéma
\c lemonldap
DROP SCHEMA bucardo CASCADE;
# Effacer l'utilisateur
DROP USER bucardo;

Finalement, il est possible d’arrêter le service bucardo :

systemctl stop bucardo

À ce moment là, il est possible de désinstaller bucardo du serveur. Toutefois, nous le gardons au cas où un rollback serait nécessaire.

Serveur Herc : export des données via réplication logique

Nous démarrons la préparation de la migration de cette instance, en version 13 :

su - postgres -c psql
# Créer l'utilisateur pgreplication et lui donner les bons droits
CREATE ROLE pgreplication LOGIN REPLICATION PASSWORD 'secret_pgreplication';
# Tous les privilèges pour tous
GRANT ALL PRIVILEGES ON DATABASE lemonldap TO pgreplication;
\c lemonldap
GRANT ALL ON ALL TABLES IN SCHEMA public TO pgreplication;

Nous éditons le fichier /etc/postgresql/13/main/pg_hba.conf pour permettre l’accès de la nouvelle instance :

vim /etc/postgresql/13/main/pg_hba.conf
# ----------------------------------- #
# [....]
# Pour la réplication et lemonldap
host    lemonldap       pgreplication   19.139.184.13/32        md5

Dans la configuration générale de PostgreSQL, inclure le fichier de configuration locale :

vim /etc/postgresql/13/main/postgresql.conf
# --------------------------------------- #
[...]
include = 'local_postgres.conf'

Créer le fichier de configuration locale :

vim /etc/postgresql/13/main/local_postgres.conf
# --------------------------------------- #
# Access au serveur
listen_addresses = '*'
# Logical replication
wal_level = logical
max_worker_processes = 10

Récupérer le schéma seulement de la base de données de l’instance actuelle :

su - postgres -c 'pg_dump --schema-only lemonldap > lemonldap_schema'

Ce fichier doit être exporté au serveur Bouquetin (par exemple via scp).

Finalement, nous créons la publication :

su - postgres -c psql
\c lemonldap
# Le premier se connecte au deuxième
CREATE PUBLICATION replication_lemonldap FOR ALL TABLES;

Réinitialiser le service :

systemctl restart postgresql@13-main.service
# Vérifier que tout est ok
systemctl status postgresql@13-main.service

Serveur Bouquetin : PostgreSQL 16, import des données

Transfert des données depuis Herc(bucardo) vers Bouquetin(postgreSQL)

Tester la connexion à Herc via psql :

psql -h 19.139.184.12 -p 5432 -U pgreplication -d lemonldap -W 
# secret_pgreplication

Nous procédons à la mise en place du dépôt PostgreSQL dans le serveur :

apt install postgresql-common curl ca-certificates
install -d /usr/share/postgresql-common/pgdg
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc

Ajouter le dépôt :

vim /etc/apt/sources.list.d/pgdg.list
# ------------------------------------ #
deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt bullseye-pgdg main

Mettre à jour des paquets et installation de PostgreSQL :

apt update
apt install postgresql-16

Éditer le fichier /etc/postgresql/16/main/pg_hba.conf pour la future connexion de l’autre instance :

vim /etc/postgresql/16/main/pg_hba.conf
# ----------------------------------- #
# [....]
# Pour la réplication et lemonldap
host    lemonldap       pgreplication   19.139.184.12/32        scram-sha-256

Dans le fichier de configuration générale, inclure le fichier de configuration locale :

vim /etc/postgresql/16/main/postgresql.conf
# --------------------------------------- #
# Changement de port pour avoir deux postgresql, ce changement est fait par défaut
port = 5433
[...]
include = 'local_postgres.conf'

Créer un fichier de configuration /etc/postgresql/16/main/local_postgres.conf :

vim /etc/postgresql/16/main/local_postgres.conf
# --------------------------------------- #
# Access au serveur
listen_addresses = '*'
# Logical replication
wal_level = logical
max_worker_processes = 10

Activer et démarrer le service :

systemctl restart postgresql@16-main.service

Initialiser l’utilisateur et la base de données LemonLDAP :

# Attention au port !
su - postgres -c 'psql -p 5433'
# A priori pas besoin, car on ne se connecte que depuis l'utilisateur postgres
ALTER USER postgres WITH ENCRYPTED PASSWORD 'secret_postgres';
# Création d'utilisateurs
CREATE USER lemonldap WITH ENCRYPTED PASSWORD 'secret_lemonldap';
CREATE DATABASE lemonldap OWNER=lemonldap;
GRANT ALL PRIVILEGES ON DATABASE lemonldap TO lemonldap;
GRANT ALL ON ALL TABLES IN SCHEMA public TO lemonldap;

Importer les schémas de la base de données maître :

su - postgres -c 'cat lemonldap_schema | psql -p 5433 lemonldap'
su - postgres -c 'psql -p 5433'
\c lemonldap
CREATE SUBSCRIPTION subscription_serveur_initial
    CONNECTION 'host=19.139.184.12 port=5432 user=pgreplication dbname=lemonldap password=secret_pgreplication'
    PUBLICATION replication_lemonldap;

Attendre quelques minutes pour faire la réplication - en réalité elle est quasi instantanée pour un nombre faible de données.

Pour s’assurer que l’application, le SSO dans notre cas, puisse voir la base de données, il faut utiliser le port habituellement réservé pour PostgreSQL. Il suffit d’effacer la ligne dans le fichier :

vim /etc/postgresql/16/main/postgres.conf
# ---------------------------------------- #
# Effacer la ligne suivante
port = 5433

Désactiver et arrêter le service PostgreSQL13 :

systemctl disable postgresql@13-main.service
systemctl stop postgresql@13-main.service

Activer et redémarrer le service PostgreSQL16 :

systemctl enable postgresql@16-main.service
systemctl start postgresql@16-main.service

Vérifier que le service écoute sur le bon port, si ce n’est pas le cas vérifier la configuration générale /etc/postgresql/16/main/postgresql.conf :

netstat -tulpn | grep LISTEN
# tcp        0      0 0.0.0.0:5432            0.0.0.0:*               LISTEN      1179337/postgres

Basculement de Herc vers Bouquetin

Finalement, dans Herc éteindre et désactiver Apache2 :

systemctl stop apache2.service
systemctl disable apache2.service

Vérifier la bascule de serveur :

journalctl -feu keepalived.service
# sept. 17 17:48:26 herc.agglonet.com Keepalived_vrrp[2344]: Script `check_server_health` now returning 1
# sept. 17 17:48:28 herc.agglonet.com Keepalived_vrrp[2344]: VRRP_Script(check_server_health) failed (exited with status 1)
# sept. 17 17:48:28 herc.agglonet.com Keepalived_vrrp[2344]: (ldap) Entering FAULT STATE
# sept. 17 17:48:28 herc.agglonet.com Keepalived_vrrp[2344]: (ldap) sent 0 priority

Serveur Bouquetin : PostgreSQL 16, export des données

Il faut préparer Bouquetin pour la migration de données vers la future instance PostgreSQL dans Herc :

su - postgres -c psql 
# Créer l'utilisateur pgreplication et lui donner les bons droits
CREATE ROLE pgreplication LOGIN REPLICATION PASSWORD 'secret_replication';
# Toutes les privilèges pour tous
GRANT ALL PRIVILEGES ON DATABASE lemonldap TO pgreplication;

Ensuite dans la base de données lemonldap :

\c lemonldap
# Drop l'abonnement à l'ancien serveur
GRANT ALL ON ALL TABLES IN SCHEMA public TO pgreplication; 
# Maintenant que la bascule est faite, nous pouvons effacer l'abonnement à Herc
DROP SUBSCRIPTION subscription_serveur_initial ;
# Créer la subscription pour la suite
CREATE PUBLICATION replication_lemonldap FOR ALL TABLES;

Serveur Herc : PostgreSQL 16, import des données

Transfert des données depuis Bouquetin(postgreSQL) vers Herc(postgreSQL)

Tester la connexion a Bouquetin via psql :

psql -h 19.139.184.13 -p 5432 -U pgreplication -d lemonldap -W 
# secret_pgreplication

Installer les pré-requis et la clé du dépôt PostgreSQL :

apt install postgresql-common curl ca-certificates
install -d /usr/share/postgresql-common/pgdg
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc

Ajouter le dépôt :

vim /etc/apt/sources.list.d/pgdg.list
# ------------------------------------ #
deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt bullseye-pgdg main

Mettre à jour les paquets et l’installation de PostgreSQL16 :

apt update
apt install postgresql-16

Éditer le fichier de connexion de PostgreSQL :

vim /etc/postgresql/16/main/pg_hba.conf
# ----------------------------------- #
# [....]
# Pour la réplication et lemonldap
host    lemonldap       pgreplication   19.139.184.12/32        scram-sha-256

Dans le fichier de configuration générale, inclure le fichier de configuration locale :

vim /etc/postgresql/16/main/postgresql.conf
# --------------------------------------- #
# Effacer la ligne suivante
port = 5433
[...]
include = 'local_postgres.conf'

Créer le fichier de configuration locale :

vim /etc/postgresql/16/main/local_postgres.conf
# --------------------------------------- #
# Accès au serveur
listen_addresses = '*'
# Réplication logique
wal_level = logical
max_worker_processes = 10

Désactiver et arrêter le service PostgreSQL13 :

systemctl disable postgresql@13-main.service
systemctl stop postgresql@13-main.service

Activer et redémarrer le service PostgreSQL16 :

systemctl enable postgresql@16-main.service
systemctl restart postgresql@16-main.service

Vérifier que le service écoute sur le bon port, si ce n’est pas le cas vérifier la configuration générale /etc/postgresql/16/main/postgresql.conf :

netstat -tulpn | grep LISTEN
# tcp        0      0 0.0.0.0:5432            0.0.0.0:*               LISTEN      1179337/postgres

Initialiser l’utilisateur et la base de données LemonLDAP :

su - postgres -c psql 
# A priori pas besoin, car on ne se connecte que depuis l'utilisateur postgres
ALTER USER postgres WITH ENCRYPTED PASSWORD 'secret_postgres';
# Création d'utilisateurs
CREATE USER lemonldap WITH ENCRYPTED PASSWORD 'secret_lemonldap';
CREATE DATABASE lemonldap OWNER=lemonldap;
GRANT ALL PRIVILEGES ON DATABASE lemonldap TO lemonldap;
\c lemonldap
GRANT ALL ON ALL TABLES IN SCHEMA public TO lemonldap;

Importer les schémas de la base de donnée maître :

su - postgres -c 'cat lemonldap_schema | psql lemonldap'
su - postgres -c psql 
\c lemonldap
CREATE SUBSCRIPTION subscription_lemonldap_replication
    CONNECTION 'host=19.139.184.13 port=5432 user=pgreplication dbname=lemonldap password=secret_pgreplication'
    PUBLICATION replication_lemonldap
    WITH (origin=none);

Serveur Herc : PostgreSQL 16 export des données

Il faut préparer Bouquetin pour la migration de données vers la future instance PostgreSQL16 :

su - postgres -c psql 
# Créer l'utilisateur pgreplication et lui donner les bons droits
CREATE ROLE pgreplication LOGIN REPLICATION PASSWORD 'secret_pgreplication';
# Toutes les privilèges pour tous
GRANT ALL PRIVILEGES ON DATABASE lemonldap TO pgreplication;
\c lemonldap
GRANT ALL ON ALL TABLES IN SCHEMA public TO pgreplication; 
# Créer la publication pour la suite
CREATE PUBLICATION replication_lemonldap FOR ALL TABLES;

Serveur Bouquetin : abonnement pour la replication bidirectionnelle

Depuis le serveur Bouquetin, s’inscrire à Herc pour permettre la synchronisation bidirectionnelle :

su - postgres -c psql 
\c lemonldap
CREATE SUBSCRIPTION subscription_lemonldap_replication
    CONNECTION 'host=19.139.184.12 port=5432 user=pgreplication dbname=lemonldap password=secret_pgreplication'
    PUBLICATION replication_lemonldap
    WITH (copy_data=false, origin=none);

Basculement de Bouquetin vers Herc

Architecture finale

Finalement, dans Herc démarrer et réactiver Apache2 :

systemctl start apache2.service
systemctl enable apache2.service

Vérifier la bascule de serveur :

journalctl -feu keepalived.service

Conclusion

L’environnement PostgreSQL est très complet et en constante évolution. Il est souvent possible d’accomplir une tâche de plusieurs manières.

La réplication logique permet de basculer d’une installation PostgreSQL vers une autre en réduisant le temps d’indisponibilité, à ce sujet je vous conseille la conférence de Cédric Duprez qui m’a fortement inspirée.

Il faut faire attention à l’utilisation de la réplication logique. Les différents rôles, connexions et sens de réplication peuvent vite devenir complexes et il devient difficile de se retrouver. Bien évidemment, plus vous aurez des services à répliquer plus la manipulation sera délicate. Toutefois, la méthode permet de prendre le temps de réfléchir même pendant son application, ce que je trouve très agréable.

Sources