Репликация базы Postgrespro на двух нодах

от автора

Всем привет, получил от руководства интересную задачу, возможно кому то это поможет сэкономить много рабочих часов. Итак суть – необходимо настроить репликацию базы данных, по возможности сделать систему отказоустойчивой.

Вводные:

  1. AstraLinux

  2. Postgrespro-12

  3. 2 сервера, нет возможности создания облачного или постановки третьего

  4. Нет внешней связи, ограниченная среда

  5. Собрать максимально отказоустойчивую систему

Итак, получив вводные я приступил к подбору стека, на данный момент все популярные решения по репликации сводятся к трём серверам, часто используется связка: patroni+haproxy+keepalived+etcd(zookeeper, konsul), на двух машинах это тоже работает, если жива основная нода. Стоит её уронить и всё, база закрыта на запись.

Подумав и посоветовавшись с коллегами, пришли к выводу, что можно использовать старые добрые скрипты, в связке с keepalived.

На всякий случай поясню, что такое keepalived – это легкое приложение, для обеспечения отказоустойчивости сервисов и передачи VIP (virtual ip address).

Первым делом останавливаем базу и инициируем сервер:

sudo rm -rvf /var/lib/pgpro/std-12/data sudo -u root /opt/pgpro/std-12/bin/pg-setup initdb 

Пример положительного вывода:

5432 Server will use port 5432 OK 

Теперь наш сервер доступен на порту 5432.
Идем в pg_hba.conf и вносим изменения:

local   all             all                                     peer # IPv4 local connections: host    all             all             127.0.0.1/32            trust # IPv6 local connections: host    all             all             ::1/128                 md5 # Allow replication connections from localhost, by a user with the # replication privilege. local   replication     all                                     peer host    replication     replicator      195.117.117.31/31       md5 #первый сервер host    replication     replicator      195.117.117.32/32       md5 #второй сервер host    replication     replicator      195.117.117.0/24        md5 host    all             replicator      195.117.117.31/31       md5 host    all             replicator      195.117.117.32/32       md5 host    all             all             195.117.117..30/32       trust #виртуальный ip для keepalived 

Обязательно нужно зайти в postgresql.conf и отредактировать строку для получения соединения от другого сервера:

listen_addresses = '*'

Изменяем права и перезапускаем базу, после проверяем ее статус:

sudo chown postgres:postgres -Rv /var/lib/pgpro/ sudo systemctl restart postgrespro-std-12 sudo systemctl status postgrespro-std-12 

Нам понадобиться специальный пользователь для репликации данных:

sudo -u postgres psql -U postgres -h 195.117.117.31 -c "CREATE USER replicator REPLICATION LOGIN ENCRYPTED PASSWORD 'replicate';" 

С помощью команды проверяем статус инстанса postgrespro (рекомендую положить в скрипт, потому что в процессе настройки вы будете часто её вызывать на обоих серверах) :

sudo -u postgres psql -c " SELECT     'Текущий сервер' AS node,     inet_server_addr() AS server_ip,     CASE WHEN pg_is_in_recovery() THEN 'replica' ELSE 'master' END AS role,     current_timestamp AS check_time; " 

Примеры выводов:

      node      | server_ip |  role  |          check_time ----------------+-----------+--------+-------------------------------  Текущий сервер |           | master | 2025-09-03 17:29:58.768637+04 (1 row) 
      node      | server_ip |  role   |          check_time ----------------+-----------+---------+-------------------------------  Текущий сервер |           | replica | 2025-09-03 18:09:30.583748+04 (1 row) 

Начнем с предварительной чистки и смены прав на каталоги:

sudo rm -rvf /var/lib/pgpro/std-12/data sudo mkdir -p /var/lib/pgpro/std-12/data sudo chown -Rv postgres:postgres /var/lib/pgpro/std-12/data sudo chmod -Rv 700 /var/lib/pgpro/std-12/data 

Далее выполняем репликацию базы данных:

sudo -u postgres pg_basebackup \   -h 192.168.169.31 \   -U replicator \   -D /var/lib/pgpro/std-12/data \   -P  \   -v -R -W #пароль созданного юзера replicator c первого сервера (replicate) 

В обязательном порядке выполняем рестарт postgrespro после репликации:

sudo systemctl restart postgrespro-std-12

После выполнения всех операций возвращаемся на первый сервер и проверяем работу реплики:

sudo -u postgres psql -c "SELECT client_addr, state FROM pg_stat_replication;"

Пример положительного вывода:

  client_addr   |   state    ----------------+-----------  195.117.117.32 | streaming (1 row) 

Первый этап завершен, теперь первый сервер — мастер, второй — реплика, пора приступать к следующему этапу. Я использую MobaXterm и его функцию MultiExec, для ввода команд сразу на нескольких серверах. Теперь нам необходимо создать скрипты на обоих серверах и сделать их исполняемыми:

  1. Проверяем, запущен ли PostgreSQL (на любом сервере):

#check-pg-alive.sh #!/bin/bash  if sudo -u postgres pg_isready -h 127.0.0.1 -U postgres -t 3 >/dev/null 2>&1; then     exit 0 else     exit 1 fi 
  1. Поднятие инстанса в статусе мастера, если сосед не отвечает или не мастер:

#promote-standby.sh #!/bin/bash  # Путь к логу LOG="/var/log/postgresql/promote.log" mkdir -p /var/log/postgresql exec >> "$LOG" 2>&1 echo "$(date): Starting promotion process..."  # Проверяем, не мастер ли мы уже (если НЕ в recovery — значит, уже мастер) if sudo -u postgres psql -tAc "SELECT pg_is_in_recovery();" 2>/dev/null | grep -q "f"; then     echo "$(date): Already master, nothing to do."     exit 0 fi  # Проверяем, что мы в режиме восстановления (реплика) if ! sudo -u postgres psql -tAc "SELECT pg_is_in_recovery();" 2>/dev/null | grep -q "t"; then     echo "$(date): Not in recovery mode — cannot promote (not a replica)."     exit 1 fi  # Дополнительно: проверим, есть ли standby.signal (опционально, но полезно) STANDBY_SIGNAL="/var/lib/pgpro/std-12/data/standby.signal" if [ ! -f "$STANDBY_SIGNAL" ]; then     echo "$(date): Warning: standby.signal not found. Promotion will still proceed if in recovery." fi  # Запускаем promote echo "$(date): Promoting to master..." sudo -u postgres pg_ctl promote -D /var/lib/pgpro/std-12/data  # Ждём 3 секунды — promote обычно синхронный, но даём время sleep 3  # Проверяем результат if sudo -u postgres psql -tAc "SELECT pg_is_in_recovery();" 2>/dev/null | grep -q "f"; then     echo "$(date): Promotion successful — server is now master."     exit 0 else     echo "$(date): Promotion failed — still in recovery or PostgreSQL unreachable."     exit 1 fi 
  1. Делает из текущего инстанса реплику, если сосед мастер:

#rejoin-replica.sh #!/bin/bash set -euo pipefail  # === НАСТРОЙКИ === LOG="/var/log/postgresql/rejoin.log" DATA_DIR="/var/lib/pgpro/std-12/data" PEER_IP="195.117.117.32"       # IP предполагаемого мастера 32/31 в зависимости от того на какой ноде висит скрипт REPL_USER="replicator" PASSWORD="replicate" PORT="5432"  MAX_ATTEMPTS=3 RETRY_DELAY=5 STARTUP_TIMEOUT=20 ROLE_CHECK_ATTEMPTS=5 ROLE_CHECK_DELAY=5   sleep 100 # указывается для второго сервера, что бы не допустить одновременного поднятия двух мастеров    # === ПОДГОТОВКА ЛОГОВ === mkdir -p "$(dirname "$LOG")" chown postgres:postgres "$(dirname "$LOG")" || true chmod 755 "$(dirname "$LOG")" || true exec >> "$LOG" 2>&1 echo "[$(date)] === STARTING SMART REJOIN ==="  # === ФУНКЦИЯ: ПРОВЕРКА, ЯВЛЯЕТСЯ ЛИ УЗЕЛ МАСТЕРОМ === is_master() {     local ip=$1     if runuser -l postgres -c "         export PGPASSWORD='$PASSWORD';         psql -h '$ip' -U '$REPL_USER' -p '$PORT' -d 'postgres' -Atc 'SELECT pg_is_in_recovery();'     " 2>/dev/null | grep -q "^f$"; then         return 0  # Это мастер     else         return 1  # Не мастер (реплика или недоступен)     fi }  # === ФУНКЦИЯ: ПРОВЕРКА, ЗАПУЩЕН ЛИ ЛОКАЛЬНЫЙ ПОСТГРЕС === is_local_postgres_running() {     pg_ctl -D "$DATA_DIR" status &>/dev/null }  # === ФУНКЦИЯ: ОСТАНОВКА ЛОКАЛЬНОГО ПОСТГРЕСА === stop_postgres() {     echo "[$(date)] Stopping PostgreSQL..."     if is_local_postgres_running; then         runuser -l postgres -c "pg_ctl -D '$DATA_DIR' stop -m fast"     fi }  # === ФУНКЦИЯ: ЗАПУСК ПОСТГРЕСА (КАК РЕПЛИКА) === start_postgres() {     echo "[$(date)] Starting PostgreSQL..."     if ! is_local_postgres_running; then         runuser -l postgres -c "pg_ctl -D '$DATA_DIR' start"     fi }  # === ФУНКЦИЯ: ПОВЫШЕНИЕ ДО МАСТЕРА === promote_to_master() {     echo "[$(date)] Promoting to master..."      # Убедимся, что PostgreSQL запущен     start_postgres      # Проверяем, в режиме ли recovery (т.е. реплика)     if runuser -l postgres -c "psql -tAc 'SELECT pg_is_in_recovery();'" 2>/dev/null | grep -q "^t$"; then         runuser -l postgres -c "pg_ctl promote -D '$DATA_DIR'"         sleep 3         if runuser -l postgres -c "psql -tAc 'SELECT pg_is_in_recovery();'" 2>/dev/null | grep -q "^f$"; then             echo "[$(date)] Promotion successful — now master."         else             echo "[$(date)] Promotion failed or still in recovery."             exit 1         fi     elif runuser -l postgres -c "psql -tAc 'SELECT pg_is_in_recovery();'" 2>/dev/null | grep -q "^f$"; then         echo "[$(date)] Already master, nothing to do."     else         echo "[$(date)] PostgreSQL is unreachable."         exit 1     fi }  # === ФУНКЦИЯ: СТАТЬ РЕПЛИКОЙ === become_replica() {     echo "[$(date)] Master $PEER_IP is alive and is master. Becoming replica..."      stop_postgres      echo "[$(date)] Cleaning old data directory..."     rm -rf "$DATA_DIR"     mkdir -p "$DATA_DIR"     chown postgres:postgres "$DATA_DIR"     chmod 700 "$DATA_DIR"      echo "[$(date)] Running pg_basebackup from $PEER_IP..."     if ! runuser -l postgres -c "         export PGPASSWORD='$PASSWORD';         pg_basebackup \             -h '$PEER_IP' \             -p '$PORT' \             -U '$REPL_USER' \             -D '$DATA_DIR' \             -v \             -P \             -R     "; then         echo "[$(date)] pg_basebackup failed!"         exit 1     fi     echo "[$(date)] Base backup completed."      # Убедимся, что standby.signal есть     if [[ ! -f "$DATA_DIR/standby.signal" ]]; then         echo "[$(date)] Creating standby.signal..."         touch "$DATA_DIR/standby.signal"         chown postgres:postgres "$DATA_DIR/standby.signal"         chmod 600 "$DATA_DIR/standby.signal"     fi      start_postgres     echo "[$(date)] Replica setup complete and PostgreSQL started." }  # === ОСНОВНАЯ ЛОГИКА ===  # Ждём немного, чтобы сеть поднялась sleep 15  # Проверяем, можем ли мы достучаться до мастера и является ли он мастером echo "[$(date)] Checking master $PEER_IP status..."  if is_master "$PEER_IP"; then     echo "[$(date)] Master $PEER_IP is alive and is master."     become_replica else     echo "[$(date)] Master $PEER_IP is unreachable or not master. Promoting self to master."     promote_to_master fi  echo "[$(date)] === SMART REJOIN FINISHED ===" 

Обновляем конфигурацию systemd:

sudo systemctl daemon-reload 

Второй этап завершен, переходим к установке и настройке keepalived(VIP), для статьи я взял ip 195.117.117.30.

Устанавливаем keepalived:

sudo apt update sudo apt install keepalived 

Находим конфиг keepalived.conf и редактируем его:

global_defs {     router_id LVS_DEVEL }  vrrp_script chk_postgresql {     script "/usr/local/bin/check-pg-alive.sh"     interval 1     timeout 1     weight 2     fall 2     rise 2 }  vrrp_instance VI_1 {     state BACKUP     interface eth0     virtual_router_id 51     priority 101 # на реплике значение 100     advert_int 1     nopreempt # данная настройка предотвращает передачу VIP, если второй сервер стал мастером, несмотря на приоритет      authentication {         auth_type PASS         auth_pass your_keepalived_password     }      virtual_ipaddress {         195.117.117.30/32 dev eth0 label eth0:0     }      track_script {         chk_postgresql     }      notify_master /usr/local/bin/promote-standby.sh } 

После перезагрузки сервиса keepalived репликация настроена!

Проверить где сейчас VIP можно командой:

ip addr show | grep 195.117.117.30 

Итоги проделанной работы:

  1. Если падает первый сервер, то второй становится мастером, при поднятии первого, мастер остается на втором.

  2. При одновременном падении серверов, первый поднимется мастером, второй репликой

  3. При одновременном падении серверов и полном отказе первого, второй станет мастером, через 100 секунд после поднятия, первый после поднятия станет репликой.

P.S. Это моя первая статья, прошу не кидать тапки сразу, напомню что кейс в изолированной среде, по этому я не работал с настройками безопасности.


ссылка на оригинал статьи https://habr.com/ru/articles/945716/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *