Pour un de nos clients, nous avons eu l'occasion de mettre en place Barman, qui se connecte à un PostgreSQL 9.3 Standby.

Cet article présente les différents problèmes que nous avons rencontrés et comment nous les avons réglés.

Barman

Tout d'abord, présentons Barman.

Il s'agit d'un outil utilisé pour la gestion des sauvegardes et l'archivage des journaux transactionnels de bases de données PostgreSQL.

Il permet aussi de réaliser des restaurations de bases de données à partir de ces sauvegardes, et notamment à un point défini dans le temps (Point in Time Recovery, ou PITR).

Son objectif est de fournir une solution permettant d'avoir une Perte de Données Maximale Admissible (PDMA, en Anglais RPO pour Recovery Point Objective) la plus proche de 0 possible.

Il fonctionne en deux phases.

La première consiste à récupérer et archiver les journaux transactionnels (Write Ahead Logs ou WAL) en continu.

La seconde consiste à réaliser une sauvegarde complète à une fréquence régulière (généralement programmée dans un cron).

Les dernières versions de Barman permettent d'utiliser le protocole de réplication pour ces deux phases, ce qui est beaucoup moins intrusif qu'avant où nous devions configurer PostgreSQL pour envoyer lui-même les WAL par le mécanisme d'archivage (archive_mode = on et archive_command), en passant par SSH pour les transférer, par exemple.

Cela permet aussi de mettre en place une sauvegarde complètement en mode "pull", où c'est le serveur de sauvegarde qui "tire" les sauvegardes vers lui, ce qui évite de créer un accès direct du serveur PostgreSQL vers ses sauvegardes, et réduit la surface lors d'une attaque mal intentionnée depuis ce serveur (destruction des bases et de leurs sauvegardes).

Contraintes avec PostgreSQL 9.3

Comme nous venons de le voir, Barman est capable d'utiliser le protocole de réplication pour effectuer les sauvegardes et la récupération des journaux transactionnels.

Seulement voilà, il utilise ce que l'on appelle les slots de réplication, c'est à dire un emplacement réservé sur le serveur PostgreSQL qui permet d'avoir une gestion plus sécurisée de la récupération des journaux. En effet lorsqu'un slot de réplication est créé, PostgreSQL sait que si il n'est pas utilisé il doit garder les WAL en vue d'une reconnexion ultérieure. Et ce mécanisme n'est disponible qu'à partir de PostgreSQL 9.4.

Autre point, nous pourrions mettre en place une copie des journaux depuis PostgreSQL "à l'ancienne", mais d'une part, nous voulons être le moins intrusif possible au niveau des serveurs PostgreSQL, et d'autre part, ça impliquerait la mise en place d'un accès direct sur le serveur de sauvegardes depuis le serveur PostgreSQL que nous souhaitons évier pour la raison exprimée plus haut. Et de toutes façons ce n'est pas possible de le faire depuis un standby avant la version 9.5.

Architecture

Tous nos serveurs tournent sur une distribution Debian Stretch, toutes les commandes et les configurations sont donc spécifiques à cette distribution.

Nous avons déjà en place une réplication entre :

  • un serveur primaire pg1 dont l'adresse est 192.168.0.41
  • un serveur standby pg2 dont l'adresse est 192.168.0.42

Le serveur de sauvegardes sera appelé bak1 et aura pour adresse IP 192.168.0.50.

Nous aurons donc deux choses à mettre en place sur bak1.

La première est la récupération des WAL en utilisant le protocole de réplication. Pour cela, nous utiliserons l'outil fourni par PostgreSQL : pg_receivexlog. Nous le ferons tourner dans un service et nous le superviserons pour éviter toute rupture dans l'archivage des WAL. Cela sera fait de façon à ce que Barman croie que c'est le serveur PostgreSQL qui lui envoie les journaux en mode "archive".

La seconde est une sauvegarde complète avec Barman. Il s'agit d'une configuration classique, Barman utilisera alors pg_basebackup pour les réaliser. Il archivera aussi les journaux fournis par le service pg_receivexlog.

Préparation du réplicat PostgreSQL

Sur pg1, nous devrons créer deux utilisateurs PostgreSQL qui serviront à Barman.

Le premier sera utilisé pour les vérifications effectuées par Barman.

Le second est utilisé pour les sauvegardes complètes et la récupération des journaux utilisant le protocole de réplication.

Voici les commandes pour les réaliser :

CREATE USER barman WITH LOGIN, SUPERUSER;
CREATE USER streaming_barman WITH LOGIN, REPLICATION;

Nous prenons ici le parti de ne pas donner de mot de passe aux utilisateurs car les accès seront filtrés par le fichier pg_hba.conf. De plus, autoriser la connexion sans mot de passe, ou stocker ce mot de passe sur le serveur de sauvegardes revient strictement au même d'un point de vue sécurité.

La sauvegarde et la récupération des WAL se faisant en se connectant sur pg2, nous allons y autoriser nos utilisateurs depuis bak1 en rajoutant dans le fichier /etc/postgresql/9.3/main/pg_hba.conf les lignes suivantes :

host    all             barman           192.168.0.50/32        trust
host    replication     streaming_barman 192.168.0.50/32        trust

Il faut recharger le service PostgreSQL quand on modifie ce fichier :

sudo service postgresql@9.3-main reload

Dernier point, si ce n'est pas déjà fait, il faut configurer au moins pg2 pour faire croire à Barman qu'il est en mode archive, en activant les paramètres suivants dans /etc/postgresql/9.3/main/postgresql.conf :

archive_mode = on
archive_command = 'cd .'

Si l'un de ces paramètres est changé, il faut cette fois complètement redémarrer PostgreSQL :

sudo service postgresql@9.3-main restart

Installation des éléments nécessaires

L'installation de Barman se fait à partir des dépôt PGDG.

Une fois le dépôt mis en place comme dans l'article référencé, il suffit d'installer barman et la bonne version de PostgreSQL avec apt sur bak1 :

apt install barman postgresql-9.3

L'installation créera une configuration de base, et deux exemples de configurations de serveurs.

Il créera aussi la tâche planifiée nécessaire, nous en reparlerons plus tard.

Nous allons aussi préparer le répertoire de destination :

sudo install -o barman -g barman -d /srv/barman

Récupération des WAL

Dans un premier temps, nous allons mettre en place la récupération des WAL.

Cela se fera en mettant en place un service Systemd, qui sera garant de l'exécution permanente de la commande pg_receivexlog.

Préparation et test du script

Nous allons d'abord écrire un script /usr/local/bin/barman-receivexlog.sh qui récupèrera les journaux pour un serveur passé en paramètre :

#!/bin/sh

SERVER="$1"

BARMAN_HOME="/srv/barman"
WAL_TARGET_DIR=${BARMAN_HOME}/${SERVER}/receive-xlog

cd /
sudo -u barman mkdir -p ${WAL_TARGET_DIR}
exec /usr/lib/postgresql-9.3/bin/pg_receivexlog \
    -h ${SERVER} -U streaming_barman -w \
    -v -D ${WAL_TARGET_DIR}

Nous pouvons tester la récupération :

sudo -u barman /usr/local/bin/barman-receivexlog.sh pg2

Le script devrait produire une sortie qui au bout d'un moment ressemble à ça :

pg_receivexlog: starting log streaming at 60F/91000000 (timeline 1)
pg_receivexlog: finished segment at 60F/92000000 (timeline 1)
pg_receivexlog: finished segment at 60F/93000000 (timeline 1)

Une fois que nous avons une seconde ligne avec finished segment, nous pouvons arrêter le script (avec CTRL+C), et vérifier la présence de WAL :

# ls /srv/barman/pg2/receive-xlog
000000010000060F00000092
000000010000060F00000093.partial

Le test étant concluant, nous pouvons supprimer les fichiers pour passer à l'écriture du service proprement dit :

rm /srv/barman/pg2/receive-xlog/*

Si le test n'est pas concluant, il faut analyser les traces côté pg2 et bak1 pour déterminer ce qu'il se passe et corriger le problème.

Écriture et activation du service

Le service de récupération des journaux transactionnels sera dans le fichier /etc/systemd/system/barman-receivexlog@.service. Le @ est important car ça permettra de gérer plusieurs serveurs si c'est nécessaire.

Voici le contenu du fichier :

[Unit]
Description=Service de récupération des journaux transactionnels pour %i
Requires=network.target
After=network.target

[Service]
User=barman
ExecStart=/usr/local/bin/barman-receivexlog.sh %i
Restart=always

[Install]
WantedBy=multi-user.target

Une fois le service écrit, nous allons l'activer et le démarrer :

sudo systemctl daemon-reload
sudo systemctl enable barman-receivexlog@pg2.service
sudo systemctl start barman-receivexlog@pg2.service

Nous pouvons vérifier son état :

# sudo systemctl status barman-receivexlog@pg2.service
 barman-receivexlog@pg2.service - Service de récupération des journaux transactionnels pour pg2.
   Loaded: loaded (/etc/systemd/system/barman-receivexlog@.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2018-10-26 14:36:04 CEST; 2s ago
 Main PID: 12264 (pg_receivexlog)
    Tasks: 1 (limit: 4915)
   CGroup: /system.slice/system-barman\x2dreceivexlog.slice/barman-receivexlog@pg2.service
           └─12264 /usr/lib/postgresql/9.3/bin/pg_receivexlog -h pg2 -U streaming_barman -w -v -D /srv/barman/pg2/receive-xlog

Oct 26 14:36:04 bak1 systemd[1]: Started Service de récupération des journaux transactionnels pour pg2..
Oct 26 14:36:04 bak1 barman_receive-xlog.sh[12264]: pg_receivexlog: starting log streaming at 60F/CB000000 (timeline 1)

Configuration de Barman

Configuration globale

La configuration globale reste la même à peu de choses près. Nous changerons juste la destination des fichiers de barman :

barman_home = /srv/barman

Configuration du serveur

Voici le plus gros. Nous allons créer une configuration du serveur dans le fichier /etc/barman.d/pg2.conf et y mettre le contenu suivant :

; Barman, Backup and Recovery Manager for PostgreSQL
; http://www.pgbarman.org/ - http://www.2ndQuadrant.com/

[pg2]
; Human readable description
description =  "PostgreSQL Database on pg2."

; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; PostgreSQL connection string (mandatory)
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
conninfo = host=pg2 user=barman dbname=postgres

; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; PostgreSQL streaming connection string
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; To be used by pg_basebackup for backup and pg_receivexlog for WAL streaming
; NOTE: streaming_barman is a regular user with REPLICATION privilege
streaming_conninfo = host=pg2 user=streaming_barman

; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Backup settings (via pg_basebackup)
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
backup_method = postgres
;streaming_backup_name = barman_streaming_backup

; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Continuous WAL archiving (via 'archive_command')
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
archiver = on
;archiver_batch_size = 50

; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Custom configuration
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; PATH setting for this server
path_prefix = "/usr/lib/postgresql/9.3/bin"

; WAL compression
compression = pybzip2

; Retention policy
retention_policy = RECOVERY WINDOW OF 1 DAY

Il y a quelques éléments que l'on peut adapter suivant ses besoins :

  • compression : on peut l'enlever si on ne veut pas de compression ou mettre une autre valeur. La documentation se trouve ici
  • retention_policy : la politique de rétention, ici 1 jour, peut être ajustée tel que c'est décrit ici

Adaptation du cron

Le mécanisme que nous avons mis en place nécessite quelques ajustements. En effet les WAL sont stockés dans un endroit différent de celui où Barman les attend. C'est nécessaire car pg_receivexlog récupère les informations dans un fichier .partial, qui serait immédiatement archivé par Barman et nous voulons éviter ça.

Du coup, nous copions d'abord les WAL dans le répertoire attendu, puis nous lançons le cron Barman.

Pour cela, nous allons écrire un nouveau script /usr/local/bin/barman-cron.sh avec le contenu suivant :

#!/bin/sh

BARMAN_HOME=/srv/barman
BARMAN_SERVERS=$*

for BARMAN_SERVER in ${BARMAN_SERVERS}; do
    /usr/bin/rsync \
        -av --remove-source-files --exclude '*.partial' \
        ${BARMAN_HOME}/${BARMAN_SERVER}/receive-xlog/ \
        ${BARMAN_HOME}/${BARMAN_SERVER}/incoming/
done

/usr/bin/barman -q cron

Ce script est fait pour prendre en paramètre les serveurs pour lesquels les WAL sont récupérés avec un service receivexlog dans le cas où il y en aurait plusieurs.

Nous en profitons pour ajouter la planification des sauvegardes complètes, ici, ça sera toutes les 6 heures.

Au final, le fichier /etc/cron.d/barman doit être modifié pour gérer ces deux actions :

# /etc/cron.d/barman: crontab entries for the barman package
* * * * * barman /usr/local/bin/barman-cron.sh pg2

0 */6 * * * barman [ -x /usr/bin/barman ] && /usr/bin/barman -q backup pg2

À partir de ce moment, le serveur est configuré pour réaliser les sauvegardes, avec archivage des WAL.

Nettoyage

Barman s'occupe tout seul du nettoyage des anciennes sauvegardes et des journaux associés en fonction de la politique de rétention, donc rien à faire à ce niveau là.

Vérification

Pour voir la réalisation des sauvegardes, nous pouvons utiliser la commande list-backup :

# barman list-backup pg2
pg2 20181029T180001 - STARTED
pg2 20181029T120001 - Mon Oct 29 12:39:00 2018 - Size: 246.3 GiB - WAL Size: 1.7 GiB
pg2 20181029T060002 - Mon Oct 29 06:39:20 2018 - Size: 246.0 GiB - WAL Size: 1.0 GiB
pg2 20181029T000001 - Mon Oct 29 00:38:21 2018 - Size: 246.0 GiB - WAL Size: 229.4 MiB
pg2 20181028T180001 - Sun Oct 28 18:41:01 2018 - Size: 246.0 GiB - WAL Size: 1.7 GiB
pg2 20181028T120001 - Sun Oct 28 12:38:36 2018 - Size: 245.7 GiB - WAL Size: 1.7 GiB

Ici, nous constatons qu'une sauvegarde est en cours.

Pour vérifier l'état du serveur, avec la commande check :

# barman check pg2
Server pg2:
        PostgreSQL: OK
        is_superuser: OK
        PostgreSQL streaming: OK
        wal_level: OK
        directories: OK
        retention policy settings: OK
        backup maximum age: OK (interval provided: 1 day, latest backup age: 5 hours, 52 minutes, 25 seconds)
        compression settings: OK
        failed backups: OK (there are 0 failed backups)
        minimum redundancy requirements: OK (have 5 backups, expected at least 0)
        pg_basebackup: OK
        pg_basebackup compatible: OK
        pg_basebackup supports tablespaces mapping: OK (pg_basebackup 9.4 or higher is required for tablespaces support)
        archive_mode: OK
        archive_command: OK
        archiver errors: OK

Cette commande peut-être compatible Nagios :

barman check --nagios pg2

Il suffit alors de la mettre dans une configuration NRPE, par exemple, et de la configurer au niveau de notre hôte.

Restauration automatique de la dernière sauvegarde

Pour effectuer certains tests, notamment la restauration elle-même, il peut être intéressant de mettre en place une restauration locale automatique.

Pour cela nous avons écrit un script /usr/local/bin/barman-recovery.sh qui prépare le cluster PostgreSQL, lance la commande de restauration sur la dernière sauvegarde du serveur passé en paramètre, et fait le nécessaire pour lancer PostgreSQL :

#!/bin/sh

PG_VERSION=9.3
PG_CLUSTER_NAME=main

BARMAN_DESTINATION_DIRECTORY=/var/lib/postgresql/${PG_VERSION}/${PG_CLUSTER_NAME}

BARMAN_SERVER="$1"

[ -z "${BARMAN_SERVER}" ] && echo "Missing server name in parameter." >&2 && exit 1

set -e

# Stop PostgreSQL.
/usr/sbin/service postgresql@${PG_VERSION}-${PG_CLUSTER_NAME} stop

# Cleanup destination.
/bin/rm -Rf ${BARMAN_DESTINATION_DIRECTORY}

# Change owner.
/bin/chown barman:barman $(dirname ${BARMAN_DESTINATION_DIRECTORY})

# Recover
LAST_BACKUP=$(/usr/bin/barman list-backup --minimal ${BARMAN_SERVER} | /usr/bin/head -n 1)

[ -z "${LAST_BACKUP}" ] && echo "Could not find backup for ${BARMAN_SERVER}." >&2 && exit 1

/usr/bin/barman -q recover ${BARMAN_SERVER} ${LAST_BACKUP} ${BARMAN_DESTINATION_DIRECTORY}

# Change destination owner.
/bin/chown postgres:postgres -R $(dirname ${BARMAN_DESTINATION_DIRECTORY})

# Start PostgreSQL.
/usr/sbin/service postgresql@${PG_VERSION}-${PG_CLUSTER_NAME} start

exit 0

Il suffit ensuite d'appeler ce script dans un cron pour qu'il s'exécute 1h après chaque sauvegarde complète, par exemple /etc/cron.d/cron-barman-recovery :

MAILTO=""

0 1,7,13,19 * * * root /usr/local/bin/barman-recovery.sh pg2

Pour aller plus loin

Cet article ne rentre pas les détails de la restauration d'une base.

Si vous souhaitez poursuivre, je vous conseille de lire l'article de DBI Services, il date un peu mais est toujours d'actualité, ou encore celui d'Oxalide, un peu plus récent.

Enfin, pour la mise en place de la récupération des WAL en streaming directement par Barman, en Anglais cette fois-ci, je vous conseille aussi l'article de 2ndQuadrant, il est un peu long, et utilise une connexion SSH pour réaliser les sauvegardes complètes, mais il explique clairement comment mettre en place les slots de réplication pour Barman.

À propos de l'auteur



Alexis est le doyen de l'équipe. Ses expériences professionnelles variées en tant que développeur Java puis administrateur système lui permettent d'avoir une excellente compréhension des problématiques de développement et d'hébergement, notamment d'applications Web.

Chez Sysnove, son rôle consiste à mettre en place et administrer les infrastructures nécessaires à l'hébergement des services fournis aux clients. En tant que directeur technique, il est responsable de l'infrastructure et gère la recherche et développement.