Blog JSystems - uwalniamy wiedzę!

Szukaj
Z tego artykułu dowiesz się:
  • czym są failover, failback, switchover i w jakim celu się je stosuje,
  • jak promować replikę do trybu zapis-odczyt,
  • jak zamienić funkcjami replikę fizyczną i serwer primary,
  • jak synchronizować repliki i serwer primary za pomocą pg_rewind.

[host 2: serwer replika]

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.

[host 1: dawny serwer master]

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.

Wykonanie kontrolowanego failovera, krok po kroku.

[host 1: oryginalny serwer master]

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

[host 2: oryginalny serwer slave]

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()"

[host 1: oryginalny serwer master]

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)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...