WAL-G: kopie zapasowe i odzyskiwanie bazy danych PostgreSQL

Od dawna wiadomo, że tworzenie kopii zapasowych w formie zrzutów SQL (używając pg_dump lub pg_dumpall) to nie najlepszy pomysł. Do tworzenia kopii zapasowych bazy danych PostgreSQL lepiej jest używać polecenia pg_basebackup, które tworzy binarną kopię dzienników WAL. Ale gdy zaczniesz badać cały proces tworzenia kopii i przywracania, zrozumiesz, że musisz napisać przynajmniej kilka trójkołowych rowerów, aby to wszystko działało i nie sprawiało ci bólu ani z góry, ani z dołu. Aby ułatwić cierpienie, opracowano WAL-G.

WAL-G to narzędzie napisane w języku Go do tworzenia kopii zapasowych i przywracania baz danych PostgreSQL (a od niedawna także MySQL/MariaDB, MongoDB i FoundationDB). Obsługuje magazyny Amazon S3 (i odpowiedniki, np. Yandex Object Storage), a także Google Cloud Storage, Azure Storage, Swift Object Storage i po prostu system plików. Cała konfiguracja sprowadza się do prostych kroków, ale ponieważ artykuły na ten temat są rozproszone w internecie – nie ma pełnego przewodnika how-to, który obejmowałby wszystkie kroki od początku do końca (na blogu Habr jest kilka postów, ale wiele kwestii zostało tam pominiętych).

WAL-G: kopie zapasowe i odzyskiwanie bazy danych PostgreSQL

Artykuł ten został napisany przede wszystkim w celu usystematyzowania mojej wiedzy. Nie jestem DBA i czasami mogę wyrażać się w sposób zrozumiały dla programistów, dlatego wszelkie poprawki są mile widziane!

Szczególnie podkreślam, że wszystko, co poniżej, jest aktualne i sprawdzone dla PostgreSQL 12.3 na Ubuntu 18.04, wszystkie polecenia powinny być wykonywane przez uprzywilejowanego użytkownika.

Instalacja

W momencie pisania tego artykułu stabilna wersja WAL-G to v0.2.15 (marzec 2020). To właśnie będziemy stosować (ale jeśli chcesz samodzielnie zbudować go z gałęzi master, to w repozytorium na githubie znajdują się wszystkie instrukcje dotyczące tego). Aby pobrać i zainstalować, musisz wykonać:

#!/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/

Następnie musisz skonfigurować najpierw WAL-G, a potem sam PostgreSQL.

Konfiguracja WAL-G

Na przykład do przechowywania kopii zapasowych zostanie użyty Amazon S3 (ponieważ jest bliżej moich serwerów i jego użycie jest bardzo tanie). Do pracy z nim potrzebny jest 'koszyk S3' oraz klucze dostępu.

We wszystkich wcześniejszych artykułach o WAL-G używano konfiguracji za pomocą zmiennych środowiskowych, ale od tej wersji ustawienia można umieścić w .walg.json pliku w katalogu domowym użytkownika postgres. Aby go utworzyć, wykonamy następujący skrypt bash:

#!/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

Trochę wyjaśnię wszystkie parametry:

  • WALG_S3_PREFIX – ścieżka do twojego koszyka S3, do którego będą przesyłane kopie zapasowe (można to zrobić zarówno do głównego katalogu, jak i do folderu);
  • AWS_ACCESS_KEY_ID – klucz dostępu do S3 (w przypadku przywracania na serwerze testowym – klucze te muszą mieć politykę ReadOnly! Szczegóły można znaleźć w sekcji dotyczącej przywracania);
  • AWS_SECRET_ACCESS_KEY – sekretne hasło w magazynie S3;
  • WALG_COMPRESSION_METHOD – metoda kompresji, najlepiej użyć Brotli (gdyż jest to złoty środek między rozmiarem wynikowym a prędkością kompresji/dekompresji);
  • WALG_DELTA_MAX_STEPS – liczba „delta” przed utworzeniem pełnej kopii zapasowej (pozwalają zaoszczędzić czas i rozmiar przesyłanych danych, ale mogą nieco spowolnić proces przywracania, dlatego niepożądane jest używanie dużych wartości);
  • PGDATA – ścieżka do katalogu z danymi twojej bazy (można sprawdzić, wykonując polecenie pg_lsclusters);
  • PGHOST – połączenie z bazą, przy lokalnej kopii zapasowej najlepiej robić przez unix-socket jak w tym przykładzie.

Pozostałe parametry można znaleźć w dokumentacji: https://github.com/wal-g/wal-g/blob/v0.2.15/PostgreSQL.md#configuration.

Konfiguracja PostgreSQL

Aby archiwizator wewnątrz bazy mógł sam przesyłać dzienniki WAL do chmury i ewentualnie je przywrócić – trzeba ustawić kilka parametrów w pliku konfiguracyjnym /etc/postgresql/12/main/postgresql.conf. Na początek musisz upewnić się, że żadne z poniższych ustawień nie są przypisane do innych wartości, aby przy ponownym uruchomieniu konfiguracji – DBMS nie zawiodło. Te parametry można dodać za pomocą:

#!/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

Opis ustawianych parametrów:

  • wal_level – ile informacji pisać w dziennikach WAL, „replica” – pisać wszystko;
  • archive_mode – włączenie przesyłania dzienników WAL przy użyciu polecenia z parametru archive_command;
  • archive_command – polecenie do archiwizacji zakończonego dziennika WAL;
  • archive_timeout – archiwizacja dzienników następuje tylko po ich zakończeniu, ale jeśli twój serwer rzadko zmienia/dodaje dane do bazy, sensowne jest ustawienie tutaj limitu w sekundach, po upływie którego polecenie archiwizacji zostanie wymuszone (mam intensywny zapis do bazy co sekundę, dlatego zrezygnowałem z ustawienia tego parametru w produkcji);
  • restore_command – polecenie do przywracania dziennika WAL z kopii zapasowej, będzie używane, jeśli w „pełnej kopii zapasowej” (base backup) brakować będzie ostatnich zmian w DB.

Więcej szczegółów na temat tych wszystkich parametrów można znaleźć w tłumaczeniu oficjalnej dokumentacji: https://postgrespro.ru/docs/postgresql/12/runtime-config-wal.

Konfiguracja harmonogramu tworzenia kopii zapasowych

Nie ulega wątpliwości, że najwygodniejszym sposobem na uruchomienie jest cron. To jego ustawimy do tworzenia kopii zapasowych. Rozpoczniemy od polecenia stworzenia pełnej kopii zapasowej: w wal-g jest to argument uruchomienia. backup-push. Na początek warto jednak wykonać to polecenie ręcznie jako użytkownik postgres, aby upewnić się, że wszystko działa poprawnie (i nie ma żadnych błędów dostępu):

#!/bin/bash

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

W argumentach uruchamiania podany jest ścieżka do katalogu z danymi – przypominam, że można ją poznać, wykonując pg_lsclusters.

Jeśli wszystko przebiegło bez błędów i dane zostały załadowane do przechowalni S3, to można skonfigurować okresowe uruchamianie w crontabie:

#!/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

W tym przykładzie proces tworzenia kopii zapasowej uruchamiany jest codziennie o 4:15 rano.

Usuwanie starych kopii zapasowych

Prawdopodobnie nie potrzebujesz przechowywać absolutnie wszystkich kopii zapasowych z ery mezozoicznej, dlatego przydatne będzie okresowe "oczyszczanie" Twojej przechowalni (zarówno "pełnych kopii zapasowych", jak i dzienników WAL). Zrobimy to również za pomocą zadania 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 będzie wykonywał to zadanie codziennie o 6:30 rano, usuwając wszystko (pełne kopie, różnice i WAL-e) z wyjątkiem kopii z ostatnich 10 dni, ale pozostawi co najmniej jedną kopię do z podanej daty, aby każdy punkt po daty mógł trafić do PITR.

Przywracanie z kopii zapasowej

Nie jest tajemnicą, że kluczowym elementem zdrowej bazy danych jest okresowe przywracanie i sprawdzanie integralności danych wewnątrz. Jak przywrócić za pomocą WAL-G – opowiem w tej sekcji, a o sprawdzaniu porozmawiamy później.

Osobno warto zauważyć że do przywracania w środowisku testowym (wszystko, co nie jest produkcją) – należy używać konta tylko do odczytu w S3, aby przypadkowo nie nadpisać kopii zapasowych. W przypadku WAL-G należy nadać użytkownikowi S3 następujące uprawnienia w zasadach grupowych (Efekt: Zezwól): s3:GetObject, s3:ListBucket, s3:GetBucketLocation. I oczywiście, nie zapomnij wcześniej ustawić archive_mode=off w pliku konfiguracyjnym postgresql.conf, aby Twoja baza testowa nie chciała stworzyć kopii zapasowej bezgłośnie.

Przywracanie odbywa się za pomocą prostego ruchu ręki z usunięciem wszystkich danych PostgreSQL (w tym użytkowników), dlatego proszę zachować szczególną ostrożność podczas uruchamiania następujących poleceń.

#!/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

Dla tych, którzy chcą sprawdzić proces odzyskiwania — poniżej przygotowano mały kawałek magii bash, aby w przypadku problemów z odzyskiwaniem – skrypt zakończył się z kodem wyjścia różnym od zera. W tym przykładzie przeprowadzanych jest 120 testów z czasem oczekiwania 5 sekund (łącznie 10 minut na odzyskanie), aby ustalić, czy plik sygnałowy został usunięty (to oznacza, że proces odzyskiwania zakończył się sukcesem):

#!/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

Po udanym odzyskaniu nie zapomnij ponownie uruchomić wszystkich procesów (pgbouncer/monit itd.).

Weryfikacja danych po odzyskaniu

Należy koniecznie sprawdzić integralność bazy po odzyskaniu, aby uniknąć sytuacji z uszkodzoną/błędną kopią zapasową. Warto robić to z każdą utworzoną archiwizacją, jednak gdzie i jak – zależy tylko od waszej wyobraźni (można uruchomić oddzielne serwery na podstawie godzinowej lub przeprowadzić weryfikację w CI). Jednak przynajmniej – konieczne jest sprawdzenie danych i indeksów w bazie.

Aby sprawdzić dane, wystarczy przepuścić je przez zrzut, ale lepiej, aby przy tworzeniu bazy włączone były sumy kontrolne (suma kontrolna danych):

#!/bin/bash

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

Aby sprawdzić indeksy – istnieje moduł amcheck, zapytanie sql do niego weźmiemy z testów WAL-G i wokół tego skonstruujemy małą logikę:

#!/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

Podsumowując,

Chcę podziękować Andriejowi Borodinowi za pomoc w przygotowaniu publikacji oraz szczególne podziękowania za jego wkład w rozwój WAL-G!

Na tym ten artykuł dobiegł końca. Mam nadzieję, że udało mi się przekazać łatwość konfiguracji i ogromny potencjał tego narzędzia w twojej firmie. Słyszałem wiele o WAL-G, ale nigdy nie miałem czasu, aby usiąść i się z nim zapoznać. A po jego wdrożeniu, ta artykuł wyszedł ze mnie.

Warto również zauważyć, że WAL-G może również działać z następującymi systemami bazodanowymi:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster