К данному времени вы проделали большую работу по изучению СУБД PostgreSQL. Но, тем не менее, вам все еще не хватает одного критически важного элемента для действительно высокой доступности, а именно - удобного и эффективного управления созданным кластером PostgreSQL.
К сожалению, перед тем как перейти к полноценной автоматизации, вам все еще нужно ручное вмешательство. Но это не страшно, ибо ребята из компании Zalando возглавили разработку решения, позволяющего максимально уменьшить работу руками с настройкой и обслуживанием большого количества серверов с базами данных под управлением СУБД PostgreSQL. Имя этого решения - Patroni.
Почему именно Patroni?
Patroni — это демон написанный на языке python, позволяющий автоматически обслуживать кластеры PostgreSQL с различными типами репликации, и автоматическим переключением ролей. Его основная фитча в том, что для поддержания актуальности кластера и выборов мастера используются распределенные хранилища DCS (Distributed Сontrol System).
Значит кластер легко интегрируется практически в любую систему, всегда можно выяснить кто в этот момент мастер, а также узнать статус всех серверов спомощью запросов в DCS, или напрямую к Patroni через API (HTTP).
Как мы узнали из теории построения кластеров баз данных, только один сервер должен быть доступен для записи. В рамках Patroni мы назовем его "лидером". Остальные сервера будут "репликами" нашего лидера. Для выполнения задания вам потребуется три сервера с установленным PostgreSQL и Patroni на борту, а также для хранения текущего состояния кластера - какое-нибудь "key-value" хранилище.
Есть 3 наиболее продвинутых решения для предоставления такого хранилища (DCS):
Мы выберем etcd от CNCF (Cloud Native Computing Foundation), ввиду того, что это решение больше подходит именно для хранения данных, нежели для решения других задач, которые решают DCS (ZooKeeper нужен больше для работы с очередями, а Consul, в свою очередь, хорош как service-discovery). По правилам построения DCS, вам нужно иметь не менее 3 нод для бесперебойной работы. Чтобы собрать их воедино, нам понадобится надежный уровень передачи сообщений. Предприимчивые студенты Стэнфордского университета придумали алгоритм обмена данными между нодами, который они назвали Raft. Существует множество теорий относительно того, как это работает, но в конечном итоге пара ключ/значение, появляющаяся на входе в кластер etcd (на cлое Raft), размещается внутренним согласованием на всех серверах. Это очень важно, потому что мы будем использовать etcd для хранения местоположения лидера кластера PostgreSQL. Если у нас есть служба, которая может подключаться к etcd, то это позволит проводить анализ данных и быстро реагировать на инцеденты. А значит, любой из наших серверов с PostgreSQL в случае форс-мажера немедленно узнает местоположение нового лидера. Это делает простым процесс смены источника репликации при потере лидера (кворум из N-1 нод).
Также вам потребуется система, позволяющая правильно направить и распределить нагрузку между серверами, обеспечивая оперативное переключение между ними. Для этого вы будете использовать HAProxy, но об этом позже.
В итоге у вас получится такая схема:
Такое расположение серверов обеспечивает надежность хранения данных в кластере. Стоит заметить, что на схеме только один экземпляр HAProxy. Это сделано для упрощения схемы, но, для обеспечения высокой доступности, их должно быть не менее двух (в "Облаках" эти функции выполняют специальные балансировщики нагрузки, например AWS ELB).
В рамках кластера СУБД PostgreSQL текущая схема будет иметь следующие возможности:
Теперь поподробнее о Patroni.
Patroni - это клей в нашей схеме. Он действует как главный координатор и выполняет несколько ролей:
Patroni повторяет выше указанные шаги каждые несколько секунд на каждом сервере, где он установлен. Как следствие, некоторые сбои могут запустить цепную реакцию, подобную гонке, когда несколько реплик пытаются стать новыми лидерами. Кластер etcd (Слой Raft) гарантирует, что только один выиграет в этой гонке, а Patroni позаботится обо всем остальном.
Это также позволяет каждому серверу PostgreSQL работать независимо, поэтому не существует единой точки отказа. Поскольку реплики параллельно перенаправляются на нового лидера, весь кластер превращается в самовосстанавливающийся.
Patroni, etcd и HAProxy имеют ряд зависимостей, необходимых для их бесперебойной работы. Большинство из них легко получить, поэтому объем работы будет относительно минимальным.
Для того, чтобы Patroni мог достоверно определить первичный экземпляра PostgreSQL, необходимо распределенное хранилище ключей (ключ - значение) etcd.
Разработчики etcd, разрабатывали его в основном для работы в контейнерах (в безымянных виртуальных контейнерах). Нам будет достаточно загрузить его и разместить некоторые двоичные файлы в соответствующих местах. Стоит отметить, что описанный метод не является идеальной установкой с надежными файлами конфигурации и другими ожидаемыми компонентами, но это легко исправить, если мы решим полагаться на etcd в долгосрочной перспективе.
К сожалению etcd не является широко распространенным пакетом во многих дистрибутивах Linux. Проект быстро развивается, поэтому мы рекомендуем получить последнюю стабильную версию, доступную по этому URL-адресу:
https://github.com/etcd-io/etcd/releases
Мы используем etcd v3.4.14 в качестве номера версии в наших инструкциях, не беспокойтесь, если используемая вами версия немного отличается.
Напомним, что нам понадобится как минимум три сервера PostgreSQL. В демонстрационных целях мы предположим, что они названы pg1, pg2 и pg3.
Выполните следующие действия на всех трех серверах, Мы сделаем уточнение конкретного сервера, когда это будет необходимо.
2.0 Установите PostgreSQL. Напомним как это делается на примере ubuntu:
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
sudo apt-get -y install postgresql
2.1 Загрузите актуальную версию etcd с помощью скрипта etcd.sh.
cat etcd.sh
ETCD_VER=v3.4.14
# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test
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
/tmp/etcd-download-test/etcd --version
/tmp/etcd-download-test/etcdctl version
# chmod +x etcd.sh
# ./etcd.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 16.5M 100 16.5M 0 0 2115k 0 0:00:08 0:00:08 --:--:-- 3776k
etcd Version: 3.4.14
Git SHA: 8a03d2e96
Go Version: go1.12.17
Go OS/Arch: linux/amd64
etcdctl version: 3.4.14
API version: 3.4
2.2 Перенесите исполняемый файл etcd от имени пользователя с полномочиями root в /usr/local/bin:
sudo cp /tmp/etcd-download-test/etcd* /usr/local/bin
2.3 Создайте каталог хранилища для etcd с помощью команд как пользователь root:
# sudo mkdir -p /db/etcd
# sudo chown postgres:postgres /db/etcd
2.3 Создайте файл с именем /etc/etcd.conf на сервере pg1 со следующим содержимым:
#----------------- etcd.conf.yml.sample
# Файл конфигурации для сервера etcd.
# Читабельное имя экземпляра.
name: 'pg1'
# Необходимо создать каталог '/data'
# Путь к каталогу данных.
data-dir: '/db/etcd'
# Путь к каталогу wal архивов.
wal-dir: '/db/etcd/wal'
# Количество транзакций запуска моментального снимка на диск.
snapshot-count: 10000
# Время (в миллисекундах) интервала heartbeat.
heartbeat-interval: 100
# Время (в миллисекундах) для выбора до тайм-аута.
election-timeout: 1000
# Подавать сигнал тревоги, когда размер серверной части превышает заданную квоту. 0 означает использование
# квоты по умолчанию.
quota-backend-bytes: 0
# Список разделенных запятыми URL-адресов для прослушивания однорангового трафика.
listen-peer-urls: http://192.168.88.170:2380,http://127.0.0.1:2380
# Список разделенных запятыми URL-адресов для прослушивания клиентского трафика.
listen-client-urls: http://192.168.88.170:2379,http://127.0.0.1:2379
# Максимальное количество файлов моментальных снимков для сохранения (0 не ограничено).
max-snapshots: 5
# Максимальное количество файлов wal для сохранения (0 не ограничено).
max-wals: 5
# Разделенный запятыми белый список источников для CORS (совместное использование ресурсов между источниками).
cors:
# Список одноранговых URL-адресов экземпляра для для внешнего взаимодейстия с остальной частью кластера.
# URL-адреса должны быть списком, разделенным запятыми..
initial-advertise-peer-urls: http://192.168.88.170:2380
# Список клиентских URL-адресов этого участника для внешнего взаимодейстия.
# URL-адреса должны быть списком, разделенным запятыми..
advertise-client-urls: http://192.168.88.170:2379
# URL-адрес обнаружения, используемый для начальной загрузки кластера.
discovery:
# Допустимые значения включают 'exit', 'proxy'
discovery-fallback: 'proxy'
# Прокси-сервер HTTP для использования для трафика к службе обнаружения.
discovery-proxy:
# Домен DNS, используемый для начальной загрузки кластера.
discovery-srv:
# Начальная конфигурация кластера для начальной загрузки.
initial-cluster: 'pg1=http://192.168.88.170:2380,pg2=http://192.168.88.169:2380,pg3=http://192.168.88.168:2380'
# Начальный токен кластера для кластера etcd во время начальной загрузки.
initial-cluster-token: 'etcd-cluster-rebrain-token'
# Начальное состояние кластера («новый» или «существующий»).
initial-cluster-state: 'new'
# Отклонять запросы на изменение конфигурации, которые могут привести к потере кворума.
strict-reconfig-check: false
# Принимать запросы клиентов etcd V2
enable-v2: true
# Включить данные профилирования во время выполнения через HTTP-сервер
enable-pprof: true
# Допустимые значения включают 'on', 'readonly', 'off'
proxy: 'off'
# Время (в миллисекундах), когда конечная точка будет находиться в состоянии сбоя.
proxy-failure-wait: 5000
# Время (в миллисекундах) интервала обновления конечных точек.
proxy-refresh-interval: 30000
# Время (в миллисекундах) интервала обновления конечных точек.
proxy-dial-timeout: 1000
# Время (в миллисекундах) для таймаута записи.
proxy-write-timeout: 5000
# Время (в миллисекундах) от чтения до тайм-аута.
proxy-read-timeout: 0
client-transport-security:
# Path to the client server TLS cert file.
cert-file:
# Path to the client server TLS key file.
key-file:
# Enable client cert authentication.
client-cert-auth: false
# Path to the client server TLS trusted CA cert file.
trusted-ca-file:
# Client TLS using generated certificates
auto-tls: false
peer-transport-security:
# Path to the peer server TLS cert file.
cert-file:
# Path to the peer server TLS key file.
key-file:
# Enable peer client cert authentication.
client-cert-auth: false
# Path to the peer server TLS trusted CA cert file.
trusted-ca-file:
# Peer TLS using generated certificates.
auto-tls: false
# Enable debug-level logging for etcd.
debug: false
logger: zap
# Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd.
log-outputs: [stderr]
# Force to create a new one member cluster.
force-new-cluster: false
auto-compaction-mode: periodic
auto-compaction-retention: "1"
#---------------------------------
Вместо имен pg1,2,3 можно также указать ip адреса
Ниже представлено краткое разъяснение каждого параметра:
2.5.1 Перед запуском etcd, на каждом хосте укажем актуальное имя и добавим в hosts адреса нод кластера.
Пример pg1:
sudo hostnamectl set-hostname pg1 --static
sudo hostname pg1
sudo tee -a /etc/hosts<<EOF
192.168.88.170 pg1
192.168.88.169 pg2
192.168.88.168 pg3
EOF
2.6 Запустите демон etcd, выполнив эту команду от имени пользователя postgres:
etcd --config-file /etc/etcd.conf \
&>/var/log/postgresql/etcd.log &
Обратите внимание на вывод в логах
pkg/fileutil: check file permission: directory "/db/etcd" exist, but the permission is "drwxr-xr-x". The recommended permission is "-rwx------" to prevent possible unprivileged access to the data.
Поэтому
chmod -R 700 /db/etcd/
2.7 Проверяем кто мастер (тот кто true): master is 192.168.88.170:2379
$ etcdctl --endpoints="192.168.88.170:2379,192.168.88.169:2379,192.168.88.168:2379" endpoint status
192.168.88.170:2379, 41e2654631d30b6a, 3.4.14, 20 kB, true, false, 188, 9, 9,
192.168.88.169:2379, 3c2a2dee0e1d51d, 3.4.14, 25 kB, false, false, 188, 9, 9,
192.168.88.168:2379, 8e6dd942b6114814, 3.4.14, 20 kB, false, false, 188, 9, 9,
2.8 Как пользователь postgres на pg2 и pg3 запишем в кластер несколько пар key-value и проверим их:
pg2:
ETCDCTL_API=3 etcdctl put name1 batman
ETCDCTL_API=3 etcdctl put name2 ironman
$ ETCDCTL_API=3 etcdctl put name1 batman
OK
$ ETCDCTL_API=3 etcdctl put name2 ironman
OK
pg3:
ETCDCTL_API=3 etcdctl put name3 superman
ETCDCTL_API=3 etcdctl put name4 spiderman
$ ETCDCTL_API=3 etcdctl put name3 superman
OK
$ ETCDCTL_API=3 etcdctl put name4 spiderman
OK
2.9 Как пользователь postgres на pg1 попробуем получить занчение имен name1,2,3,4 с помощью команды:
postgres@pg1:/root$ ETCDCTL_API=3 etcdctl get name1
name1
batman
postgres@pg1:/root$ ETCDCTL_API=3 etcdctl get name2
name2
ironman
postgres@pg1:/root$ ETCDCTL_API=3 etcdctl get name3
name3
superman
postgres@pg1:/root$ ETCDCTL_API=3 etcdctl get name4
name4
spiderman
Мы начали с загрузки и установки etcd, так что у нас есть распределенный коммуникационный уровень для Patroni. Загружаемый файл также должен содержать документацию, но нам нужно только установить etcd и etcdctl. Эти две утилиты командной строки либо запускают etcd, либо отправляют ему произвольные инструкции во время его работы.
Подобно PostgreSQL, etcd также использует журнал write-ahead для обеспечения надежности данных. Следовательно, нам нужно место для хранения этих данных WAL. По умолчанию etcd создаст подкаталог, в котором он был запущен, у нас это каталог /db/etcd/wal.
Теперь мы должны настроить etcd на всех серверах PostgreSQL, составляющих кластер Patroni. Мы начинаем с наименования узла с помощью параметра name, а затем определяем каталог WAL, который мы обсуждали ранее, с параметром data-dir.
Сервис etcd поддерживает одноранговую сеть, в которой узлы могут общаться между собой. По умолчанию эта сеть работает на порту 2380 на каждом узле, где работает etcd, но мы хотим явно указать имя хоста, чтобы гарантировать, что мы можем принимать внешние соединения. Параметр initial-Advertise-peer-urls определяет имя и порт, которые другие узлы etcd должны использовать при связи с этой системой. Аналогичным образом, параметр listen-peer-urls обеспечивает аналогичное поведение, определяя, какой хост и порт нужно отслеживать для соединений, поэтому мы используем одно и то же значение для обоих.
Помимо внутренних коммуникаций, клиенты обычно подключаются к etcd через порт 2379 для хранения и получения пар key/value. Установив параметр listen-client-urls для прослушивания как имени узла, так и localhost, мы гарантируем, что Patroni может устанавливать значения локально, и любой узел в кластере также может связываться с etcd в случае, если их локальная служба etcd недоступна. Как и в случае с одноранговым объявлением, каждый узел объявляет о себе с помощью значения параметра Advertise-client-urls, поэтому мы используем имя узла для внешней связи.
Наконец, мы можем запустить сам сервис etcd и перенаправить его вывод в файл журнала. Обычно etcd работает явно с помощью флагов командной строки или переменных среды, но и то, и другое несколько неудобно по сравнению со стабильностью файла конфигурации. Таким образом, мы устанавливаем флаг --config-file для нашего файла /etc/etcd.conf, чтобы предотвратить такое поведение.
Чтобы доказать, что все прошло, как ожидалось, и что ключи, установленные в одном узле, доступны на всех узлах, мы использовали etcdctl для установки значения. Причина, по которой мы также установили для переменной среды ETCDCTL_API значение 3, заключается в том, что etcdctl по умолчанию совместим только с API версии 2. Мы хотели специально продемонстрировать, что параметр get может получить целый ряд ключей, если мы того пожелаем.
Многие дистрибутивы Linux переходят на systemd в качестве механизма управления службами. Если бы мы хотели управлять etcd таким образом, мы бы создали файл с именем etcd.service в каталоге /lib/systemd/system со следующим содержимым:
[Unit]
Description=etcd key-value store
Documentation=https://github.com/coreos/etcd
After=network.target
[Service]
User=postgres
Type=notify
ExecStart=/usr/local/bin/etcd --config-file /etc/etcd.conf
Restart=always
RestartSec=10s
LimitNOFILE=40000
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable etcd
Тогда мы могли бы запускать или останавливать etcd с помощью команд systemctl:
sudo systemctl start etcd
sudo systemctl stop etcd
И вывод журнала будет доступен через команду journalctl:
journalctl -u etcd.service
Успех установки зависит от нескольких потенциально дополнительных пакетов, которые отсутствуют во многих дистрибутивах Linux. В системах на базе Red Hat необходимо установить пакет EPEL для соответствующей платформы Red Hat со следующего URL-адреса:
https://fedoraproject.org/wiki/EPEL
Пользователям дистрибутивов на основе Debian должно быть достаточно следовать инструкциям ниже.
Для базовой установки нам понадобится как минимум три сервера PostgreSQL. В демонстрационных целях мы предположим, что они названы pg1, pg2 и pg3.
Выполните следующие действия на всех трех серверах:
sudo apt-get install python-psycopg2 python-pip python-yaml
1.1 В системах на базе Red Hat необходимо будет заменить эту команду на yum:
sudo yum install python-psycopg2 python-pip PyYAML
Мы начинаем с установки нескольких популярных библиотек (python-psycopg2, python-pip, python-yaml). Первым шагом идет установка python-psycopg2 - адаптер базы данных PostgreSQL. Patroni использует его для подключения к Postgres для различных операций, и, поскольку он широко используется, он уже содержится в стандартных дистрибутивах.
Затем мы устанавливаем pip, специальную программу установки для Python, которая может загружать и устанавливать пакеты Python из индекса пакетов Python. Это очень похоже на сеть расширений Postgres, но для популярных пакетов Python. Он понадобится нам в дальнейшем для установки Patroni.
Последний на очереди - YAML (язык разметки). Это формат, который некоторые проекты используют для определения файлов конфигурации. Patroni оказался среди этих проектов. API Python, который взаимодействует с этими файлами, на самом деле называется PyYAML, но системы на основе Debian переименовывают его в python-yaml в соответствии с выбранной схемой именования. Системы Red-Hat обычно используют указанное имя пакета.
Установив все эти пакеты, мы сможем довольно легко поставить остальную часть стека.
Patroni - это основной координирующий компонент нашего стека. Как видно из схемы выше, он в той или иной степени задействован в каждом элементе стека. Хотя он связывает все элементы стека вместе, мы приступаем к его установки только сейчас, потому что он тесно интегрируется с etcd key-value и PostgreSQL.
Если сервер PostgreSQL уже запущен, Patroni примет его. Если нет, Patroni создаст новый экземпляр в зависимости от того, как он настроен. Мы уже установили, что хранилище key-value распределяет одну и ту же информацию по всему кластеру, поэтому первый установленный сервер также становится основным узлом для кластера. Любой последующий экземпляр Patroni запускается как копия или преобразуется в неё.
Для установки Patroni нам понадобится как минимум три сервера PostgreSQL. Как и раньше, мы предполагаем, что они называются pg1, pg2 и pg3. Выполните следующие действия на всех трех серверах, кроме указанных отдельно:
3.0 Установите Patroni, выполнив следующие команды от имени пользователя с полномочиями root:
pip install --upgrade setuptools
pip install patroni[etcd]
3.1 Выполните команду pg_config --bindir, чтобы узнать, где хранятся двоичные файлы PostgreSQL:
root@pg1:~# pg_config --bindir
/usr/lib/postgresql/13/bin
3.2 Теперь создайте каталог конфигурации для Patroni, который принадлежит пользователю postgres:
mkdir /etc/patroni
sudo chown postgres:postgres /etc/patroni
3.3 Как пользователь postgres создайте файл с именем patroni.yml в каталоге /etc/patroni со следующим содержимым ниже. Замените все экземпляры pg1 и его ip адрес на соответствующее имя сервера на каждом узле:
# PostgreSQL - 1 : 192.168.88.170
# PostgreSQL - 2 : 192.168.88.169
# PostgreSQL - 3 : 192.168.88.168
# etcd - 1 : 192.168.88.170
# etcd - 2 : 192.168.88.169
# etcd - 3 : 192.168.88.168
# Конфиг для patroni /etc/patroni.yml
# --------------------------------------------------------------------------
scope: batman # должно быть одинаковым на всех нодах
namespace: /cluster/ # должно быть одинаковым на всех нодах
name: pg1 # должно быть разным на всех нодах
restapi:
listen: 192.168.88.170:8008 # адрес той ноды, в которой находится этот файл
connect_address: 192.168.88.170:8008 # адрес той ноды, в которой находится этот файл
etcd:
hosts: 192.168.88.170:2379,192.168.88.169:2379,192.168.88.168:2379 # перечислите здесь все ваши ноды, в случае если вы устанавливаете etcd на них же
# this section (bootstrap) will be written into Etcd:/<namespace>/<scope>/config after initializing new cluster
# and all other cluster members will use it as a `global configuration`
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"
max_wal_senders: 5
max_replication_slots: 5
wal_log_hints: "on"
archive_mode: "on"
archive_timeout: 600s
archive_command: "cp -f %p /db/pg_archived/%f"
recovery_conf:
restore_command: "cp -f /db/pg_archived/%f %p"
initdb:
- encoding: UTF8
- data-checksums
- locale: en_US.UTF8
# init pg_hba.conf должен содержать адреса ВСЕХ машин, используемых в кластере
pg_hba:
- host replication replicator ::1/128 md5
- host replication replicator 127.0.0.1/8 md5
- host replication replicator 192.168.88.170/24 md5
- host replication replicator 192.168.88.169/24 md5
- host replication replicator 192.168.88.168/24 md5
- host all all 0.0.0.0/0 md5
users:
rebrain_admin:
password: admin
options:
- createrole
- createdb
postgresql:
listen: 192.168.88.170:5432 # адрес той ноды, в которой находится этот файл
connect_address: 192.168.88.170:5432 # адрес той ноды, в которой находится этот файл
data_dir: /etc/patroni/pgdata # эту директорию создаст скрипт, описанный выше и установит нужные права
bin_dir: /usr/lib/postgresql/13/bin # укажите путь до вашей директории с postgresql
pgpass: /tmp/pgpass0
authentication:
replication:
username: replicator
password: pwd
superuser:
username: root
password: admin
create_replica_methods:
basebackup:
checkpoint: 'fast'
parameters:
unix_socket_directories: '/var/run/postgresql'
external_pid_file: '/var/run/postgresql/13-main.pid'
logging_collector: "on"
log_directory: "/var/log/postgresql"
log_filename: "postgresql-13-main.log"
3.4 Как пользователь postgres, поменяйте права на файл patroni.yml:
chmod 600 /etc/patroni/patroni.yml
3.5 Начиная с pg1, выполните следующую команду, чтобы запустить Patroni. (убедитесь, что postgresql выключен и никто не занял порт 5432):
patroni /etc/patroni/patroni.yml \
&> /var/log/postgresql/patroni.log &
Вывод файла журнала:
cat /var/log/postgresql/patroni.log
2021-03-05 19:38:08,999 INFO: Selected new etcd server http://192.168.88.168:2379
2021-03-05 19:38:09,012 INFO: No PostgreSQL configuration items changed, nothing to reload.
2021-03-05 19:38:09,023 INFO: Lock owner: None; I am pg1
2021-03-05 19:38:11,232 INFO: initialized a new cluster
2021-03-05 19:38:21,202 INFO: Lock owner: pg1; I am pg1
2021-03-05 19:38:21,220 INFO: no action. i am the leader with the lock
Повторите запуск Patroni на оставшихся нодах. В выводе должны увидеть следующее:
pg2:
2021-03-05 19:46:39,141 INFO: Lock owner: pg1; I am pg2
2021-03-05 19:46:39,141 INFO: does not have lock
2021-03-05 19:46:39,142 INFO: establishing a new patroni connection to the postgres cluster
2021-03-05 19:46:41,318 INFO: no action. i am a secondary and i am following a leader
pg3:
2021-03-05 19:57:41,307 INFO: Lock owner: pg1; I am pg3
2021-03-05 19:57:41,307 INFO: does not have lock
2021-03-05 19:57:41,326 INFO: no action. i am a secondary and i am following a leader
Перед первым запуском Patroni будет использовать pg_basebackup для инициализации новых реплик, у которых нет существующих данных.
Сперва мы начинаем с установки библиотеки разработки пакетов - setuptools. Затем устанавливаем patroni[etcd].
Затем нам нужно найти двоичные файлы PostgreSQL, и самый простой способ сделать это - вызвать утилиту pg_config с параметром --bindir. Это особенно необходимо, если мы используем дистрибутив Linux с нестандартным двоичным каталогом, который может повлиять на работу кластера.
Поскольку стек будет работать под пользователем postgres, нам необходимо назначить этого пользователя владельцем каталога.
Наша следующая задача - создать конфигурационный файл для Patroni. Этот файл будет определять способ инициализации нового определения кластера, текущие параметры работы и существующую структуру для инструмента командной строки patronictl. Мы начинаем с создания файла с именем patroni.yml в каталоге /etc/patroni и указываем, что он принадлежит пользователю postgres. Это позволяет нам потенциально добавлять информацию о пароле и обеспечивать ее конфиденциальность и безопасность в нашем кластере.
В разделе restapi мы определяем два параметра. Мы устанавливаем прослушивание на pg1: 8008, чтобы Patroni отслеживал порт 8008 на указанном узле. Этот URL-адрес может использоваться для получения или определения информации о конфигурации или для определения текущего основного сервера. Мы устанавливаем для connect_address то же значение, чтобы Patroni мог при необходимости получить доступ к собственному REST API.
После restapi - это раздел etcd. Здесь мы определяем расположение нашего хранилища ключей и значений. Из-за его относительной простоты нам требуется только установить параметр хоста на pg1: 2379, тот же клиентский интерфейс и порт, которые мы определили для etcd.
Далее мы определяем секцию начальной загрузки, которая содержит несколько подэлементов. Все параметры, которые мы определяем в этих подразделах, используются для инициализации нового кластера. Если мы присоединим Patroni к существующему экземпляру PostgreSQL, только раздел dcs останется актуальным и будет сохранен в хранилище ключей и значений для дальнейшего использования.
Часть dcs соответствует определению кластера. Здесь мы начинаем с установки ttl на 30, что означает, что основной узел должен восстанавливать свой статус каждые 30 секунд или потенциально запускать переключение на другой узел. Если установить для loop_wait значение 10, реплика должна заметить отсутствие мастера в течение десяти секунд или меньше. Установка для параметра retry_timeout значения 10 в основном предотвращает зависание соединений во время операций в случае, если серверы исчезают из-за проблем с сетью. И, наконец, мы устанавливаем maximum_lag_on_failover равным байтовому эквиваленту 1 МБ в качестве минимального порога, которому реплики должны удовлетворять, прежде чем считаться кандидатами на аварийное переключение.
После основных элементов DCS мы определяем, как PostgreSQL обрабатывает Patroni. Если мы используем PostgreSQL выше 9.5, мы можем установить для use_pg_rewind значение true как более быстрый метод преобразования бывшего первичного сервера в новую реплику без необходимости повторной синхронизации данных. Так-же рекомендуется использовать слоты репликации в PostgreSQL 9.4 и выше, когда это возможно, чтобы предотвратить задержку репликации, а установка use_slots в true делает это явным для Patroni.
Подраздел параметров - это просто набор значений, обычно встречающихся в postgresql.conf. Точно так же раздел recovery_conf соответствует файлу recovery.conf, используемому для определения операций восстановления реплики. Это специально дополнительные значения, то есть мы добавляем их только в том случае, если мы хотим переопределить значения Patroni по умолчанию или определить любые элементы конфигурации, которые мы считаем критическими для работы кластера. Обычно мы ограничиваем такие дополнения требованиями репликации или необходимым управлением файлами WAL.
После раздела dcs идет раздел initdb, который в основном используется для обработки параметров утилиты initdb PostgreSQL. В этом случае мы включили контрольные суммы данных и убеждаемся, что вновь инициализированная база данных использует кодировку символов UTF8. Указание этого последнего значения может показаться необязательным, но на практике мы сталкивались с базами данных в кодировке ASCII, и их очень сложно исправить после того, как они будут созданы.
Затем есть раздел pg_hba для дополнительных записей во вновь созданных файлах pg_hba.conf. В этом случае мы решили разрешить учетной записи replicator использовать псевдобазу replication, и все другие учетные записи могут подключаться в нашей ограниченной подсети. Здесь Вы можете разместить все необходимые записи pg_hba.conf для базовой работы кластера в стеке приложений.
Далее у нас есть раздел пользователей, где мы можем создать столько учетных записей пользователей для вновь созданного кластера, сколько нам нужно. В нашем случае мы выбрали одну учетную запись admin с возможностью создания дополнительных ролей и баз данных. Этот раздел также является причиной, по которой мы хотим, чтобы файл принадлежал пользователю системы postgres.
Последний раздел в файле конфигурации - postgresql, он определяет рабочее состояние каждого локального экземпляра PostgreSQL. Как и в разделе restapi, здесь также есть записи для прослушивания и подключения для определения целей подключения. Здесь мы также определяем data_dir, поскольку данные PostgreSQL могут находиться в разных местах на каждом сервере.
Причина, по которой мы явно установили bin_dir на полный путь к двоичным файлам PostgreSQL, связана с возможностью размещения на серверах нескольких версий PostgreSQL или использования нестандартных каталогов установки. Здесь мы используем значение, полученное ранее с помощью pg_config.
Разделы pgpass и аутентификации по сути идут вместе. Первый определяет место для временного файла паролей, а второй объявляет как replicator, так и суперпользователя (root). Поскольку для вновь подготовленных реплик необходима правильная аутентификация, чтобы они могли загрузиться и начать репликацию, эти разделы гарантируют, что процесс всегда будет успешным.
И, наконец, мы можем указать столько произвольных значений postgresql.conf в подразделе параметров, сколько захотим. В отличие от тех, что находятся в разделе начальной загрузки, они применяются только к экземпляру, которым управляет текущий узел Patroni. Хотя это маловероятно, бывают случаи, когда определенные узлы требуют определенных настроек для правильной работы.
К счастью, конфигурационный файл Patroni - самая сложная часть его использования. Как только мы сделаем файл конфигурации доступным для чтения только пользователю postgres, мы можем запустить Patroni на каждом узле, передав полный путь к файлу конфигурации команде patroni. Даже если каталог данных каждой новой реплики был полностью пуст, мы должны увидеть, что patroni воссоздает его при начальной инициализации с ведомого сервера.
Мы также можем просмотреть полный статус кластера с любого существующего узла. Для этого нам нужно передать параметр списка и путь к нашему файлу конфигурации в инструмент командной строки patronictl. Нам также необходимо указать, о каком кластере мы хотим получить информацию. Это связано с тем, что существует несколько методов получения информации о кластере.
Если мы используем параметр -c для детализации файла конфигурации, наши результаты должны выглядеть так:
$ patronictl -c /etc/patroni/patroni.yml list
+ Cluster: batman (6936256601885826058) ------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+----------------+---------+---------+----+-----------+
| pg1 | 192.168.88.170 | Leader | running | 1 | |
| pg2 | 192.168.88.169 | Replica | running | 1 | 0.0 |
| pg3 | 192.168.88.168 | Replica | running | 1 | 0.0 |
+--------+----------------+---------+---------+----+-----------+
Последний элемент в нашей схеме - это HAProxy. HAProxy используется для перенаправления трафика на основной узел read/write в нашем кластере PostgreSQL. HAProxy нам нужен для того, чтобы иметь возможность всегда достигать первичного узла независимо от его местоположения.
Приступим к установке и настройке HAProxy:
Для систем основанных на Debian, установка HAProxy производится из стандартного системного репозитория с помощью команды apt:
apt install haproxy
Для Red-Hat используем эквивалентную команду yum:
yum install haproxy
Мы помним, что для обеспечения отказоустойчивости нам понадобилось три сервера PostgreSQL. Мы назвали их pg1, pg2 и pg3. Теперь для них мы будем имитировать внешний LoadBalancer, в качестве которого выступит сервер с HAProxy.
После установки HAProxy через менеджер пакетов на диске будет сздан файл конфигурации /etc/haproxy/haproxy.cfg, давайте отредактируем его согласно наших исходных данных:
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 *:32700
stats enable
stats uri /
frontend ft_postgresql
bind *:5000
mode tcp
default_backend bk_db
backend bk_db
option httpchk
mode tcp
server pg1_192.168.88.170 192.168.88.170:5432 maxconn 100 check port 8008
server pg2_192.168.88.169 192.168.88.169:5432 maxconn 100 check port 8008
server pg3_192.168.88.168 192.168.88.168:5432 maxconn 100 check port 8008
После внесения изменений в конфигурационный файл, сервису haproxy необходимо заново перечитать его, чтобы начать использовать полученные изменения. Но для нас не критично, мы можем его даже перезапустить полностью:
systemctl restart haproxy
Проверяем, что указанные в конфиге haproxy.cfg порты слушаются сервисом haproxy:
lsof -i -P -n | grep LISTEN | grep haproxy
haproxy 2169 root 6u IPv4 40385 0t0 TCP *:32700 (LISTEN)
haproxy 2169 root 7u IPv4 40386 0t0 TCP *:5000 (LISTEN)
4.4 Теперь, если перейти в браузере на страницу stats вашего сервера с HAproxy указав соответвсующий IP и порт 32700, вы увидите статистику по вашему кластеру.
В состоянии UP будет находится тот сервер, который является лидером, а реплики будут в состоянии DOWN. Не пугайтесь! На самом деле они работают, но возвращают код 503 на запросы от HAproxy. Эта особенность позволяет нам всегда точно знать, какой из трех серверов является лидером кластера на данный момент.
4.5 Проведем эксперемент и на pg3 выполним следующую команду:
$ psql -h HAproxy_ip_addr -p 5000 -d postgres -U root -W -c "select inet_server_addr();"
inet_server_addr
------------------
192.168.88.170
(1 row)
Функция inet_server_addr возвращает IP-адрес сервера, с которым мы связались. Ввиду того, что мы обратились к кластеру PostgreSQL через HAProxy, мы были перенаправлены на pg1 и в результате получили 192.168.88.170.
Теперь остановим patroni на pg1, подождем 30 сек и посмотрим что изменится в HAProxy stats:
Как видно из полученной информации - картина изменилась, лидером теперь является сервер pg3. Давайте снова выполним запрос через HAProxy и посмотрим куда он нас перенаправит:
psql -h HAproxy_ip_addr -p 5000 -d postgres -U root -W -c "select inet_server_addr();"
Password:
inet_server_addr
------------------
192.168.88.168
(1 row)
Мы получили IP 192.168.88.168, а это значит, что HAProxy перенаправил наши запросы на нового лидера pg3. Так и должно было быть =)
Подытожим, что мы имеем на данный момент. Кластер etcd хранит информацию о лидере и репликах кластера PostgreSQL, сам кластер работает под управлением patroni. Репликация работает. Автопереключение работает. HAProxy в stats показывает нам текущего лидера кластера PostgreSQL и направляет на него все запросы.
Теперь информации достаточно, чтобы выполнить задание. Приступайте, как будете готовы!
ETCD_VER=v3.5.0
# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}
rm -f /task13/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /task13/etcd/ && mkdir -p /task13/etcd/
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /task13/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /task13/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /task13/etcd --strip-components=1
rm -f /task13/etcd-${ETCD_VER}-linux-amd64.tar.gz
/task13/etcd/etcd --version
/task13/etcd/etcdctl version
Используя пример конфига из официального репозитория (https://github.com/etcd-io/etcd/blob/release-3.4/etcd.conf.yml.sample) на каждом сервере с etcd настройте etcd так, чтобы использовались следующие параметры окружения и конфигурации: - путь к исполняемому файлу etcd "/task13/etcd/etcd" - путь к конфигурационному файлу etcd "/etc/etcd/etcd.yml" - name: 'rebrain-etcd-node-X' |где X - номер сервера - data-dir: '/task13/etcd/data-dir' - wal-dir: '/task13/etcd/wal-dir' - Формат для записи параметра listen-peer-urls: http://#{Внешний etcd-X IP}:2380,http://127.0.0.1:2380 |где X - номер сервера (например: http://135.181.196.92:2380) - Формат для записи параметра listen-client-urls: http://#{Внешний etcd-X IP}:2379,http://127.0.0.1:2379 |где X - номер сервера (например: http://135.181.196.92:2379) - Формат для записи параметра initial-advertise-peer-urls: http://#{Внешний etcd-X IP}:2380 |где X - номер сервера (например: http://135.181.196.92:2380) - Формат для записи параметра advertise-client-urls: http://#{Внешний etcd-X IP}:2379 |где X - номер сервера (например: http://135.181.196.92:2379) - initial-cluster: 'rebrain-etcd-node-1=http://#{Внешний etcd-1 IP}:2380,rebrain-etcd-node-2=http://#{Внешний etcd-2 IP}:2380,rebrain-etcd-node-3=http://#{Внешний etcd-3 IP}:2380' - initial-cluster-token: 'etcd-cluster-rebrain-token'
На каждом сервере с etcd cоздайте пользователя и группу etcd, измените рекурсивно владельца рабочей директории /task13/etcd/ и директории с конфигами /etc/etcd/ на etcd. Установите etcd в качесчтве сервиса systemd. Должны быть выполнены следующие требования:
файл сервиса должен быть создан в директории "/etc/systemd/system/" с именем "patroni.service"
в файле "patroni.service" должны содержаться следующие параметры:
- в блоке [Unit]
- Description=Patroni needs to orchestrate a high-availability PostgreSQL
- Documentation=https://patroni.readthedocs.io/en/latest/
- After=syslog.target network.target
- в блоке [Service]
- User=postgres
- Group=postgres
- Type=simple
- ExecStart=/usr/local/bin/patroni /etc/patroni/patroni.yml
- Restart=no
- в блоке [Install]
- WantedBy=multi-user.target
CREATE TABLE test (id SERIAL Primary Key NOT NULL, info TEXT);
Внесите в таблицу test следующие данные:
INSERT INTO test (info) VALUES ('Hello'),('From'),('Patroni'),('Leader');
Затем зайдите на любую из реплик с помощью psql и получите все данные из таблицы test командой:
SELECT * FROM test;
Вы должны увидеть следующий вывод:
id | info
----+---------
1 | Hello
2 | From
3 | Patroni
4 | Leader
(4 rows)
Если вы его увидели, репликация работает. 11. На сервере HAProxy из официальных репозиториев Ubuntu установите HAProxy. Настройте HAProxy так, чтобы:
конфигурационный файл HAProxy находился в директории /etc/haproxy/
настройки в конфигурационном файле HAProxy haproxy.cfg соответствовали следующим значениям: - listen stats - mode http - bind *:32700 - stats enable - stats uri /
- frontend ft_postgresql
- bind *:5432
- mode tcp
- default_backend bk_db
- backend bk_db
- option httpchk
- mode tcp
- server patroni_node_1_IP patroni_node_1_IP:5432 maxconn 100 check port 8008
- server patroni_node_2_IP patroni_node_2_IP:5432 maxconn 100 check port 8008
- server patroni_node_2_IP patroni_node_3_IP:5432 maxconn 100 check port 8008
psql -h HAproxy_ip_addr -p 5432 -d postgres -U root -W -c "select inet_server_addr();"
Чтобы начать выполнение задания, укажите в настройках вашу должность, название компании и аккаунт в Telegram
Заполнить