Заряжай Patroni. Тестируем Patroni + Zookeeper кластер (Часть вторая)

Кадр из фильма «Терминатор 2: Судный день»

В первой части мы остановились на том, что у нас есть отказоустойчивый кластер и теперь вроде бы все работает. Мы уже можем зайти в базу данных через лидера и что-нибудь туда записать. Последняя проблема, которую нужно решить — программно узнать, где находится лидер. Неужели нужно будет каждый раз делать эту проверку после падения, чтобы понять через какую ноду подключаться к базе?

Это было бы настолько скверно, что испортило бы все решение. Но не беспокойтесь, с помощью HA Proxy вопрос решается довольно легко и незатейливо.

HA Proxy

Также как с Patroni, сначала сделаем отдельную директорию под билд/деплой файлы и начнем их там создавать:

  • haproxy.cfg

Это файл конфига, который мы положим в наш кастомный образ.

haproxy.cfg
global
    maxconn 100
    stats socket /run/haproxy/haproxy.sock 
    stats timeout 2m # Wait up to 2 minutes for input

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 patroni1:5432 maxconn 100 check port 8091
    server patroni2 patroni2:5432 maxconn 100 check port 8091
    server patroni3 patroni3:5432 maxconn 100 check port 8091
Details

В этих строчках мы назначаем порты, по которым будет получать доступ:

// этот для вывода статистики
listen stats
    mode http
    bind *:7000
//этот для подключения к postgres
listen postgres
    bind *:5000

А здесь мы просто перечисляем все сервисы Patroni, которые создали ранее:

server patroni1 patroni1:5432 maxconn 100 check port 8091
server patroni2 patroni2:5432 maxconn 100 check port 8091
server patroni3 patroni3:5432 maxconn 100 check port 8091

И последнее. Эта строка нужна, если мы хотим проверять состояние кластера с помощью специальной утилиты из контейнера с HA Proxy:

stats socket /run/haproxy/haproxy.sock 
  • Dockerfile

Dockerfile выглядит довольно просто и, думаю, не требует комментариев:

Dockerfile
FROM haproxy:1.7
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
RUN mkdir /run/haproxy &&
    apt-get update -y &&
    apt-get install -y hatop &&
    apt-get clean

Compose файл выглядит тоже достаточно просто:

docker-compose-haproxy.yml
version: "3.7"

networks:
  patroni_patroni:
    external: true

services:
 haproxy:
    image: haproxy-patroni
    networks:
      - patroni_patroni
    ports:
      - 5000:5000
      - 7000:7000
    hostname: haproxy
    deploy:
      mode: replicated
      replicas: 1
      placement: 
        constraints: [node.hostname == floitet]

Когда все файлы готовы, можно и задеплоить весь этот сет:

// build
docker build -t haproxy-patroni
// deploy
docker stack deploy --compose-file docker-compose-haproxy.yml

Когда HА Proxy запустится, можно будет в контейнере посмотреть статистику кластера специальной командой:

sudo docker ps | grep haproxy
sudo docker exec -ti $container_id /bin/bash
hatop -s /var/run/haproxy/haproxy.sock

Выполнив эти три шага, мы увидим прямо в консоли красивый вывод статистики.

Сам я предпочитаю смотреть статистику через patronictl либо Patroni API, но HA Proxy тоже один из вариантов и почему бы не настроить и его заодно.

А теперь немного о Patroni API.

Patroni API

Я обещал показать три способа смотреть статистику кластера, и вот добрался-таки до последнего. Вывод статистики через API хорош тем, что он дает самую полную информацию о кластере (и вообще через него можно делать всё).

Более подробно почитать про API можно в доках, а здесь я покажу самые основные вещи: стандартные запросы и как их выполнить в нашем сетапе.

Также как с подключением к БД, мы не сможем обращаться к API, не находясь в сети ’patroni_patroni’. Так что нам придется слать все наши запросы из контейнера. Чтобы читать вывод json в приятном человеческому глазу формате, сделаем кастомный имейдж с curl’ом и jq.

Dockerfile
FROM alpine:3.10
RUN apk add --no-cache curl jq bash
CMD ["/bin/sh"]

И потом запустим контейнер с этим образом, подключив его к нужной сети:

docker run --rm -ti --network=patroni_patroni curl-jq

Теперь мы можем обращаться к API Patroni нод по их именам и получать cтаты в таком вот виде:

Работа с API
// Статистика ноды

curl -s patroni1:8091/patroni | jq
{
  "patroni": {
    "scope": "patroni",
    "version": "2.0.1"
  },
  "database_system_identifier": "6893104757524385823",
  "postmaster_start_time": "2020-11-15 19:47:33.917 UTC",
  "timeline": 10,
  "xlog": {
    "received_location": 100904544,
    "replayed_timestamp": null,
    "replayed_location": 100904544,
    "paused": false
  },
  "role": "replica",
  "cluster_unlocked": false,
  "state": "running",
  "server_version": 110009
}

// Статистика кластера

curl -s patroni1:8091/cluster | jq
{
  "members": [
    {
      "port": 5432,
      "host": "10.0.1.5",
      "timeline": 10,
      "lag": 0,
      "role": "replica",
      "name": "patroni1",
      "state": "running",
      "api_url": "http://10.0.1.5:8091/patroni"
    },
    {
      "port": 5432,
      "host": "10.0.1.4",
      "timeline": 10,
      "role": "leader",
      "name": "patroni2",
      "state": "running",
      "api_url": "http://10.0.1.4:8091/patroni"
    },
    {
      "port": 5432,
      "host": "10.0.1.3",
      "lag": "unknown",
      "role": "replica",
      "name": "patroni3",
      "state": "running",
      "api_url": "http://10.0.1.3:8091/patroni"
    }
  ]
}

Как тестировать?

Основная идея, что теперь мы можем ставить свои эксперименты с Patroni кластером так, как если бы это был кластер с тремя реальными серверами. Достаточно просто выключать и включать сервисы Patroni, чтобы сымитировать падения серверов. Если лидер у нас patroni3, то мы делаем:

docker service scale patroni_patroni3=0

И отключаем его, убивая его единственный контейнер. Теперь можно глянуть состояние кластера и убедиться, что лидер перешел на здоровую ноду:

postgres@patroni1:/$ patronictl -c /etc/patroni.yml list patroni
+ Cluster: patroni (6893104757524385823) --+----+-----------+
| Member   | Host      | Role    | State   | TL | Lag in MB |
+----------+-----------+---------+---------+----+-----------+
| patroni1 | 10.0.1.93 | Leader  | running |  9 |           |
| patroni2 | 10.0.1.91 | Replica | running |  9 |         0 |
+----------+-----------+---------+---------+----+-----------+

Если сделать scale для patroni3 на ’1′, то он вернется в кластер и займет роль реплики.

Теперь вы можете запускать все тесты и проверять, как Patroni кластер будет реагировать на критические ситуации.

Бонус

Как и обещал, ниже небольшой скрипт для тестов, а также инструкции как с ним работать. Так что, если вам нужно что-то уже готовое для быстрого теста, добро пожаловать. Если же у вас есть свои идеи и сценарии и вам не требуется готовое решение, просто пропустите спойлер.

Тестируем Patroni cluster

Для тех читателей, кто хочет потестировать кластер Patroni на каком-то готовом примере прямо сейчас, я сделал вот этот скрипт. Он супер простой, и я уверен у вас не возникнет проблем с тем, чтобы понять, как он работает. По сути, он просто пишет в базу текущее время каждую секунду. Ниже я шаг за шагом покажу, как его запустить и какие результаты теста были у меня.

Допустим, вы уже скачали скрипт и положили его где-то у себя на машине. Если нет, то проделайте эту подготовку. Теперь нам нужно запустить Docker контейнер с официальным образом Miscrosoft SDK такой командой:

docker run --rm -ti --network=patroni_patroni -v /home/floitet/Documents/patroni-test-script:/home mcr.microsoft.com/dotnet/sdk /bin/bash

Два момента. Первое, как и раньше мы хотим подключиться к сети ’patroni_patroni’ и второе, мы делаем mount к той директории, где уже лежит готовый скрипт. Таким образом мы сможем запускать его из контейнера.

Теперь мы хотим, чтобы у нас появился единственный dll, который нужен чтобы скрипт взлетел. Заходим в контейнер и находясь в директории ’/home’ создаем папку ’patroni-test’ для консольного приложения. Заходим в нее и выполняем следующую команду:

dotnet new console

// видим такие строчки

Processing post-creation actions...
Running 'dotnet restore' on /home/patroni-test/patroni-test.csproj...
  Determining projects to restore...
  Restored /home/patroni-test/patroni-test.csproj (in 61 ms).
Restore succeeded.

И теперь мы можем добавить в проект нужный нам для работы пакет:

dotnet add package npgsql

А потом просто упаковываем проект:

dotnet pack

Если все прошло удачно, то мы получим ’Npgsql.dll’ по адресу: ’patroni-test/bin/Debug/net5.0/Npgsql.dll’.

Этот путь мы и добавляем как референс в скрипте, так что если у вас он отличается от моего, то в скрипте это нужно подправить.

А дальше просто запускаем скрипт:

dotnet fsi /home/patroni-test.fsx

// и в output мы увидим как скрипт пошел писать время: 

11/18/2020 22:29:32 +00:00
11/18/2020 22:29:33 +00:00
11/18/2020 22:29:34 +00:00

Важно не закрывать терминал со скриптом, пока идет тест.

Давайте посмотрим, где сейчас находится лидер, чтобы знать кого ронять. Можно использовать любой из трёх способов, я смотрел через patronictl:

+ Cluster: patroni (6893104757524385823) --+----+-----------+
| Member   | Host      | Role    | State   | TL | Lag in MB |
+----------+-----------+---------+---------+----+-----------+
| patroni1 | 10.0.1.18 | Replica | running | 21 |         0 |
| patroni2 | 10.0.1.22 | Leader  | running | 21 |           |
| patroni3 | 10.0.1.24 | Replica | running | 21 |         0 |
+----------+-----------+---------+---------+----+-----------+

Теперь нам нужно открыть новый терминал и «убить» лидера:

docker service ls | grep patroni
docker service scale $patroni2-id=0

Через какое-то время в окне со скриптом мы увидим сообщения об ошибке:

// давайте запомним время последней удачной записи 

11/18/2020 22:33:06 +00:00
Error
Error
Error

Если мы проверим статус кластера, то можем заметить некоторую задержку — он всё ещё показывает patroni2 в качестве лидера. Но спустя n секунд он все-таки перестроится и, пройдя короткую стадию по выбору лидера, придет в такое состояние:

+ Cluster: patroni (6893104757524385823) --+----+-----------+
| Member   | Host      | Role    | State   | TL | Lag in MB |
+----------+-----------+---------+---------+----+-----------+
| patroni1 | 10.0.1.18 | Replica | running | 21 |         0 |
| patroni3 | 10.0.1.24 | Leader  | running | 21 |           |
+----------+-----------+---------+---------+----+-----------+

Если же вернемся к терминалу со скриптом, то увидим, что соединение наконец-то восстановлено и запись возобновилась:

Error
Error
Error
11/18/2020 22:33:48 +00:00
11/18/2020 22:33:49 +00:00
11/18/2020 22:33:50 +00:00
11/18/2020 22:33:51 +00:00

Теперь проверим, как там поживает сама база данных и всё ли с ней в порядке после падения лидера:

docker run --rm -ti --network=patroni_patroni postgres:11 /bin/bash
psql --host haproxy --port 5000 -U approle -d postgres
postgres=> c patronitestdb
You are now connected to database "patronitestdb" as user "approle".
// Я установил время чуть раньше, чем произошла авария
patronitestdb=> select * from records where time > '22:33:04' limit 15;
time       
-----------------
 22:33:04.171641
 22:33:05.205022
 22:33:06.231735
 // как мы видим в моем случае Patroni понадобилось 42 секунды 
 // чтобы восстановить соединение 
 22:33:48.345111
 22:33:49.36756
 22:33:50.374771
 22:33:51.383118
 22:33:52.391474
 22:33:53.399774
 22:33:54.408107
 22:33:55.416225
 22:33:56.424595
 22:33:57.432954
 22:33:58.441262
 22:33:59.449541

Вывод по тестам

Из этого небольшого эксперимента мы можем заключить, что Patroni справился со своей задачей. После того, как лидера упал, произошли перевыборы, и мы смогли подсоединиться к базе. Все предыдущие данные также оказались в целости. Возможно, мы могли бы пожелать, чтобы восстановление произошло быстрее, чем за 42 секунды, но, в конце концов, это не так критично.

Послесловие

Полагаю, на этом можно считать туториал завершенным. Надеюсь, он помог вам разобраться с основными моментами в настройке и работе с Patroni и в целом был полезен. Спасибо за внимание и да хранит Patroni ваши данные, отстреливаясь от багов и героически поднимаясь после падений!

За помощь в настройке этого решения отдельное спасибо коллеге Андрею Юрченко. Без этого отзывчивого парня мои Patroni застряли бы в магазине/стволе и не убили бы ни одного врага.

Все файлы использованные во второй части здесь.

Let’s block ads! (Why?)

Read More

Recent Posts

Роскомнадзор рекомендовал хостинг-провайдерам ограничить сбор данных с сайтов для иностранных ботов

Центр управления связью общего пользования (ЦМУ ССОП) Роскомнадзора рекомендовал компаниям из реестра провайдеров ограничить доступ поисковых ботов к информации на российских сайтах.…

10 часов ago

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

5 дней ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

6 дней ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

6 дней ago

Нейросети будут писать тексты объявления за продавцов на Авито

У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…

6 дней ago

Объявлены победители международной премии Workspace Digital Awards-2024

24 апреля 2024 года в Москве состоялась церемония вручения наград международного конкурса Workspace Digital Awards. В этом году участниками стали…

6 дней ago