Невмирущий Postgresql cluster всередині Kubernetes cluster

Якщо ви коли-небудь замислювалися про довіру і надії, то швидше за все, не відчували цього ні до чого так само сильно, як до систем управління базами даних. Ну і дійсно, це ж База Даних! У назві міститься весь сенс — місце, де зберігаються дані, основне завдання ЗБЕРІГАТИ. І що найсумніше, як завжди, одного разу, ці переконання розбиваються про останки такий однієї померлої БД на 'виявляли у своєму житті таку'.

І що ж робити? — запитаєте ви. Не деплоить на сервера нічого, — відповідаємо ми. Нічого, що не вміє сама себе лагодити, хоча б тимчасово, проте надійно та швидко!
У цій статті я спробую розповісти про свій досвід налаштування майже безсмертного Postgresql кластера всередині іншого резервний рішення від Google — Kubernates (aka k8s)
Зміст
Стаття вийшла трохи більше, ніж очікувалося, а тому в коді багато посилань, в основному на код, у контексті якого йде мова.
Додатково додаю зміст, в тому числі для нетерплячих, щоб відразу перейти до результатів:

Завдання
Потреба мати сховище з даними для майже будь-якої програми — необхідність. А мати це сховище стійким до знегод в мережі або на фізичних серверах — хороший тон грамотного архітектора. Інший аспект — висока доступність сервісу навіть при великих конкуруючих запити на обслуговування, що означає легке масштабування при необхідності.

Разом отримуємо проблеми для рішення:
  • Фізично розподілена сервіс
  • Балансування
  • Не обмежене масштабування шляхом додавання нових вузлів
  • Автоматичне відновлення при збоях, знищення та втрати зв'язку вузлами
  • Відсутність єдиної точки відмови
" Додаткові параметри, зумовлені специфікою релігійних переконань автора:
  • Postgres (найбільш академічне і консистентное рішення для РСУБД серед безкоштовних доступних)
  • Docker упаковка
  • Kubernetes опис інфраструктури
На схемі це буде виглядати приблизно так:
master (primary node1) --\
|- slave1 (node2) ---\ / balancer \ 
| |- slave2 (node3) ----|---| |----client
|- slave3 (node4) ---/ \ balancer /
|- slave4 (node5) --/

За умови вхідних даних:
  • Більше число запитів на читання (по відношенню до запису)
  • Лінійний зростання навантаження при піках до x2 від середнього

Рішення методом "Гугления"
Будучи досвідченим в рішенні IT проблем, людиною, я вирішив запитати у колективного розуму: "postgres cluster kubernetes" — купа сміття, "postgres cluster docker" — купа сміття, "postgres cluster" — кілька варіантів, з яких довелося воять.

Що мене засмутило, так це відсутність адекватних Docker збірок і опис будь-якого варіанту для кластеризації. Не кажучи вже про Kubernetes. До речі кажучи, для Mysql варіантів було не багато, але все ж були. Як мінімум сподобався приклад в офіційному репозиторії k8s Galera(Mysql cluster)
Гугл дав ясно зрозуміти, що проблеми доведеться вирішувати самому і в ручному режимі..."але хоч з допомогою розрізнених рад та статей" — видихнув я.

Погані і непреемлимые рішення
Відразу зауважу, що всі пункти в цьому параграфі можуть бути суб'єктивними і, навіть, життєздатними. Однак, покладаючись на свій досвід і чуття, мені довелося їх відсікти.

Pgpool. Чому Pgpool не завжди хороший?Коли хтось робить універсальне рішення (для чого б то не було), мені завжди здається, що такі речі громіздкі, неповороткі і важкі в обслуговуванні. Так само вийшло і з Pgpool, який вміє майже все:
  • Балансування
  • Зберігання пачки коннектов для оптимізації з'єднання і швидкості доступу до БД
  • Підтримка різних варіантів реплікації (stream, slony)
  • Авто визначення Primary сервера для запису, що важливо при реорганізації ролей у кластері
  • Підтримка failover/failback
  • Власна реплікація master-master
  • Узгоджена робота кількох вузлів Pgpool-ів для викорінення єдиної точки відмови.
Перші чотири пункти я знайшов корисними, і зупинився на тому, зрозумівши і обдумавши проблеми інших:
  • Відновлення з допомогою Pgpool2 не пропонує ніякої системи ухвалення рішення про наступний майстер — вся логіка повинна бути описана в командах failover/failback
  • Час запису, при реплікації master-master, зводиться до подвоєного стосовно варіанта без нього, незалежно від кількості вузлів… ну хоч не росте лінійно
  • Як побудувати каскадний кластер (коли один slave читає з попереднього slave) — взагалі не зрозуміло
  • Звичайно ж добре, що Pgpool знає про своїх братів і може оперативно ставати активним ланкою при проблемах на сусідньому вузлі, але цю проблему для мене вирішує Kubernetes, який гарантує аналогічне поведінка, взагалі, будь-якого сервісу встановленого в нього.

Slony. Як слон залишив насВласне, теж почитавши і порівнявши знайдене з вже знайомої і працюючої "з коробки" потокової реплікацією (Streaming Replication), легко далося рішення навіть не думати про Слонів.
та й до всього іншого, на першій же сторінці сайту проекту хлопці пишуть що, з postgres 9.0+ вам не потрібні Slony за умови відсутності якихось специфічних вимог до системи:
  • часткова реплікація
  • інтеграція з іншими рішеннями ("Londiste and Bucardo")
  • додаткове поведінка при реплікації
Вообщем, мені здається Slony не торт… як мінімум якщо у вас немає цих самих трьох специфічних завдань.
Master-master replication. Не всі реплікації однаково корисніОзирнувшись довкола і розібравшись у варіантах підходу ідеальної двосторонньої реплікації, виявилося, що жертви не сумісні з життям деяких додатків. Не кажучи про швидкості, є обмеження в роботі з транзакціями, складними запитами (
SELECT FOR UPDATE
та інші).
Цілком ймовірно, я не так обізнаний саме в цьому питанні, але побаченого мені вистачило, щоб залишити цю ідею. І ще розкинувши мізками, мені здалося, що для системи з посиленою операцією запису потрібні зовсім інші технології, а не реляційні бази даних.

Консолідація і складання вирішення
У прикладах я буду говорити про те, як принципово повинно виглядати рішення, а в коді — як це вийшло у мене. Для створення кластера зовсім не обов'язково мати Kubernetes (є приклад docker-compose) або Docker в принципі. Просто тоді, все описане буде корисно, не як рішення типу CPM (Copy-Paste-Modify), а як керівництво по установці з снипетами.

Primary і Standby замість Master і Slave
Чому ж колеги з Postgresql відмовилися від термінів "Master" і "Slave"?.. хм, можу помилятися, але ходив слух, що за не-політ-коректності, мовляв, що рабство це погано. Ну і правильно.

Перше, що потрібно зробити — це включити Primary сервер, за ним перший шар Standby, а за тим другій — відповідно до поставленого завдання. Звідси отримуємо просту процедуру по включенню звичайного Postgresql сервера в режимі Primary/Standby з конфігурацією для включення Streaming Replication
На що варто звернути увагу в файлі конфігурації
wal_level = hot_standby 
max_wal_senders = 5
wal_keep_segments = 5001
hot_standby = on

Всі параметри в коментарях мають короткий опис, але якщо коротко, ця конфігурація дає серверу зрозуміти, що він відтепер частина кластер і, в разі чого, потрібно дозволити читати WAL логи іншим клієнтам. Плюс дозволити запити під час відновлення. Відмінне опис за детальною настроювання такого роду реплікації можна знайти на Postgresql Wiki.
Як тільки ми отримали перший сервер кластера, можемо включати Stanby, який знає, де знаходиться його Primary.
Моя задача тут звелася до складання універсального способу Docker Image, який включається в роботу в залежності від режиму, як то так:
  • Primary:
    • Конфігурує Repmgr (про нього трохи пізніше)
    • Створює базу і користувача для програми
    • Створює базу і користувача для моніторингу та підтримки реплікації
    • Оновлює конфіг(
      postgresql.conf
      ) і відкриває доступ користувачам ззовні(
      pg_hba.conf
      )
    • Запускає Postgresql сервіс тлі
    • Реєструється як Master в Repmgr
    • Запускає
      repmgrd
      — демон від Repmgr для моніторингу реплікації (про нього теж пізніше)
  • Standby:
    • Клонує Primary сервер з допомогою Repmgr (між іншим з усіма конфіг, так як просто копіює
      $PGDATA
      директорію)
    • Конфігурує Repmgr
    • Запускає Postgresql сервіс тлі — після клонування сервіс осудна усвідомлює, що є standby і покірно слідує за Primary
    • Реєструється як Slave в Repmgr
    • Запускає
      repmgrd
Для всіх цих операцій важлива послідовність, і тому у коді напхане
sleep
. Знаю — не добре, але так зручно конфігурувати затримки через ENV змінні, коли потрібно разом стартанути всі контейнери (наприклад через
docker-compose up
)
Всі змінні до цього образу описані в
docker-compose
файл
.
Вся різниця першого і другого шару Standby сервісів в тому, що для другого майстром є будь-сервіс з першого шару, а не Primary. Не забуваємо, що другий ешелон повинен стартувати після першого з затримкою в часі.

Split-brain і вибори нового лідера в кластері
Split brain — ситуація, в якій різні сегменти кластера можуть створити/обрати нового Master-a і думати, що проблема вирішена.

Це одна, але далеко не єдина проблема, яку мені допоміг вирішити Repmgr.
По суті це менеджер, який вміє робити наступне:
  • Клонувати Master (master — в термінах Repmgr) і автоматично настоить вновьрожденный Slave
  • Допомогти реанімувати кластер при смерті Master-а.
  • В автоматичному або ручному режимі Repmgr може обрати нового Master-а і перенастроювати всі Slave сервіси слідувати за новим лідером.
  • Виводити з кластера вузли
  • Моніторити здоров'я кластера
  • Виконувати команди при події усередині кластера
У нашому випадку на допомогу приходить
repmgrd
, який запускається основним процесом в контейнері і стежить за цілісністю кластера. При ситуації, коли пропадає доступ до Master сервера, Repmgr намагається проаналізувати поточну структуру кластера і прийняти рішення про те, хто стане наступним Master-му. Природно Repmgr досить розумний, щоб не створити ситуацію Split Brain і вибрати єдино правильного Master-а.

Pgpool-II — swiming pool of connections
Остання частина системи — Pgpool. Як я писав в розділі про погані рішення, сервіс все-таки робить свою роботу:
  • Балансує навантаження між усіма уздами кластера
  • Храннит дескриптори з'єднань для оптимізації швидкості доступу до БД
  • При нашому випадку Streaming Replication — автоматично знаходить Master і використовує його для запитів на запис.

Як результат, у мене вийшов досить простий Docker Image, який при старті конфігурує себе на роботу з набором вузлів і користувачів, у яких буде можливість проходити md5 авторизацію крізь Pgpool (c цим теж, як виявилося, не все просто)
Дуже часто виникає завдання позбутися єдиної точки відмови, і в нашому випадку цією точкою є pgpool сервіс, який проксирует всі запити і може стати найбільш слабкою ланкою на шляху доступу до даних.
На щастя, в цьому випадку нашу проблему вирішує k8s і дозволяє зробити стільки реплікацій сервісу скільки потрібно.
прикладі для Kubernetes на жаль цього немає, але якщо ви знайомі з тим, як працює Replication Controller та/або Deployment, то провернути вищеописане вам не складе праці.

Результат
Ця стаття — не переказ скриптів для вирішення завдання, але опис структури рішення цієї задачі. Що означає — для більш глибокого розуміння та оптимізації рішення доведеться почитати код, як мінімум README.md в github, який покроково і детально розповідає як запустити кластер для docker-compose і Kubernetes. До всього іншого, для тих, хто перейметься і вирішиться з цим рухатися далі, я готовий простягнути віртуальну руку допомоги.


Документація та використаний матеріал
PS:
Сподіваюся, що викладений матеріал буде корисний і подарує трохи позитиву перед початком літа! Удачі і хорошого настрою, колеги ;)
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.