Заряжай Patroni. Тестируем Patroni + Zookeeper кластер (Часть первая)
Кадр из фильма «Рембо IV»
Вступление
Если вы работаете с crucial data, то рано или поздно задумаетесь о том, что неплохо бы поднять кластер отказоустойчивости. Даже если основной сервер с базой улетит в глухой нокаут, show must go on, не так ли? При этом мы подразумеваем две вещи:
Patroni лишь одно из решений проблемы. До него я попробовал несколько других сервисов и на Github одного из них (не буду показывать курсором) до сих пор висит без ответа открытый мной баг репорт, хотя прошло уже несколько месяцев. У другого была не самая полная документация. Какие-то мне показались недостаточно надежными.
По Patroni же действительно много инфы, и он качественно поддерживается разработчиками. Также он использует DCS для синхронизации нод, что должно предотвращать ситуации со «split brain».
Из минусов совершенно точно нужно назвать то, что это не «out of the box solution. Как сказано в доке:
«Patroni is a template for you to create your own customized, high-availability solution using Python…»
И ключевое здесь слово «template». То есть все придется собирать самому. Но в каком-то смысле это плюс — по крайней мере, мы детально будем знать, что именно идёт в прод.
По специализации, кстати, я не DevOps инженер. Когда появилась необходимость поднять отказоустойчивый кластер мне пришлось собрать все ямы и столбы какие только были на этом пути. Надеюсь, этот туториал поможет вам достичь результата, испытав в разы меньше страданий и боли, чем довелось мне.
Если вы устали уже от вступлений и рветесь в бой, смело переходите к следующей главе.
Если нет, то предлагаю под спойлером прочесть короткие заметки по конфигурации кластера, которую я выбрал для деплоя.
Что, ещё один туториал о Patroni?
Зачем читать именно этот туториал?
Есть уже немало туториалов, которые рассказывают, как поднять кластер Patroni. Этот затрагивает вопросы деплоя в среде docker swarm и использования Zookeeper в качестве DCS.
Почему Zookeeper?
Я считаю, это один из моментов, которые стоит рассмотреть всерьез, прежде чем выбрать конечный сетап для продакшена. Дело в том, что Patroni использует сторонние сервисы чтобы установить и обслуживать коммуникацию между своими нодами. Их общее название — DCS (Dynamic Configuration Storage).
Если вы уже смотрели какие-то из туториалов о Patroni, то, должно быть, заметили, что самый частый кейс — это когда в качестве DCS используют Etcd кластер.
Интересный момент в работе Etcd кластера заключается в том, что:
Since etcd writes data to disk, its performance strongly depends on disk performance. For this reason, SSD is highly recommended.
(из документации Etcd)
Словом, если у вас нет по SSD диску на каждой машине, где будут работать ноды Etcd, то вы в зоне опасности. Конечно, пока нагрузка небольшая, то ничего критичного происходить не будет, но если это рабочий, нагруженный прод, то очень возможно (и даже вероятно), что вы просто перегрузите Etcd кластер. А это приведет к IO ошибкам при доступе к базе. Звучит скверно? На самом деле так и есть. Ловить такие ошибки на проде очень неприятно.
Здесь нам на помощь и может прийти Zookeeper, который ничего не пишет на диск и хранит все данные в памяти. Такой вариант оптимален в ситуации, когда не на всех серверах есть SSD, зато RAM хватает.
Почему Docker Swarm?
У меня не было выбора, так как одним из ключевых требований было, чтобы кластер был развернут в Swarm«е. Так что, если это и ваш кейс тоже, то вы в правильном месте!
Для тех же, кто открыл пост с желанием потестировать технологию, выбор Docker Swarm«а тоже может быть вполне органичным. Хотя бы по той причине, что вам не придется устанавливать и настраивать никаких сторонних сервисов (ну за исключением самого Docker«а, разумеется) или тянуть на локальную машину ворох непонятных зависимостей. Полагаю, недалеко от истины утверждение, что Docker у нас у всех и так уже настроен везде где только можно, мы все знаем, как он работает, так что почему бы не использовать его.
Потребуется лишь одна команда, чтобы сделать тюнинг Docker«а, который позволит развернуть на локальной машине кластер Patroni на 3 ноды без виртуальных машин, Kubernetes или подобных вещей.
Если вы не хотите копать в сторону каких-то еще инструментов за пределами Docker«а и хотите сделать все чисто и аккуратно, то данный туториал вам более чем подойдет.
А в конце будет небольшой бонус
Во второй части туториала я покажу различные варианты проверки статуса кластера (целых 3), и в конце дам простой скрипт с инструкциями для быстрого теста кластера.
Окей, достаточно разговоров. Давайте перейдем к практике.
Docker Swarm
Для быстрого и приближенного к реальности теста, нам, на самом деле, достаточно одной ноды в Swarm кластере. Поскольку мы можем очень легко поднимать и ронять сервисы, запущенные в Swarm«е, мы сможем имитировать падение сервера с нодой, имея в распоряжении только одну локальную машину.
Я исхожу из предположения, что у вас уже установлен и настроен Docker Engine. В таком случае, нужно только выполнить следующую команду:
docker swarm init
//now check your single-node cluster
docker node ls
ID HOSTNAME STATUS AVAILABILITY
a9ej2flnv11ka1hencoc1mer2 * floitet Ready Active
Одна из важных фич Swarm«а заключается в том, что теперь мы можем использовать не только обычные Docker контейнеры, но и так называемые сервисы. Сервисы — это по сути дела абстракция над контейнерами. Если отталкиваться от аналогии с ООП, то сервис — это класс, а контейнер конкретный объект класса. Параметры и правила сервиса задаются при деплое из yml-файла.
Рекомендую запомнить hostname ноды — потом мы используем его для указания «constraint» в конфигурационном файле.
В целом, это все приготовления в части Docker Swarm«а, которые нужно сделать. Здесь никаких проблем быть не должно. Так что двинемся дальше.
Zookeeper
Прежде чем мы начнем деплой самого Patroni, нам нужно сначала развернуть кластер с DCS (в нашем случае, как мы помним, это Zookeeper). Я взял версию 3.4, и она работает вполне стабильно. Далее идет docker-compose конфиг и некоторые комментарии по моментам, которые, как мне кажется, имеет смысл отдельно упомянуть.
docker-compose-zookeeper.yml
version: '3.7'
services:
zoo1:
image: zookeeper:3.4
hostname: zoo1
ports:
- 2191:2181
networks:
- patroni
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=0.0.0.0:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
deploy:
replicas: 1
placement:
constraints:
- node.hostname == floitet
restart_policy:
condition: any
zoo2:
image: zookeeper:3.4
hostname: zoo2
networks:
- patroni
ports:
- 2192:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=0.0.0.0:2888:3888 server.3=zoo3:2888:3888
deploy:
replicas: 1
placement:
constraints:
- node.hostname == floitet
restart_policy:
condition: any
zoo3:
image: zookeeper:3.4
hostname: zoo3
networks:
- patroni
ports:
- 2193:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=0.0.0.0:2888:3888
deploy:
replicas: 1
placement:
constraints:
- node.hostname == floitet
restart_policy:
condition: any
networks:
patroni:
driver: overlay
attachable: true
Details
Конечно же, важно дать каждой ноде уникальное имя и внешний порт. Hostname лучше ставить одинаковое с именем сервиса.
zoo1:
image: zookeeper:3.4
hostname: zoo1
ports:
- 2191:2181
Стоит отметить и то, как мы перечисляем host«ы в строке ниже: для первого сервиса server.1 будет привязан к 0.0.0.0, а, например, для zoo2 это уже будет server.2 соответственно и т.д.
ZOO_SERVERS: server.1=0.0.0.0:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
А таким образом мы контролируем распределение сервисов по нодам. Поскольку нода у нас сейчас только одна, мы спокойно могли бы убрать эти строчки из конфига, но когда серверов будет несколько, можно будет менять node.hostname и тем самым определять на какую ноду пойдет сервис.
placement:
constraints:
- node.hostname == floitet
И последний момент, который мы здесь обсудим, это network. Я намерен деплоить все сервисы Zookeeper«а и все сервисы Patroni в одну сеть с драйвером overlay, чтобы они были изолированы от других сервисов и могли общаться между собой по именам, а не по IP (как это выглядит, будет видно дальше).
networks:
patroni:
driver: overlay
// мы должны отметить сеть как attachable
// чтобы потом можно было присоединять к ней остальные сервисы
attachable: true
Итак, можно задеплоить Zookeeper:
sudo docker stack deploy --compose-file docker-compose-zookeeper.yml patroni
Теперь нужно проверить, что все работает. Первое что можно сделать это просто посмотреть список сервисов:
sudo docker service ls
gxfj9rs3po7z patroni_zoo1 replicated 1/1 zookeeper:3.4 *:2191->2181/tcp
ibp0mevmiflw patroni_zoo2 replicated 1/1 zookeeper:3.4 *:2192->2181/tcp
srucfm8jrt57 patroni_zoo3 replicated 1/1 zookeeper:3.4 *:2193->2181/tcp
И следующим шагом можно сделать пинг сервисов с помощью специальной команды mntr:
echo mntr | nc localhost 2191
// with the output being smth like this
zk_version 3.4.14-4c25d480e66aadd371de8bd2fd8da255ac140bcf, built on 03/06/2019 16:18 GMT
zk_avg_latency 6
zk_max_latency 205
zk_min_latency 0
zk_packets_received 1745
zk_packets_sent 1755
zk_num_alive_connections 3
zk_outstanding_requests 0
zk_server_state follower
zk_znode_count 16
zk_watch_count 9
zk_ephemerals_count 4
zk_approximate_data_size 1370
zk_open_file_descriptor_count 34
zk_max_file_descriptor_count 1048576
zk_fsync_threshold_exceed_count 0
Также можно проверить логи сервиса, если есть желание:
docker service logs $zookeeper-service-id
// service-id comes from 'docker service ls' command.
// in my case it could be
docker service logs gxfj9rs3po7z
Отлично, вот мы и разобрались с Zookeeper«ом. Теперь можно переходить к самому Patroni.
Patroni
Мы наконец добрались до основной части туториала, где нам предстоит поднимать кластер Patroni. Первое что нужно сделать — это билд кастомного имейджа Patroni, чтобы нам было что деплоить. Мы включим в сборку только самые необходимые вещи, и я постараюсь объяснить все шаги как можно более подробно, чтобы в дальнейшем вам легко было работать с этим образом и апгрейдить его по своему желанию.
Сначала создадим отдельную директорию «patroni-test» и перейдем в неё. Для того чтобы успешно сбилдить рабочий имейдж нам понадобится пара дополнительных файлов, с них и начнем.
patroni.yml
Это основной конфигурационный файл. Одна из особенностей Patroni, что мы можем задавать параметры для кластера из разных мест и patroni.yml — одно из них. Этот файл мы будем копировать в кастомный имейдж, так что любые изменения, внесенные в него, требуют ребилда образа.
Я в итоге пришел к идее, что буду хранить здесь только те параметры, которые наверняка почти никогда не буду трогать, поэтому складываю здесь только самые необходимые настройки. Ниже я привожу базовый конфиг. Если захочется, то можно добавить в него каких-то параметров, допустим, для движка Posgtres«а (например, max_connections и т.п.). Но для тестового стенда этого вполне достаточно.
patroni.yml
scope: patroni
namespace: /service/
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
postgresql:
use_pg_rewind: true
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication all all md5
- host all all all md5
zookeeper:
hosts:
- zoo1:2181
- zoo2:2181
- zoo3:2181
postgresql:
data_dir: /data/patroni
bin_dir: /usr/lib/postgresql/11/bin
pgpass: /tmp/pgpass
parameters:
unix_socket_directories: '.'
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Details
Важно указать Patroni путь к бинарным файлам Postgres«а. В моем случае, так как я использую Postgres 11, директория выглядит так:»/usr/lib/postgresql/11/bin».
В директории, внутри уже созданного контейнера, Patroni будет искать файлы Postgres«а. Без этой настройки скорее всего ничего не взлетит (по крайней мере у меня не взлетело). И также еще есть «data_dir» — это место в контейнере, где будут храниться данные. Позже мы сделаем mount этой директории к месту на локальном жестком диске, чтобы не потерять все полимеры, если кластер все же упадет безнадежно. Это добавит нам работы по созданию этих папок локально, но, по-моему, оно того стоит.
postgresql:
data_dir: /data/patroni
bin_dir: /usr/lib/postgresql/11/bin
Также я перечисляю все сервера Zookeeper«а в этом конфиг файле, чтобы потом передать информацию о них утилите patronictl. Стоит отметить, что если не указать их в patroni.yml, то мы останемся в итоге с нерабочим patronictl. Как вы видите, перечисляя сервера, я не пишу никакие IP, а использую их имена. Это та самая фича Docker Swarm«а о которой я рассказывал выше.
zookeeper:
hosts:
- zoo1:2181
- zoo2:2181
- zoo3:2181
patroni-entrypoint.sh
Из следующего файла подтягивается большая часть настроек в моей конфигурации. Это небольшой скрипт, который будет выполнен, когда контейнер сервиса окажется создан.
patroni-entrypoint.sh
#!/bin/sh
readonly CONTAINER_IP=$(hostname --ip-address)
readonly CONTAINER_API_ADDR="${CONTAINER_IP}:${PATRONI_API_CONNECT_PORT}"
readonly CONTAINER_POSTGRE_ADDR="${CONTAINER_IP}:5432"
export PATRONI_NAME="${PATRONI_NAME:-$(hostname)}"
export PATRONI_RESTAPI_CONNECT_ADDRESS="$CONTAINER_API_ADDR"
export PATRONI_RESTAPI_LISTEN="$CONTAINER_API_ADDR"
export PATRONI_POSTGRESQL_CONNECT_ADDRESS="$CONTAINER_POSTGRE_ADDR"
export PATRONI_POSTGRESQL_LISTEN="$CONTAINER_POSTGRE_ADDR"
export PATRONI_REPLICATION_USERNAME="$REPLICATION_NAME"
export PATRONI_REPLICATION_PASSWORD="$REPLICATION_PASS"
export PATRONI_SUPERUSER_USERNAME="$SU_NAME"
export PATRONI_SUPERUSER_PASSWORD="$SU_PASS"
export PATRONI_approle_PASSWORD="$POSTGRES_APP_ROLE_PASS"
export PATRONI_approle_OPTIONS="${PATRONI_admin_OPTIONS:-createdb, createrole}"
exec /usr/local/bin/patroni /etc/patroni.yml
Details. Важно!
На самом деле, основной смысл вообще делать такой скрипт заключается в том, что мы просто не сможем стартануть сервис с Patroni, не зная IP адрес host«а. И в том случае, когда host«ом оказывается Docker-контейнер, нам как-то нужно сначала узнать какой же IP этот контейнер получил, и только потом мы можем запустить Patroni. Эта потребность закрывается вот здесь:
readonly CONTAINER_IP=$(hostname --ip-address)
readonly CONTAINER_API_ADDR="${CONTAINER_IP}:${PATRONI_API_CONNECT_PORT}"
readonly CONTAINER_POSTGRE_ADDR="${CONTAINER_IP}:5432"
...
export PATRONI_RESTAPI_CONNECT_ADDRESS="$CONTAINER_API_ADDR"
export PATRONI_RESTAPI_LISTEN="$CONTAINER_API_ADDR"
export PATRONI_POSTGRESQL_CONNECT_ADDRESS="$CONTAINER_POSTGRE_ADDR"
Как я уже говорил раньше, параметры конфига Patroni можно передавать разными способами. В этом скрипте мы пользуемся тем, что один из таких способов — это «Environment configuration». «PATRONIRESTAPICONNECTADDRESS», «PATRONIRESTAPILISTEN», «PATRONIPOSTGRESQLCONNECTADDRESS» — специальные переменные среды, о которых Patroni знает заранее и которые будут считаны. И кстати, они переписывают локальные настройки из patroni.yml, так что be aware!
И еще момент. Документация Patroni не рекомендует использовать superuser«а для подключения к базе приложений. Т.е. нужно создать отдельного юзера, который мы будем использовать непосредственно для коннекта, а superuser«а и replicator«а трогать не будем совсем. Создать такого юзера можно также через переменную среды. Если хотите, чтобы юзер назывался как-то иначе чем «approle», просто замените в этой строке «approle» на что-то другое.
export PATRONI_approle_PASSWORD="$POSTGRES_APP_ROLE_PASS"
export PATRONI_approle_OPTIONS="${PATRONI_admin_OPTIONS:-createdb, createrole}"
И в последней строчке, когда всё уже готово к старту, мы делаем запуск Patroni сервиса с указанием откуда брать основной конфиг файл:
exec /usr/local/bin/patroni /etc/patroni.yml
Dockerfile
Dockerfile я решил сделать настолько простым, насколько это только возможно. Но этого вполне достаточно, чтобы сделать билд рабочего Docker-образа. Давайте глянем, что в нем все-таки происходит.
Dockerfile
FROM postgres:11
RUN apt-get update -y\
&& apt-get install python3 python3-pip -y\
&& pip3 install --upgrade setuptools\
&& pip3 install psycopg2-binary \
&& pip3 install patroni[zookeeper] \
&& mkdir /data/patroni -p \
&& chown postgres:postgres /data/patroni \
&& chmod 700 /data/patroni
COPY patroni.yml /etc/patroni.yml
COPY patroni-entrypoint.sh ./entrypoint.sh
USER postgres
ENTRYPOINT ["bin/sh", "/entrypoint.sh"]
Details
Одна из главных деталей здесь это директория, которую мы указываем создать внутри контейнера, а также ее владелец и его права. Позже, когда будем деплоить Patroni, нужно будет подобным же образом создать папки на локальной машине, куда мы сможем сделать mount этой директории из контейнера.
// владелец должен быть 'postgres', а mode 700
mkdir /data/patroni -p \
chown postgres:postgres /data/patroni \
chmod 700 /data/patroni
...
// устанавливаем в кач-ве активного юзера внутри контейнера
// юзера postgres
USER postgres
Файлы, которые мы создали, ранее копируются в имейдж в этих строчках:
COPY patroni.yml /etc/patroni.yml
COPY patroni-entrypoint.sh ./entrypoint.sh
И, как я уже упомянул ранее, мы хотим запустить этот скрипт сразу после создания контейнера:
ENTRYPOINT ["bin/sh", "/entrypoint.sh"]
Вот, пожалуй, и вся основная подготовка. Теперь мы готовы создать наш кастомный Patroni имейдж.
docker build -t patroni-test .
Самое время обсудить последний по списку, но не по важности файл для Patroni — compose yml.
Правильно написанный compose файл — важная часть общей картины. Разберем, что нам нужно иметь ввиду, настраивая конфигурацию.
docker-compose-patroni.yml
version: "3.4"
networks:
patroni_patroni:
external: true
services:
patroni1:
image: patroni-test
networks: [ patroni_patroni ]
ports:
- 5441:5432
- 8091:8091
hostname: patroni1
volumes:
- /patroni1:/data/patroni
environment:
PATRONI_API_CONNECT_PORT: 8091
REPLICATION_NAME: replicator
REPLICATION_PASS: replpass
SU_NAME: postgres
SU_PASS: supass
POSTGRES_APP_ROLE_PASS: appass
deploy:
replicas: 1
placement:
constraints: [node.hostname == floitet]
patroni2:
image: patroni-test
networks: [ patroni_patroni ]
ports:
- 5442:5432
- 8092:8091
hostname: patroni2
volumes:
- /patroni2:/data/patroni
environment:
PATRONI_API_CONNECT_PORT: 8091
REPLICATION_NAME: replicator
REPLICATION_PASS: replpass
SU_NAME: postgres
SU_PASS: supass
POSTGRES_APP_ROLE_PASS: appass
deploy:
replicas: 1
placement:
constraints: [node.hostname == floitet]
patroni3:
image: patroni-test
networks: [ patroni_patroni ]
ports:
- 5443:5432
- 8093:8091
hostname: patroni3
volumes:
- /patroni3:/data/patroni
environment:
PATRONI_API_CONNECT_PORT: 8091
REPLICATION_NAME: replicator
REPLICATION_PASS: replpass
SU_NAME: postgres
SU_PASS: supass
POSTGRES_APP_ROLE_PASS: appass
deploy:
replicas: 1
placement:
constraints: [node.hostname == floitet]
Details
Первое, о чем хочется сказать, это момент с external network, о котором говорилось ранее. Мы хотим разместить Patroni сервисы там же, где мы держим и сервисы Zookeeper. Таким образом мы сможем обращаться к сервисам по именам, и все имена: «zoo1′, «zoo2′, «zoo3′, — которые мы перечислили в patroni.yml, задавая сервера Zookeeper«а, будут работать, как надо.
networks:
patroni_patroni:
external: true
Нужно отметить, что у нас будут два end point«а: сама база данных и API. И для того и для другого требуется открыть порты:
ports:
- 5441:5432
- 8091:8091
...
environment:
PATRONI_API_CONNECT_PORT: 8091
// также нужно убедиться, что в PATRONI_API_CONNECT_PORT мы передаем
// тот же самый, который мы открываем для сервиса
Также, нам, конечно, нужно передать все переменные среды, которые мы заявили в entrypoint скрипте. Но и это еще не всё. Есть вопрос с директорией для mount«а, который мы тоже здесь решаем:
volumes:
- /patroni3:/data/patroni
Как видно из этой строки, ту директорию »/data/patroni», которая была создана в Dockerfile, мы монтируем к локальной директории. Так вот эту локальную директорию нам нужно создать. И не только создать, но и выставить правильного юзера и режим доступа, например так:
sudo mkdir /patroni3
sudo chown 999:999 /patroni3
sudo chmod 700 /patroni3
// 999 это дефолтный uid для юзера postgres
// эти шаги нужно повторить для каждой ноды Patroni
Мы наконец готовы деплоить Patroni кластер:
sudo docker stack deploy --compose-file docker-compose-patroni.yml patroni
После деплоя в логах сервиса мы должны увидеть что-то в таком ключе:
INFO: Lock owner: patroni3; I am patroni1
INFO: does not have lock
INFO: no action. i am a secondary and i am following a leader
Было бы печально, если бы мы могли проверить статус кластера и ноды только читая логи. Так что предлагаю коснуться немного способов проверки состояния кластера и начать с самого простого — patronictl. Для этого нужно сначала получить id любого контейнера Patroni:
sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a0090ce33a05 patroni-test:latest "bin/sh /entrypoint.…" 3 hours ago Up 3 hours 5432/tcp patroni_patroni1.1.tgjzpjyuip6ge8szz5lsf8kcq
...
И потом зайти в контейнер с помощью exec команды:
sudo docker exec -ti a0090ce33a05 /bin/bash
// при вызове команды мы должны передать имя кластера
// это параметр 'scope' в patroni.yml ('patroni' в нашем случае)
patronictl list patroni
// и тут ошибка...
Error: 'Can not find suitable configuration of distributed configuration store\nAvailable implementations: exhibitor, kubernetes, zookeeper'
Команда patronictl полагается на patroni.yml, чтобы получить информацию о серверах Zookeeper«а. Он не знает, где мы этот файл положили. Так что явно укажем ему путь:
patronictl -c /etc/patroni.yml list patroni
// and here is the nice output with the current states
+ Cluster: patroni (6893104757524385823) --+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+----------+-----------+---------+---------+----+-----------+
| patroni1 | 10.0.1.93 | Replica | running | 8 | 0 |
| patroni2 | 10.0.1.91 | Replica | running | 8 | 0 |
| patroni3 | 10.0.1.92 | Leader | running | 8 | |
+----------+-----------+---------+---------+----+-----------+
PostgreSQL Connection
Готово! Теперь мы можем подключаться к Postgres и что-то туда писать. Но делать это мы сможем только из нашей сети «patroni_patroni». Так что сначала создаем контейнер с подходящим образом, и потом из него уже выполняем команды:
docker run --rm -ti --network=patroni_patroni postgres:11 /bin/bash
// доступ к конкретной ноде
psql --host patroni3 --port 5432 -U approle -d postgres
// доступ к лидеру через haproxy
// так что нужно указать какую-либо через флаг '-d'
Вот мы и настроили сам кластер Patroni. Но наш сетап был бы неполным, если бы мы на этом и остановились. Есть ещё несколько важных моментов, которые мы должны закрыть, но об этом во второй части.
Все config файлы для первой части можно забрать отсюда.