WAL-G : sauvegardes et restauration du SGBD PostgreSQL

On sait depuis longtemps que faire des sauvegardes dans des dumps SQL (en utilisant pg_dump ou pg_dumpall) n'est pas une bonne idée. Pour sauvegarder le SGBD PostgreSQL, il est préférable d'utiliser la commande pg_basebackup, qui fait une copie binaire des journaux WAL. Mais lorsque vous commencerez à étudier l'ensemble du processus de création d'une copie et de restauration, vous comprendrez que vous devez écrire au moins quelques tricycles pour que cela fonctionne et ne vous cause pas de douleur au-dessus et au-dessous. Pour soulager les souffrances, WAL-G a été développé.

WA-G est un outil écrit en Go pour sauvegarder et restaurer les bases de données PostgreSQL (et plus récemment MySQL/MariaDB, MongoDB et FoundationDB). Il prend en charge le travail avec le stockage Amazon S3 (et ses analogues, par exemple Yandex Object Storage), ainsi qu'avec Google Cloud Storage, Azure Storage, Swift Object Storage et simplement avec le système de fichiers. L'ensemble de la configuration se résume à des étapes simples, mais étant donné que les articles à ce sujet sont dispersés sur Internet, il n'existe pas de manuel pratique complet qui inclurait toutes les étapes du début à la fin (il existe plusieurs articles sur Habré, mais de nombreux points y manquent).

WAL-G : sauvegardes et restauration du SGBD PostgreSQL

Cet article a été écrit principalement pour systématiser mes connaissances. Je ne suis pas DBA et je peux m'exprimer quelque part dans un langage profane, donc toute correction est la bienvenue !

Par ailleurs, je note que tout ce qui suit est pertinent et testé pour PostgreSQL 12.3 sur Ubuntu 18.04, toutes les commandes doivent être exécutées en tant qu'utilisateur privilégié.

Installation

Au moment de la rédaction de cet article, la version stable de WAL-G est v0.2.15 (mars 2020). C'est ce que nous utiliserons (mais si vous souhaitez le construire vous-même à partir de la branche master, alors le référentiel github contient toutes les instructions pour cela). Pour télécharger et installer, vous devez faire :

#!/bin/bash

curl -L "https://github.com/wal-g/wal-g/releases/download/v0.2.15/wal-g.linux-amd64.tar.gz" -o "wal-g.linux-amd64.tar.gz"
tar -xzf wal-g.linux-amd64.tar.gz
mv wal-g /usr/local/bin/

Après cela, vous devez d'abord configurer WAL-G, puis PostgreSQL lui-même.

Configuration de WAL-G

Pour un exemple de stockage de sauvegardes, Amazon S3 sera utilisé (car il est plus proche de mes serveurs et son utilisation est très bon marché). Pour travailler avec, vous avez besoin d'un « bucket s3 » et de clés d'accès.

Tous les articles précédents sur WAL-G utilisaient une configuration utilisant des variables d'environnement, mais avec cette version, les paramètres peuvent être situés dans Fichier .walg.json dans le répertoire personnel de l'utilisateur postgres. Pour le créer, exécutez le script bash suivant :

#!/bin/bash

cat > /var/lib/postgresql/.walg.json << EOF
{
    "WALG_S3_PREFIX": "s3://your_bucket/path",
    "AWS_ACCESS_KEY_ID": "key_id",
    "AWS_SECRET_ACCESS_KEY": "secret_key",
    "WALG_COMPRESSION_METHOD": "brotli",
    "WALG_DELTA_MAX_STEPS": "5",
    "PGDATA": "/var/lib/postgresql/12/main",
    "PGHOST": "/var/run/postgresql/.s.PGSQL.5432"
}
EOF
# обязательно меняем владельца файла:
chown postgres: /var/lib/postgresql/.walg.json

Laissez-moi vous expliquer un peu tous les paramètres :

  • WALG_S3_PREFIX – le chemin d'accès à votre bucket S3 où les sauvegardes seront téléchargées (vous pouvez soit à la racine, soit dans un dossier) ;
  • AWS_ACCESS_KEY_ID – clé d'accès dans S3 (en cas de récupération sur un serveur de test, ces clés doivent avoir une politique ReadOnly ! Ceci est décrit plus en détail dans la section sur la récupération.);
  • AWS_SECRET_ACCESS_KEY – clé secrète dans le stockage S3 ;
  • WALG_COMPRESSION_METHOD – méthode de compression, il est préférable d'utiliser Brotli (puisque c'est le juste milieu entre la taille finale et la vitesse de compression/décompression) ;
  • WALG_DELTA_MAX_STEPS – le nombre de « deltas » avant de créer une sauvegarde complète (ils permettent de gagner du temps et de réduire la taille des données téléchargées, mais peuvent légèrement ralentir le processus de récupération, il est donc déconseillé d'utiliser des valeurs élevées) ;
  • PGDONNEES – chemin d'accès au répertoire contenant les données de votre base de données (vous pouvez le découvrir en exécutant la commande pg_lsclusters);
  • PGHOST – la connexion à la base de données, avec une sauvegarde locale il vaut mieux le faire via un socket unix comme dans cet exemple.

D'autres paramètres peuvent être trouvés dans la documentation : https://github.com/wal-g/wal-g/blob/v0.2.15/PostgreSQL.md#configuration.

Configuration de PostgreSQL

Pour que l'archiveur à l'intérieur de la base de données télécharge les journaux WAL sur le cloud et les restaure à partir d'eux (si nécessaire), vous devez définir plusieurs paramètres dans le fichier de configuration. /etc/postgresql/12/main/postgresql.conf. Juste pour commencer tu dois t'assurerqu'aucun des paramètres ci-dessous n'est défini sur d'autres valeurs, de sorte que lorsque la configuration est rechargée, le SGBD ne plante pas. Vous pouvez ajouter ces paramètres en utilisant :

#!/bin/bash

echo "wal_level=replica" >> /etc/postgresql/12/main/postgresql.conf
echo "archive_mode=on" >> /etc/postgresql/12/main/postgresql.conf
echo "archive_command='/usr/local/bin/wal-g wal-push "%p" >> /var/log/postgresql/archive_command.log 2>&1' " >> /etc/postgresql/12/main/postgresql.conf
echo “archive_timeout=60” >> /etc/postgresql/12/main/postgresql.conf
echo "restore_command='/usr/local/bin/wal-g wal-fetch "%f" "%p" >> /var/log/postgresql/restore_command.log 2>&1' " >> /etc/postgresql/12/main/postgresql.conf

# перезагружаем конфиг через отправку SIGHUP сигнала всем процессам БД
killall -s HUP postgres

Description des paramètres à régler :

  • niveau_wal – combien d'informations écrire dans les journaux WAL, « réplique » – tout écrire ;
  • mode_archive – activer le téléchargement des journaux WAL à l'aide de la commande du paramètre archive_commande;
  • archive_commande – commande pour archiver un journal WAL terminé ;
  • archive_timeout – l'archivage des journaux n'est effectué que lorsqu'il est terminé, mais si votre serveur modifie/ajoute peu de données à la base de données, alors il est logique de définir ici une limite en secondes, après quoi la commande d'archivage sera appelée de force (J'écris intensivement dans la base de données chaque seconde, j'ai donc décidé de ne pas définir ce paramètre en production);
  • restaurer_commande – la commande pour restaurer le journal WAL à partir d'une sauvegarde sera utilisée si la « sauvegarde complète » (sauvegarde de base) ne contient pas les dernières modifications de la base de données.

Vous pouvez en savoir plus sur tous ces paramètres dans la traduction de la documentation officielle : https://postgrespro.ru/docs/postgresql/12/runtime-config-wal.

Mise en place d'un planning de sauvegarde

Quoi qu'on en dise, le moyen le plus pratique de l'exécuter est cron. C'est ce que nous allons configurer pour créer des sauvegardes. Commençons par la commande pour créer une sauvegarde complète : dans wal-g c'est l'argument de lancement sauvegarde-push. Mais d’abord, il est préférable d’exécuter cette commande manuellement depuis l’utilisateur postgres pour s’assurer que tout va bien (et qu’il n’y a pas d’erreurs d’accès) :

#!/bin/bash

su - postgres -c '/usr/local/bin/wal-g backup-push /var/lib/postgresql/12/main'

Les arguments de lancement indiquent le chemin d'accès au répertoire de données - je vous rappelle que vous pouvez le découvrir en exécutant pg_lsclusters.

Si tout s'est déroulé sans erreur et que les données ont été chargées dans le stockage S3, vous pouvez alors configurer un lancement périodique dans crontab :

#!/bin/bash

echo "15 4 * * *    /usr/local/bin/wal-g backup-push /var/lib/postgresql/12/main >> /var/log/postgresql/walg_backup.log 2>&1" >> /var/spool/cron/crontabs/postgres
# задаем владельца и выставляем правильные права файлу
chown postgres: /var/spool/cron/crontabs/postgres
chmod 600 /var/spool/cron/crontabs/postgres

Dans cet exemple, le processus de sauvegarde démarre chaque jour à 4h15.

Suppression des anciennes sauvegardes

Très probablement, vous n'avez pas besoin de conserver absolument toutes les sauvegardes de l'ère mésozoïque, il sera donc utile de « nettoyer » périodiquement votre stockage (à la fois les « sauvegardes complètes » et les journaux WAL). Nous ferons tout cela à travers une tâche cron :

#!/bin/bash

echo "30 6 * * *    /usr/local/bin/wal-g delete before FIND_FULL $(date -d '-10 days' '+%FT%TZ') --confirm >> /var/log/postgresql/walg_delete.log 2>&1" >> /var/spool/cron/crontabs/postgres
# ещё раз задаем владельца и выставляем правильные права файлу (хоть это обычно это и не нужно повторно делать)
chown postgres: /var/spool/cron/crontabs/postgres
chmod 600 /var/spool/cron/crontabs/postgres

Cron exécutera cette tâche tous les jours à 6h30, en supprimant tout (sauvegardes complètes, deltas et WAL) à l'exception des copies des 10 derniers jours, mais en laissant au moins une sauvegarde à date spécifiée afin qu'à tout moment après les dates ont été incluses dans le PITR.

Restauration à partir d'une sauvegarde

Ce n'est un secret pour personne que la clé d'une base de données saine est la restauration périodique et la vérification de l'intégrité des données qu'elle contient. Je vais vous expliquer comment récupérer à l'aide de WAL-G dans cette section, et nous parlerons des contrôles plus tard.

À noter séparément que pour restaurer dans un environnement de test (tout ce qui n'est pas en production), vous devez utiliser un compte en lecture seule dans S3 afin de ne pas écraser accidentellement les sauvegardes. Dans le cas de WAL-G, vous devez définir les droits suivants pour l'utilisateur S3 dans la stratégie de groupe (Effet : Autoriser): s3:ObtenirObjet, s3:ListeBucket, s3 : Obtenir l'emplacement du compartiment. Et bien sûr, n’oubliez pas de définir archive_mode = désactivé dans le fichier de paramètres postgresql.conf, afin que votre base de données de test ne veuille pas être sauvegardée en silence.

La restauration s'effectue d'un léger mouvement de la main supprimer toutes les données PostgreSQL (y compris les utilisateurs), soyez donc extrêmement prudent lorsque vous exécutez les commandes suivantes.

#!/bin/bash

# если есть балансировщик подключений (например, pgbouncer), то вначале отключаем его, чтобы он не нарыгал ошибок в лог
service pgbouncer stop
# если есть демон, который перезапускает упавшие процессы (например, monit), то останавливаем в нём процесс мониторинга базы (у меня это pgsql12)
monit stop pgsql12
# или останавливаем мониторинг полностью
service monit stop
# останавливаем саму базу данных
service postgresql stop
# удаляем все данные из текущей базы (!!!); лучше предварительно сделать их копию, если есть свободное место на диске
rm -rf /var/lib/postgresql/12/main
# скачиваем резервную копию и разархивируем её
su - postgres -c '/usr/local/bin/wal-g backup-fetch /var/lib/postgresql/12/main LATEST'
# помещаем рядом с базой специальный файл-сигнал для восстановления (см. https://postgrespro.ru/docs/postgresql/12/runtime-config-wal#RUNTIME-CONFIG-WAL-ARCHIVE-RECOVERY ), он обязательно должен быть создан от пользователя postgres
su - postgres -c 'touch /var/lib/postgresql/12/main/recovery.signal'
# запускаем базу данных, чтобы она инициировала процесс восстановления
service postgresql start

Pour ceux qui souhaitent vérifier le processus de récupération, un petit morceau de magie bash a été préparé ci-dessous, afin qu'en cas de problème de récupération, le script plante avec un code de sortie différent de zéro. Dans cet exemple, 120 vérifications sont effectuées avec un délai d'attente de 5 secondes (un total de 10 minutes pour la récupération) pour savoir si le fichier de signal a été supprimé (cela signifie que la récupération a réussi) :

#!/bin/bash

CHECK_RECOVERY_SIGNAL_ITER=0
while [ ${CHECK_RECOVERY_SIGNAL_ITER} -le 120 ]
do
    if [ ! -f "/var/lib/postgresql/12/main/recovery.signal" ]
    then
        echo "recovery.signal removed"
        break
    fi
    sleep 5
    ((CHECK_RECOVERY_SIGNAL_ITER+1))
done

# если после всех проверок файл всё равно существует, то падаем с ошибкой
if [ -f "/var/lib/postgresql/12/main/recovery.signal" ]
then
    echo "recovery.signal still exists!"
    exit 17
fi

Après une récupération réussie, n'oubliez pas de redémarrer tous les processus (pgbouncer/monit, etc.).

Vérification des données après la récupération

Il est impératif de vérifier l'intégrité de la base de données après la restauration, afin d'éviter qu'une situation de sauvegarde cassée/tordue ne se produise. Et il est préférable de le faire avec chaque archive créée, mais où et comment ne dépend que de votre imagination (vous pouvez augmenter les serveurs individuels toutes les heures ou effectuer un contrôle dans CI). Mais au minimum, il est nécessaire de vérifier les données et les index de la base de données.

Pour vérifier les données, il suffit de les exécuter via un dump, mais il est préférable que lors de la création de la base de données, les sommes de contrôle soient activées (sommes de contrôle des données):

#!/bin/bash

if ! su - postgres -c 'pg_dumpall > /dev/null'
then
    echo 'pg_dumpall failed'
    exit 125
fi

Pour vérifier les index - existe module amcheck, prenons la requête SQL correspondant à partir de Tests WAL-G et construisez un peu de logique autour de cela :

#!/bin/bash

# добавляем sql-запрос для проверки в файл во временной директории
cat > /tmp/amcheck.sql << EOF
CREATE EXTENSION IF NOT EXISTS amcheck;
SELECT bt_index_check(c.oid), c.relname, c.relpages
FROM pg_index i
JOIN pg_opclass op ON i.indclass[0] = op.oid
JOIN pg_am am ON op.opcmethod = am.oid
JOIN pg_class c ON i.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE am.amname = 'btree'
AND c.relpersistence != 't'
AND i.indisready AND i.indisvalid;
EOF
chown postgres: /tmp/amcheck.sql

# добавляем скрипт для запуска проверок всех доступных баз в кластере
# (обратите внимание что переменные и запуск команд – экранированы)
cat > /tmp/run_amcheck.sh << EOF
for DBNAME in $(su - postgres -c 'psql -q -A -t -c "SELECT datname FROM pg_database WHERE datistemplate = false;" ')
do
    echo "Database: ${DBNAME}"
    su - postgres -c "psql -f /tmp/amcheck.sql -v 'ON_ERROR_STOP=1' ${DBNAME}" && EXIT_STATUS=$? || EXIT_STATUS=$?
    if [ "${EXIT_STATUS}" -ne 0 ]
    then
        echo "amcheck failed on DB: ${DBNAME}"
        exit 125
    fi
done
EOF
chmod +x /tmp/run_amcheck.sh

# запускаем скрипт
/tmp/run_amcheck.sh > /tmp/amcheck.log

# для проверки что всё прошло успешно можно проверить exit code или grep’нуть ошибку
if grep 'amcheck failed' "/tmp/amcheck.log"
then
    echo 'amcheck failed: '
    cat /tmp/amcheck.log
    exit 125
fi

résumant

Je voudrais exprimer ma gratitude à Andreï Borodine pour son aide dans la préparation de la publication et des remerciements particuliers pour sa contribution au développement de WAL-G !

Ceci conclut cette note. J'espère avoir pu transmettre la facilité de configuration et l'énorme potentiel d'utilisation de cet outil dans votre entreprise. J'ai beaucoup entendu parler de WAL-G, mais je n'ai jamais eu assez de temps pour m'asseoir et comprendre. Et après l’avoir mis en œuvre chez moi, cet article est sorti de moi.

Par ailleurs, il convient de noter que WAL-G peut également fonctionner avec les SGBD suivants :

Source: habr.com

Ajouter un commentaire