Когда почта ложится — ложится всё. Переписка, задачи, согласования, доступы — всё завязано на почтовый сервер. А если под ним единственный PostgreSQL без резервирования, то между вами и катастрофой — один сбойный диск или зависший процесс.
В этой статье — пошаговый рецепт: собираем отказоустойчивый кластер PostgreSQL на базе Patroni, используем etcd для консенсуса и HAProxy как единую точку входа. Три виртуальные машины — и ваша почта RuPost переживёт падение ноды без потери писем и без ручного вмешательства.
Архитектура решения
Схема проста и проверена в продакшене: два инстанса PostgreSQL с потоковой репликацией, etcd как распределённое хранилище состояния кластера, HAProxy для маршрутизации клиентских подключений к текущему мастеру. Это позволяет избежать split-brain и обеспечивает прозрачный failover.
Что делает каждый компонент
Компонент | Роль |
Patroni | Управляет кластером PostgreSQL: автоматический failover, промоушен реплики в мастер, синхронизация конфигураций |
etcd | Распределённое key-value хранилище. Хранит состояние кластера и определяет лидера. Нужен минимум на 3 нодах для кворума |
HAProxy | Reverse proxy и единая точка входа. Проверяет через REST API, какая нода — мастер, и направляет трафик только к ней |
PostgreSQL | База данных с потоковой репликацией между мастером и репликой |
Шаг 1. Установка etcd на все ноды
etcd нужен на всех трёх ВМ. Он обеспечивает консенсус: при падении мастера PostgreSQL узлы голосуют за нового лидера.
Скачиваем и устанавливаем приложения. Затем удаляем временные файлы командами на всех ВМ
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
Проверяем корректность установки и перемещаем исполняемый файл в каталог /usr/local/bin/:
/tmp/etcd-download-test/etcd --version /tmp/etcd-download-test/etcdctl version mv /tmp/etcd-download-test/etcd* /usr/local/bin/
Создаём группу и пользователя в этой группе, от имени которого будет осуществляться работа etcd командами:
sudo groupadd --system etcd sudo useradd -s /sbin/nologin --system -g etcd etcd
Создаём каталог /var/lib/etcd и предоставляем права на этот каталог пользователю etcd командами:
sudo mkdir -p /var/lib/etcd/ sudo mkdir /etc/etcd sudo chown -R etcd:etcd /var/lib/etcd/ sudo chmod -R 700 /var/lib/etcd/
Следующие операции нужно выполнить на всех серверах, где будет установлен etcd.
Создаём файл службы /etc/systemd/system/etcd.service со следующим содержанием:
[Unit] Description=etcd service Documentation=https://github.com/coreos/etcd [Service] User=etcd Type=notify ExecStart=/usr/local/bin/etcd \ --name ${ETCD_NAME} \ --enable-v2=true \ --data-dir /var/lib/etcd \ --initial-advertise-peer-urls http://${ETCD_HOST_IP}:2380 \ --listen-peer-urls http://${ETCD_HOST_IP}:2380 \ --listen-client-urls http://${ETCD_HOST_IP}:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://${ETCD_HOST_IP}:2379 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster haproxy=http://192.168.42.9:2380,postgresql1=http:// 192.168.42.7:2380,postgresql2=http://192.168.42.8:2380 \ --initial-cluster-state new \ --heartbeat-interval 1000 \ --election-timeout 5000 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF
${ETCD_NAME} — это имя хоста где запущен etcd
${ETCD_HOST_IP} — это ip адрес хоста где запущен etcd
Имена haproxy, postgresql1 и postgresql2 — это хостнеймы наших reverse proxy, первого инстанса postgresql и второго инстанса postgresql соответственно.
IP адреса 192.168.42.9, 192.168.42.7 и 192.168.42.8 — это ip адреса наших reverse proxy, первого инстанса postgresql и второго инстанса postgresql соответственно.
После этого запускаем etcd на всех ВМ командами:
systemctl daemon-reload systemctl enable etcd systemctl start etcd.service
Первый запущенный сервер будет сервером начальной загрузки. После того как выполнится запуск всех серверов, выберется лидер.
Проверить состояние кластера после запуска можно командами:
systemctl status -l etcd.service etcdctl member list etcdctl endpoint status --write-out=table etcdctl endpoint health
Устанавливаем Postgresql на две ВМ, которые будут выступать в роли кластера postgresql, командами:
sudo apt install postgresql-14 dpkg -l | grep postgresql
Создаём пользователя для репликации в базе данных postgresql с именем replicator. Также в дальнейшем нам понадобится пользователь postgres.
Останавливаем службу postgresql. Управление кластером будет осуществлять patroni:
sudo systemctl stop postgresql sudo systemctl disable postgresql
Устанавливаем patroni командой:
apt install patroni
Создаём файл конфигурации для кластера patroni /etc/patroni.yml:
scope: postgres name: postgresql2 restapi: listen: 0.0.0.0:8008 connect_address: 192.168.42.8:8008 etcd: host: haproxy:2379 bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true use_slots: true parameters: wal_level: replica hot_standby: "on" logging_collector: "on" max_wal_senders: 5 max_replication_slots: 5 initdb: - encoding: UTF8 - data-checksums pg_hba: - host replication replicator 127.0.0.1/32 trust - host replication replicator 192.168.42.8/24 md5 - host all all 192.168.42.1/24 md5 - host all all 0.0.0.0/0 md5 users: admin: password: ******************** options: - createrole - createdb postgresql: listen: 0.0.0.0:5432 connect_address: 192.168.42.8:5432 data_dir: "/data/patroni" pgpass: /tmp/pgpass authentication: replication: username: replicator password: ******************** superuser: username: postgres password: ******************** parameters: unix_socket_directories: '/var/run/postgresql' tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
postgresql2 — это имя первого хоста где развернут postgresql
Haproxy — это имя хоста где развернут reverse proxy. Также здесь указан мой ip адрес postgresql1 192.168.42.8. У вас имена и ip адреса будут отличаться. Указываются два пользователя:
Replicator для создания репликаций
Postgres — полный админ базы данных postgresql
Также создаём аналогичный файл конфигурации patroni на второй ВМ, где развернут второй инстанс postgresql. Добавляем в файл /etc/hosts все узлы, которые принимают участие. Это нужно сделать на всех трёх ВМ. Пример моего файла /etc/hosts
192.168.42.7 postgresql1
192.168.42.9 haproxy
192.168.42.8 postgresql2
Проверяем конфигурацию командой на всех нодах:
patroni --validate-config /etc/patroni.yml
Подготавливаем директорию для работы patroni:
sudo mkdir -p /data/patroni sudo chown postgres:postgres /data/patroni sudo chmod 700 /data/patroni
Создаём службу для запуска patroni:
sudo nano /etc/systemd/system/patroni.service
[Unit] Description=High availability PostgreSQL Cluster After=syslog.target network.target [Service] Type=simple User=postgres Group=postgres ExecStart=/usr/local/bin/patroni /etc/patroni.yml KillMode=process TimeoutSec=30 Restart=no [Install] WantedBy=multi-user.target
Запускаем patroni. Проверяем службу на всех ВМ:
sudo systemctl start patroni sudo systemctl status patroni
Подготавливаем HAproxy для единой точки входа в наш кластер. Информацию о том, куда направлять соединения, haproxy будет получать от patroni.
Устанавливаем HAproxy командой:
sudo apt -y install haproxy
Создаём бэкап конфигурационного файла HAproxy и редактируем его под наши нужды:
sudo cp -p /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg-orig sudo nano /etc/haproxy/haproxy.cfg
Используйте свои IP-адреса
global maxconn 100 defaults log global mode tcp retries 2 timeout client 30m timeout connect 4s timeout server 30m timeout check 5s listen stats mode http bind *:7000 stats enable stats uri / listen postgres bind *:5000 option httpchk http-check expect status 200 default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions server patroni1 192.168.42.7:5432 maxconn 100 check port 8008 server patroni2 192.168.42.8:5432 maxconn 100 check port 8008
Проверяем конфигурационный файл на наличие ошибок командой:
sudo /usr/sbin/haproxy -c -V -f /etc/haproxy/haproxy.cfg
Запускаем HAproxy командой и проверяем состояние:
sudo systemctl restart haproxy sudo systemctl status haproxy
После этого мы сможет подключаться к нашему кластеру postgresql по порту 7000
Перейдём к особенностям настройки кластера patroni для почтовой системы rupost
При конфигурации почтовой системы rupost необходимо при конфигурации базы данных поставить галочку использовать patroni как на рис. 1

Далее вам нужно ввести ip адрес любой из нод postgresql и порт API. И также логин и пароль пользователя rupost (как на рис.2)

Далее для корректной работы rupost необходимо увеличить количество подключений к базе postgresql. Для этого:
sudo patronictl -c /etc/patroni/config.yml edit-config
Поправить на 500. Затем нажать последовательно ctrl затем O

Затем Enter

Затем надо нажать ctrl + x
Подтвердите вносимые изменения, нажав Y
Для применения вносимых изменений нужно перезагрузить кластер patroni командой:
patronictl -c /etc/patroni/config.yml restart patroni-psql15
Подтвердите перезагрузку, нажав Y.
После этого изменения вступят в силу.
Таким образом, в статье полностью описан процесс настройки кластера patroni с особенностями использования для rupost.
Готовый кластер обеспечивает:
Автоматический failover — при падении мастера Patroni промоутит реплику, HAProxy переключает трафик
Единую точку входа — приложениям не нужно знать топологию кластера
Защиту от split-brain — etcd с кворумом из 3 нод исключает конфликт мастеров
Мониторинг — веб-панель HAProxy на порту 7000 для визуального контроля
Три ВМ, свободный стек, воспроизводимая настройка. Почта работает — даже когда сервер не хочет.
