Blog JSystems - uwalniamy wiedzę!
Blog JSystems - uwalniamy wiedzę!
Failover, czyli awaryjne przerzucenie odpowiedzialności za wszystkie zadania wykonywane przez klaster primary na inną replikowaną instancję, oraz otwarcie jej do zapisu w postgresie w środowiskach produkcyjnych o najwyższych wymogach dostępności. Odpowiada za to zazwyczaj jakieś zewnętrzne narzędzie jak Patroni - czy REPMGR albo autorskie, niestandardowe skrypty czy automatyzacje.
Przejęcie roli primary w postgresie możemy wykonać na kilka sposobów. Za pomocą narzędzia pg_ctl, funkcji pg_promote() lub pliku triggera zdefiniowanego w parametrze postgresql.conf "promote_trigger_file"
/usr/lib/postgresql/15/bin/pg_ctl -D /data_pg/ promote
lub
psql -c "select pg_promote()"
lub
touch /sciezka/do/pliku/triggera
Po wykonaniu jednego polecenia z powyższych replika zakończy odtwarzanie, podniesie znacznik linii czasu o 1 oraz otworzy bazę do zapisu.
Linie czasu (timeline) to mechanika wprowadzona w postgresie, która gwarantuje, że po każdym zakończeniu odtwarzania zmian, czy to po odzyskaniu bazy z kopii zapasowej, czy awansowanie - repliki na instancję, primary spowoduje zmianę "generacji" klastra, - przez co pliki WAL będą generowane z nowym przedrostkiem. Pierwsza wartość w nazwie pliku WAL to numer linii czasu, dzięki czemu, jeżeli przez problemy sieciowe stara instancja primary była - odizolowana od reszty klastra i aplikacji - przed rozwiązaniem problemu wygenerowała nowe pliki WAL, nie będzie konfliktu z logami WAL wygenerowanymi przez świeżo awansowaną na primary replikę. Stara instancja primary będzie nadal generowała logi transakcyjne w poprzedniej linii czasu, a nowa już w podniesionej. Każde podniesienie linii czasu powoduje również stworzenie pliku historii dla danej linii czasu, który zawiera między innymi informację, w którym miejscu linie czasu się rozdzieliły. Dzięki temu unikniemy problemu zduplikowanych logów transakcyjnych i będziemy mogli jednocześnie archiwizować pliki WAL dla obu klastrów, starego primary i nowego. A korzystając z pełnej kopii zapasowej wykonanej dla starej instancji primary oraz plików WAL, będziemy mogli odtworzyć dowolny z klastrów primary do dowolnego momentu w czasie. Dzięki parametrowi postgresa "recovery_target_timeline", którym możemy wskazać, w której linii czasu powinien wykonać odzyskiwanie, domyślną wartością jest - "latest", czyli podczas odtwarzania zmian postgres podąża za wszystkimi zmianami linii czasu, jeżeli posiada odpowiednią liczbę plików WAL w archiwum lub katalogu pg_wal.
Failback, czyli powrót do oryginalnej konfiguracji, przed wystąpieniem sytuacji awaryjnej.
Musimy zacząć od podłączenia starej instancji primary do klastra jako replika i zacząć odtwarzać wszystkie zmiany wykonane, kiedy była niedostępna. Czasami jeżeli awansowanie repliki na primary było kontrolowane, wystarczy stworzyć plik standby.signal w PGDATA, zadbać o odpowiednią konfigurację w parametrach primary_conninfo, restore_command i recovery_target_timeline = 'latest' oraz wystartować starą instancję główną jako replikę.
Ale jeżeli doszło do nieplanowanego zatrzymania primary, doszło do awarii sieci lub jakiejkolwiek sytuacji, której pierwotna primary mogła zapisać jakieś dane - nieprzesłane na nowo awansowaną instancję główną, może być wymagane użycie pg_rewind, czyli narzędzia do synchronizacji dwóch katalogów PGDATA odmiennej generacji. Aby móc go użyć, musimy posiadać użytkownika z odpowiednimi uprawnieniami oraz dodać na nowym serwerze primary wpisy pg_hba.conf umożliwiające połączenie do bazy "postgres" i pozwalające na inicjalizację replikacji.
Przykładowe polecenie dla pg_rewind w trybie "dry run". Nie wykonujmy na razie poniższego pg_rewinda, praktyczna demonstracja funkcjonalności przedstawiona będzie trochę później.
/usr/lib/postgresql/15/bin/pg_rewind -n -c -R -D /data_pg/ --source-server='host=<X.X.X.X adres nowego primary> port=5432 user=replicator password=tajnehaslo application_name=pg1 dbname=postgres'
Powyższe polecenie z powodu użycia przełącznika "-n" wykona tzw. "dry run", sprawdzi, czy wszystko jest ustawione jak należy, czy użytkownik ma odpowiednie uprawnienia, czy wszystkie pliki WAL są dostępne. Jeżeli polecenie się powiedzie, możemy usunąć "-n" i wykonać je jeszcze raz.
Przykład polecenia pg_rewind:
/usr/lib/postgresql/15/bin/pg_rewind -c -R -D /data_pg/ --source-server='host=<X.X.X.X adres nowego primary> port=5432 user=replicator password=tajnehaslo application_name=pg1 dbname=postgres'
Po wykonaniu pg_rewind zweryfikujmy, - czy został stworzony plik standby.signal.
Później możemy zmodyfikować primary_conninfo, aby dodać parametr application_name, ustawić odpowiednio wartość dla "primary_slot_name" oraz na aktualnej primary stworzyć nowy fizyczny slot replikacyjny.
Przykład tworzenia fizycznego slotu replikacyjnego:
postgres=# select pg_create_physical_replication_slot('replika_pg1');
Możemy wystartować starą instancję główną, która teraz powinna łączyć się jako replika do nowego primary.
Znając wszystkie kroki, - możemy wykonać failback poprzez switchover, czyli kontrolowaną zamianę ról pomiędzy serwerami. Zatrzymać aktualny primary, awansować replikę i otworzyć do zapisu, upewnić się, że slot replikacyjny istnieje, na zatrzymanej instancji stworzyć "standby.signal" oraz wystartować klaster.
Stworzenie użytkownika na aktualnym serwerze obsługującym zapis (master / primary):
postgres@ubuntu:~$ psql create user replicator with replication password 'tajnehaslo'; GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean) TO replicator; GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO replicator; GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO replicator; GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean) TO replicator;
Zatrzymanie postgresa na klastrze primary "pg1".
postgres@ubuntu-pg1:~$ /usr/lib/postgresql/15/bin/pg_ctl -D /data_pg/ stop
Promote instancji standby "pg2" oraz stworzenie slotu replikacyjnego dla starego primary. Nazwę slota replikacyjnego powinniśmy wybrać w sposób pozwalający nam na łatwą identyfikację, - dla jakiej usługi dany slot został stworzony.
postgres=# select pg_promote();
postgres=# select pg_create_physical_replication_slot('pg1');
Sprawdzić wpisy w pg_hba.conf na pg2 czy użytkownik replicator ma możliwość podłączenia się do bazy postgres i jako replikacja. Dodajemy je, jeżeli nie istnieją, podając wewnętrzny adres IP serwera pg1 , czyli naszego dawnego mastera:
host replication replicator 192.168.0.26/32 scram-sha-256 host postgres replicator 192.168.0.26/32 scram-sha-256
Po edycji pg_hba.conf pamiętajmy o przeładowaniu konfiguracji na pg2.
psql -c "select pg_reload_conf()"
Synchronizacja katalogu z danymi ze starej instancji primary (pg1) z nową (pg2) za pomocą pg_rewind. Poniższe wykonujemy jako użytkownik systemowy postgres na dawnym serwerze primary - czyli pg1:
/usr/lib/postgresql/15/bin/pg_rewind -c -R -D /data_pg/ --source-server='host=<X.X.X.X adres nowego primary> port=5432 user=replicator password=tajnehaslo application_name=pg1 dbname=postgres'
Sprawdzenie w postgresql.auto.conf, czy konfiguracja recovery (restore_command) dla replikacji wal-shipping i ustawnienia do replikacji strumieniowej (primary_conninfo, primary_slot_name oraz restore_command) są poprawne na pg1. Jeżeli któryś z nich nie istnieje, należy go dodać.
Parametr primary_conninfo powinien odnosić się w connection stringu do odpowiedniego hosta (host=x.x.x.x) oraz zawierać application_name z nazwą dla instancji, która pozwoli nam na jej szybką identyfikację, np. "pg1", a w primary_slot_name (który dopisujemy, - jeśli go nie ma) powinniśmy mieć nazwę slota, który wcześniej stworzyliśmy na pg2, czyli "pg1".
Przykładowo:
restore_command = 'pgbackrest --stanza=test --pg1-path=/data_pg --archive-get %f "%p"' primary_conninfo = 'host=10.1.0.11 application_name=pg1 ...' primary_slot_name = 'pg1'
postgres@ubuntu-pg1:~$ /usr/lib/postgresql/15/bin/pg_ctl -D /data_pg/ start
Ostatnim krokiem będzie weryfikacja, czy replikacja działa poprawnie, za pomocą odpowiednich zapytań, z widoku pg_stat_replication na primary (pg2) lub pg_stat_wal_receiver na replica (pg1).
select * from pg_stat_replication; select * from pg_stat_wal_receiver;
Komentarze (0)
Brak komentarzy...