Thank you for reading this post, don't forget to subscribe!
Кластер Postgresql, созданный штатными средствами не совсем удобен в эксплуатации — у него сложные механизмы отслеживания проблем с репликацией, нет возможности автоматического переключения с основной ноды на резервную и обратно, сложный процесс восстановления после падения. Приложение Patroni позволяет сделать процесс управления кластером Postgresql проще. Оно хранит данные кластера в базе типа key-value и ориентируется на работоспособность системы с помощью DCS (Distributed Control Service). В качестве таких систем для Patroni могут выступать:
Consul Hashicorp.
Kubernetes.
Zookeeper.
etcd.
В данной инструкции мы рассмотрим процесс установки и настройки Patroni с Consul.
Установка и запуск PostgreSQL на Ubuntu
рассмотрим вариант установки без подключения дополнительного репозитория, а также с его настройкой для возможности установки разных версий.
Версия репозитория
В зависимости от версии операционной системы, из коробки будет установлена своя версия СУБД. Если для нас версия не принципиальна, просто вводим команду:
apt install postgresql postgresql-contrib
Разные версии PostgreSQL
Для установки более свежей версии PostgreSQL на Ubuntu, необходимо подключить репозиторий:
echo "deb [arch=amd64] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/postgresql.list
Импортируем GPG ключ репозитория:
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
После чего обновляем список пакетов:
apt update
Устанавливаем postgres:
apt install postgresql-14 postgresql-contrib
* в данном примере будет установлен postgresql версии 14.
Готово. Мы уже можем использовать СУБД.
Тестовое подключение
Заходим в систему под учетной записью postgres:
su - postgres
Подключаемся к СУБД:
psql
Делаем тестовый запрос на получение списка таблиц:
=# \dt *
Мы должны увидеть что-то на подобие:
1 2 3 4 5 6 7 8 |
List of relations Schema | Name | Type | Owner ------------+-------------------------+-------+---------- pg_catalog | pg_aggregate | table | postgres pg_catalog | pg_am | table | postgres pg_catalog | pg_amop | table | postgres pg_catalog | pg_amproc | table | postgres pg_catalog | pg_attrdef | table | postgres |
Выходим из оболочки psql:
=# \q
Отключаемся от системы пользователем postgres:
exit
Установка и настройка кластера Consul Hashicorp на Linux Ubuntu
Подготовка сервера
Выполним предварительные операции по настройке наших серверов. На каждой ноде выполняем данные действия.
1. Установка дополнительных пакетов
Обновляем список пакетов:
apt update
Выполняем команду:
apt install unzip wget
* где:
- unzip — необходим для распаковки архивов.
- wget — утилита для загрузки файлов по сети.
2. Настраиваем время
Задаем часовой пояс:
timedatectl set-timezone Europe/Moscow
* в данном примере мы задаем московское время. Полный перечень возможных вариантов можно получить командой: timedatectl list-timezones.
Устанавливаем утилиту для синхронизации времени:
apt install chrony
Запускаем ее в качестве сервиса:
systemctl enable chrony
3. Настройка имени серверов
Для сервера консул важно, чтобы серверы были доступны по именам. Предположим, что наши серверы имеют следующие названия и IP-адреса:
- consul01.test.local (192.168.0.15)
- consul02.test.local (192.168.0.20)
- consul03.test.local (192.168.0.25)
Тогда вводим команды.
а) Сервер 1:
hostnamectl set-hostname consul01.test.local
б) Сервер 2:
hostnamectl set-hostname consul02.test.local
в) Сервер 3:
hostnamectl set-hostname consul03.test.local
Чтобы серверы могли обращаться друг к другу по именам, мы должны либо зарегистрировать их в локальной системе DNS, либо добавить на каждом сервере записи в файл hosts:
vi /etc/hosts
192.168.0.15 consul01.test.local consul01
192.168.0.20 consul02.test.local consul02
192.168.0.25 consul03.test.local consul03
Проверить доступность серверов по имени можно с помощью команды ping. На каждом из серверов введем:
ping consul01
ping consul02
ping consul03
4. Настройка безопасности
Открываем порты в брандмауэре:
iptables -I INPUT -p tcp --match multiport --dports 8300,8301,8302,8500,8600 -j ACCEPT
iptables -I INPUT -p udp --match multiport --dports 8301,8302,8600 -j ACCEPT
Для сохранения правил используем утилиту iptables-persistent:
apt install iptables-persistent
netfilter-persistent save
Установка и запуск Consul
Выполним установку компонентов, настройку кластера и его запуск.
Установка
Установка будет выполнена путем копирования бинарного файла. Для начала перейдем на страницу загрузки программного продукта и посмотрим его последнюю версию.
После подставим нужную версию в переменную (для нашего удобства):
export VER="1.12.2"
* на момент обновления инструкции, последняя версия была 1.12.2.
После загружаем архив с бинарником:
wget https://releases.hashicorp.com/consul/${VER}/consul_${VER}_linux_amd64.zip
И распаковываем его:
unzip consul_${VER}_linux_amd64.zip
Копируем полученных бинарник в каталог /usr/bin:
mv consul /usr/bin/
Проверяем, что приложение может запускаться в наших системах:
consul -v
Мы должны увидеть что-то на подобие:
Consul v1.12.2
Revision 19041f20
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
Можно переходить к настройке.
Настройка
Создаем учетную запись, от которой будет работать консул:
useradd -r -c 'Consul DCS service' consul
* в данном примере мы создадим учетную запись consul, которая будет системной. Также мы добавим небольшой комментарий.
Создаем каталоги для приложения консул:
mkdir -p /var/lib/consul /etc/consul.d
И выставим на них нужные права:
chown consul:consul /var/lib/consul /etc/consul.d
chmod 775 /var/lib/consul /etc/consul.d
* в данном примере мы указали, что владельцем данных каталогов будет созданная учетная запись consul. Права будут полные у владельца, остальные смогут читать данные.
Сгенерируем ключ для консула на любой из нод кластера:
consul keygen
Мы получим последовательность символов, например:
wHFWVHTstpfh08ZflUs4FD2FAMueraoCN2LyqmeLxV0=
Полученный ключ присваиваем в качестве значения переменной на всех нодах:
CONSUL_TOKEN=wHFWVHTstpfh08ZflUs4FD2FAMueraoCN2LyqmeLxV0=
* где wHFWVHTstpfh08ZflUs4FD2FAMueraoCN2LyqmeLxV0= нужно заменить на тот ключ, который был сгенерирован на вашем сервере.
Также на наших кластерных серверах создадим переменные с указанием всех нод:
CONSUL_SERVER1=consul01.test.local
CONSUL_SERVER2=consul02.test.local
CONSUL_SERVER3=consul03.test.local
Создаем конфигурационный файл для консула:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
cat <<EOF > /etc/consul.d/config.json { "bind_addr": "0.0.0.0", "bootstrap_expect": 3, "client_addr": "0.0.0.0", "datacenter": "dc1", "node_name": "$(hostname)", "data_dir": "/var/lib/consul", "domain": "consul", "enable_local_script_checks": true, "dns_config": { "enable_truncate": true, "only_passing": true }, "enable_syslog": true, "encrypt": "${CONSUL_TOKEN}", "leave_on_terminate": true, "log_level": "INFO", "rejoin_after_leave": true, "retry_join": [ "${CONSUL_SERVER1}", "${CONSUL_SERVER2}", "${CONSUL_SERVER3}" ], "server": true, "start_join": [ "${CONSUL_SERVER1}", "${CONSUL_SERVER2}", "${CONSUL_SERVER3}" ], "ui_config": { "enabled": true } } EOF |
* мы используем команду для создания конфигурационного файла с указанием переменных, созданных ранее. Это делается для нашего удобства работы, чтобы вводить одну и ту же команду на всех нодах.
** где:
- bind_addr — адрес, на котором будет слушать наш сервер консул. Это может быть IP любого из наших сетевых интерфейсов или, как в данном примере, все.
- bootstrap_expect — минимально ожидаемое количество серверов в кластере. Consul будет ждать, пока данное число серверов не станет доступным, только после этого загрузит кластер.
- client_addr — адрес, к которому будут привязаны клиентские интерфейсы.
- datacenter — привязка сервера к конкретному датацентру. Нужен для логического разделения. Серверы с одинаковым датацентром должны находиться в одной локальной сети.
- node_name — имя ноды, на которой запускается сервис.
- data_dir — каталог для хранения данных.
- domain — домен, в котором будет зарегистрирован сервис.
- enable_local_script_checks — разрешает на агенте проверку работоспособности сервисов.
- dns_config — параметры для настройки DNS.
- enable_syslog — разрешение на ведение лога.
- encrypt — ключ для шифрования сетевого трафика. В качестве значения используем сгенерированный ранее. Должен быть одинаковый на всех участниках кластера.
- leave_on_terminate — при получении сигнала на остановку процесса консула, корректно отключать ноду от кластера.
- log_level — минимальный уровень события для отображения в логе. Возможны варианты "trace", "debug", "info", "warn", and "err".
- rejoin_after_leave — по умолчанию, нода покидающая кластер не присоединяется к нему автоматически. Данная опция позволяет управлять данным поведением.
- retry_join — перечисляем узлы, к которым можно присоединять кластер. Процесс будет повторяться, пока не завершиться успешно.
- server — режим работы сервера.
- start_join — список узлов кластера, к которым пробуем присоединиться при загрузке сервера.
- ui_config — конфигурация для графического веб-интерфейса.
Посмотреть итоговую конфигурацию можно командой:
cat /etc/consul.d/config.json
Проверяем корректность конфигурационного файла:
consul validate /etc/consul.d
Мы должны увидеть:
…
Configuration is valid!
* также мы можем увидеть некоторые предупреждения. Например:
- Node name "consul01.test.local" will not be discoverable via DNS due to invalid characters. Появляется в случае, если мы разрешаем имя сервера в адрес не с помощью внутренней DNS, а при помощи файла hosts.
- bootstrap_expect > 0: expecting 3 servers. Отображается, если у нас еще настроены не все участники кластера.
В завершение настройки создадим юнит в systemd для возможности автоматического запуска сервиса:
vi /etc/systemd/system/consul.service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[Unit] Description=Consul Service Discovery Agent Documentation=https://www.consul.io/ After=network-online.target Wants=network-online.target [Service] Type=simple User=consul Group=consul ExecStart=/usr/bin/consul agent \ -config-dir=/etc/consul.d ExecReload=/bin/kill -HUP $MAINPID KillSignal=SIGINT TimeoutStopSec=5 Restart=on-failure SyslogIdentifier=consul [Install] WantedBy=multi-user.target |
Перечитываем конфигурацию systemd:
systemctl daemon-reload
Система установлена, настроена и готова к запуску.
Запуск и проверка
Стартуем наш сервис:
systemctl start consul
Также разрешаем автоматический старт при запуске сервера:
systemctl enable consul
Смотрим текущее состояние работы сервиса:
systemctl status consul
Мы должны увидеть состояние:
…
Active: active (running) since …
…
Состояние нод кластера мы можем посмотреть командой:
consul members
А данной командой можно увидеть дополнительную информацию:
consul members -detailed
Также у нас должен быть доступен веб-интерфейс по адресу:
http://<IP-адрес любого сервера консул>:8500. Перейдя по нему, мы должны увидеть страницу со статусом нашего кластера.
Аутентификация
При входе на веб-интерфейс или работе из командной строки, мы заметим, что система позволяет выполнять операции без запроса идентификационных данных. Если нам нужно обезопасить систему, настроим ACL и донастроим сервис.
Включение ACL
Открываем конфигурационные файлы на всех нодах нашего кластера:
vi /etc/consul.d/config.json
Добавляем строки:
1 2 3 4 5 6 7 8 9 |
{ ... "ui_config": { "enabled": true }, "acl": { "enabled": true, "default_policy": "deny", "enable_token_persistence": true } } |
* обратите внимание, что строка "ui_config": { "enabled": true }, у нас уже была в конфигурационном файле, но мы добавили запятую в конце, так как это критично для конфига в формате json. Наша добавленная настройка acl запрещает по умолчанию доступ и требует ввода токена безопасности.
Проверяем корректность внесенных настроек:
consul validate /etc/consul.d/config.json
Если ошибок нет, то перезапускаем сервис:
systemctl restart consul
Заходим на веб-интерфейс — мы обнаружим, что в правом верхнем углу появилась ссылка для входа в систему:
Работа с токенами
Получаем токен командой:
consul acl bootstrap
* если команда нам вернула ошибку Failed ACL bootstrapping: Unexpected response code: 500 (The ACL system is currently in legacy mode.), то значит, что мы не на всех нодах применили новую настройку. Проверяем, на всех ли серверах кластера внесены соответствующие изменения по добавлению ACL.
Мы получим ответ на подобие:
1 2 3 4 5 6 7 |
AccessorID: af5eaac1-4f0b-d46a-58ba-64ec857dfc4c SecretID: 59ac7fa8-dca6-e066-ff33-0bf9bb6f466a Description: Bootstrap Token (Global Management) Local: false Create Time: 2021-09-01 16:28:58.710545082 +0300 MSK Policies: 00000000-0000-0000-0000-000000000001 - global-management |
В данном примере 59ac7fa8-dca6-e066-ff33-0bf9bb6f466a — нужный нам для аутентификации SecretID. Возвращаемся в веб-интерфейсу и пробуем войти в систему с использованием данного кода.
Если нам нужно удалить данный токен, вводим команду:
Для работы из командной строки также необходимо авторизоваться. Для этого создаем переменную в системном окружении:
export CONSUL_HTTP_TOKEN=59ac7fa8-dca6-e066-ff33-0bf9bb6f466a
* где 59ac7fa8-dca6-e066-ff33-0bf9bb6f466a, полученный нами SecretID.
Создать новый токен можно командой:
consul acl token create -policy-name global-management
Чтобы увидеть список токенов, вводим:
consul acl token list
Если нам понадобиться удалить токен, мы должны использовать его AccessorID:
consul acl token delete -id 54b5f2bb-1a57-3884-f0ea-1284f84186f5
* где будет удален токен с AccessorID 54b5f2bb-1a57-3884-f0ea-1284f84186f5.
Настройка политики для DNS
После включения ACL наша система перестанет отвечать на запросы DNS. Это связано с политикой блокировки по умолчанию. Для разрешения запросов мы должны создать политику с разрешением данных запросов, получить для ее токен и применить данный токен на всех нодах консула.
На одной из нод создаем файл с политикой:
cd /etc/consul.d/
vi dns-request-policy.txt
1 2 3 4 5 6 |
node_prefix "" { policy = "read" } service_prefix "" { policy = "read" } |
Создаем политику в консуле:
consul acl policy create -name "dns-requests" -rules @dns-request-policy.txt
Создаем токен безопасности на основе политики:
consul acl token create -description "Token for DNS Requests" -policy-name dns-requests
Мы должны увидеть что-то на подобие:
1 2 3 4 5 6 7 |
AccessorID: b53741e2-7663-eag6-fd67-a64dbd32feb5 SecretID: 42bd65e5-42a5-356b-a81b-60eff20f657 Description: Token for DNS Requests Local: false Create Time: 2021-09-02 12:37:42.342511655 +0300 MSK Policies: 89e42b6b-bbec-5263-bc7b-60f3a604a0d6 - dns-requests |
* где SecretID — это нужный нам токен.
На всех компьютерах с consul авторизовываемся:
export CONSUL_HTTP_TOKEN=59ac7fa8-dca6-e066-ff33-0bf9bb6f466a
* где 59ac7fa8-dca6-e066-ff33-0bf9bb6f466a — ранее созданный токен для получения доступа к управлению консулом.
И вводим:
consul acl set-agent-token default 42bd65e5-42a5-356b-a81b-60eff20f657
* где 42bd65e5-42a5-356b-a81b-60eff20f657 — тот SecretID, который мы получили на основе политики, разрешающей запросы DNS.
==================================================================
Предварительная настройка
Прежде чем перейти к настройке patroni и кластера, подготовим наши серверы баз данных.
Настройка брандмауэра
Если на наших серверах используется брандмауэр, то нам нужно открыть порт 5432, по которому работает postgresql, а также порт 8008 для patroni.
Вводим команды:
iptables -I INPUT -p tcp --dport 5432 -j ACCEPT
iptables -I INPUT -p tcp --dport 8008 -j ACCEPT
Для сохранения правил вводим:
apt install iptables-persistent
netfilter-persistent save
Подготовка СУБД
Patroni самостоятельно инициализирует базу и настройки, поэтому наш каталог с данными Postgresql должен быть чист, а сервис postgresql находится в выключенном состоянии.
Если на вашем сервере баз данных есть данные, действия ниже приведут к их потере. В этом случае стоит развернуть кластер на других серверах и перенести данные.
На наших серверах СУБД смотрим путь до каталога с данными:
su - postgres -c "psql -c 'SHOW data_directory;'"
В моем случае для обоих серверов путь был /var/lib/postgresql/14/main — сохраняем путь. Скоро он нам понадобиться.
Останавливаем сервис postgres и запрещаем его автозапуск:
systemctl disable postgresql --now
Удаляем содержимое каталога с данными, путь до которого нам вернула команда SHOW data_directory:
rm -rf /var/lib/postgresql/14/main/*
Готово. Можно переходить к установке и настройке Patroni.
Установка и настройка Patroni
Данное приложение разработано на Python и требует установки как последнего, так и некоторых его компонентов.
Выполняем команду:
apt install python3 python3-pip python3-psycopg2
* в нашем примере мы будем работать с python версии 3. Также нам нужна библиотека для работы с postgresql python3-psycopg2.
Теперь ставим сам patroni, как приложение python:
pip3 install patroni[consul]
Установка завершена. Переходим к настройке.
Создадим каталог для хранения конфигурации:
mkdir /etc/patroni
И сам конфигурационный файл:
vi /etc/patroni/patroni.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
name: postgres01.test.local scope: pgdb watchdog: mode: off consul: host: "localhost:8500" register_service: true #token: <consul-acl-token> restapi: listen: 0.0.0.0:8008 connect_address: "192.168.0.11:8008" auth: 'patrest:password' bootstrap: dcs: ttl: 30 loop_wait: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true use_slots: true parameters: archive_mode: "on" wal_level: hot_standby max_wal_senders: 10 wal_keep_segments: 8 archive_timeout: 1800s max_replication_slots: 5 hot_standby: "on" wal_log_hints: "on" initdb: - encoding: UTF8 - data-checksums pg_hba: - local all all trust - host replication replicator 192.168.0.0/24 md5 - host replication replicator 127.0.0.1/32 trust - host all all 0.0.0.0/0 md5 postgresql: pgpass: /var/lib/postgresql/14/.pgpass listen: 0.0.0.0:5432 connect_address: "192.168.0.11:5432" data_dir: /var/lib/postgresql/14/main/ bin_dir: /usr/lib/postgresql/14/bin/ pg_rewind: username: postgres password: password pg_hba: - local all all trust - host replication replicator 192.168.0.0/24 md5 - host replication replicator 127.0.0.1/32 trust - host all all 0.0.0.0/0 md5 replication: username: replicator password: password superuser: username: postgres password: password |
* данные конфигурационные файлы на разных серверах будут немного различаться. Опишем подробнее опции, на которые нам нужно обратить внимание:
- name — имя узла, на котором настраивается данный конфиг.
- scope — имя кластера. Его мы будем использовать при обращении к ресурсу, а также под этим именем будет зарегистрирован сервис в consul.
- consul - token — если наш кластер consul использует ACL, необходимо указать токен.
- restapi - connect_address — адрес на настраиваемом сервере, на который будут приходить подключения к patroni.
- restapi - auth — логин и пароль для аутентификации на интерфейсе API.
- pg_hba — блок конфигурации pg_hba для разрешения подключения к СУБД и ее базам. Необходимо обратить внимание на подсеть для строки host replication replicator. Она должна соответствовать той, которая используется в вашей инфраструктуре.
- postgresql - pgpass — путь до файла, который создаст патрони. В нем будет храниться пароль для подключения к postgresql.
- postgresql - connect_address — адрес и порт, которые будут использоваться для подключения к СУДБ.
- postgresql - data_dir — путь до файлов с данными базы.
- postgresql - bin_dir — путь до бинарников postgresql.
- pg_rewind, replication, superuser — логины и пароли, которые будут созданы для базы.
Создадим юнит в systemd для patroni:
vi /lib/systemd/system/patroni.service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[Unit] Description=Patroni service After=syslog.target network.target [Service] Type=simple User=postgres Group=postgres ExecStart=/usr/local/bin/patroni /etc/patroni/patroni.yml ExecReload=/bin/kill -s HUP $MAINPID KillMode=process TimeoutSec=30 Restart=no [Install] WantedBy=multi-user.target |
* обратите внимание, что в официальной документации предлагается не перезапускать автоматически службу (Restart=no). Это дает возможность разобраться в причине падения базы.
Перечитываем конфигурацию systemd:
systemctl daemon-reload
Разрешаем автозапуск и стартуем сервис:
systemctl enable patroni --now
Проверяем статусы сервиса на обоих серверах:
systemctl status patroni
Посмотреть список нод
patronictl -c /etc/patroni/patroni.yml list pgdb
* где pgdb — имя нашей базы (указана с использованием директивы scope в конфигурационном файле patroni).
Мы должны увидеть что-то на подобие:
1 2 3 4 5 6 |
+------------------------+--------------+---------+---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | + Cluster: pgdb (7126124696482454785) --+---------+---------+----+-----------+ | postgres01.test.local | 192.168.0.11 | Leader | running | 1 | | | postgres02.test.local | 192.168.0.12 | Replica | running | 1 | 0 | +------------------------+--------------+---------+---------+----+-----------+ |
Можно проверить, что сервер консул разрешает имена кластера:
nslookup -port=8600 master.pgdb.service.consul 127.0.0.1
nslookup -port=8600 replica.pgdb.service.consul 127.0.0.1
* где consul — настроенный в consul домен.
Команды нам должны вернуть адреса соответствующих серверов — лидера и реплики.
Наш кластер в рабочем состоянии.