Всем привет, получил от руководства интересную задачу, возможно кому то это поможет сэкономить много рабочих часов. Итак суть – необходимо настроить репликацию базы данных, по возможности сделать систему отказоустойчивой.
Вводные:
-
AstraLinux
-
Postgrespro-12
-
2 сервера, нет возможности создания облачного или постановки третьего
-
Нет внешней связи, ограниченная среда
-
Собрать максимально отказоустойчивую систему
Итак, получив вводные я приступил к подбору стека, на данный момент все популярные решения по репликации сводятся к трём серверам, часто используется связка: 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, для ввода команд сразу на нескольких серверах. Теперь нам необходимо создать скрипты на обоих серверах и сделать их исполняемыми:
-
Проверяем, запущен ли 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
-
Поднятие инстанса в статусе мастера, если сосед не отвечает или не мастер:
#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
-
Делает из текущего инстанса реплику, если сосед мастер:
#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
Итоги проделанной работы:
-
Если падает первый сервер, то второй становится мастером, при поднятии первого, мастер остается на втором.
-
При одновременном падении серверов, первый поднимется мастером, второй репликой
-
При одновременном падении серверов и полном отказе первого, второй станет мастером, через 100 секунд после поднятия, первый после поднятия станет репликой.
P.S. Это моя первая статья, прошу не кидать тапки сразу, напомню что кейс в изолированной среде, по этому я не работал с настройками безопасности.
ссылка на оригинал статьи https://habr.com/ru/articles/945716/
Добавить комментарий