Привет! Меня зовут Олег Малахов, я работаю в AGIMA. Недавно на одном из проектов перед нами поставили задачу — разработать кластеризируемую систему для управления гипервизорами. Таким образом заказчик хотел сохранить высокую доступность и отказоустойчивость системы, а также обеспечить связность при работе в географически разнесенных Data-центрах. В итоге мы рассмотрели кучу вариантов, но остановились на Etdc. И сейчас расскажу, почему выбрали именно его.
Какие варианты рассматривали
Когда перед нами только поставили задачу, мы рассматривали несколько инструментов, которые могли обеспечить кластеризацию:
Consul — это распределенный сервис от HashiCorp. Он помогает хранить конфигурации, отслеживать изменения, находить сервисы и многое другое. Но у него два больших минуса: закрытый исходный код и Business Source License — поэтому мы быстро его отмели.
ZooKeeper — это тоже система «ключ-значение», распространяется по Apache License. Она также позволяет обеспечить сервис-дискавери и кластеризацию. Но при этом написана на Java, в связи с чем имеет проблемы с производительностью. Нам это не подошло.
Самописное решение: была идея написать реализацию алгоритма Raft или другого алгоритма кластеризации самостоятельно. Это бы позволило закрыть все наши потребности. Но тут тоже было большое «но» — это дорого, долго и сложно. Мы бы столкнулись с множеством подводных камней.
Etcd — тоже распределенное хранилище Key-value. Плюсы: оно простое, надежное и отказоустойчивое — система сама восстанавливается после сбоев. При этом размер хранилища ограничен 8 ГБ, производительность не всегда на высоте и нет горизонтального масштабирования.
Хорошенько перебрав все варианты, остановились на последнем. Мы использовали Etcd для распределенного хранения конфигурации и состояния. Система «ключ-значение» Etcd позволяла хранить и синхронизировать данные между узлами кластера. Кроме того, у него простой интерфейс API для взаимодействия.
Кластеризация с использованием Etcd
Чтобы запустить кластер Etcd, нужно как минимум три узла. Число узлов всегда будет нечетным. Так мы обеспечиваем кворум. В различных гайдах и инструкциях рекомендуется использовать три или пять узлов, максимум — семь. Если сделать больше узлов, это снизит производительность, поскольку на сбор кворума будет уходить больше времени.
Поясню: у нас практически любая операция должна получить кворум от всех узлов, поэтому чем больше узлов — тем ниже скорость операции.
Чтобы обойти это, можно подключить так называемый Learner, то есть не голосующую ноду. Она, как и остальные, синхронизируется с кластером, но не голосует. Если одна нода у нас выбывает, Learner можно быстро включить.
Что касается кластера Etcd, разделенного между Data-центрами, то, если мы будем использовать два Data-центра, система не будет High Availability. В таком случае лучше использовать третий Data-центр, так называемую кворумную площадку. Это может быть любая виртуальная машина в третьем месте, вплоть до ноутбука системного администратора.
Распределение данных и синхронизация между узлами в Etcd
Когда происходит запись данных, следует избегать больших значений при вызове метода API PUT. Также Etcd лучше применять для хранения метаданных «ключ-значения», которые редко меняются. Еще гайды советуют не создавать большого количества аренд, то есть ключей с TTL, а переиспользовать их по возможности.
Etcd позволяют читать множество ключей одним запросом, передавая массив ключей или стартовый ключ. Также API позволяет отслеживать изменения ключа — эта функция называется Watch. В Etcd реализованы два типа чтения:
Линейное (linearizable): чтение данных с кворума, которое обеспечивает высокую консистентность, это поведение по умолчанию.
Сериализуемое: чтение данных с ближайшего узла, в таком случае консистентность данных не гарантирована.
То есть мы можем выбирать либо скорость, либо консистентность данных.
Ситуации отказа и как с ними работать
Если чтение из Etcd невозможно (например, кластер лежит и все ноды недоступны), мы можем восстановить кластер из снапшота. Etcd поддерживает создание снапшотов и позволяет легко восстановить кластер даже в случае аварийной ситуации.
Если запись в Etcd невозможна (например, после превышения лимита хранилища), кластер может перейти в режим обслуживания. В этом случае доступны функции чтения и удаления. Далее мы чистим место и проводим дефрагментацию.
Если был потерян кворум, то кластер деградирует и переходит в режим исключительно чтения, когда запись невозможна. Решение в таком случае — пересоздать кластер из снапшота.
Основные функции Etcd
Мы использовали Etcd для хранения конфигурации нашего сервиса, для сервис-дискавери и для хранения состояния системы.
Хранение конфигурации.
Etcd — это система «ключ-значение», то есть мы можем в ключах хранить любые конфигурации. Ключи плоские, но у нас есть префиксы и диапазоны ключей. Они позволяют нам обеспечивать иерархичность для хранения конфигурации.Хранение состояния приложения.
Состояние, хранимое в базе, всегда будет отставать от истинного состояния системы. Причина простая: чтобы считать состояние, записать его, обновить всегда требуется время. Также сервис сам не может постоянно отчитываться о своем состоянии, так как любой сбой в сервисе сделает его ненадежным рассказчиком.
Поэтому нам нужен внешний наблюдатель — сервис, который будет отслеживать состояние и следить, чтобы состояние соответствовало источнику истины. В Kubernetes используется такой паттерн, как Reconciliation Loop, когда наблюдатель постоянно отслеживает состояние подов, изменяет запись о состоянии или приводит под в нужное состояние.
Сервис Discovery.
При создании записи о сервисе мы использовали ключ с TTL. Для отслеживания состояния сервисов мы опять же использовали Watch, то есть уведомления об изменении ключа. Таким образом, с помощью Etcd мы реализовали самописную систему сервис-дискавери.
Особенности Etcd
Производительность
Две вещи, на которые нужно смотреть, когда говоришь о производительности Etcd — это скорость сети и скорость диска. Так как система у нас кворумная, от скорости сети зависит, с какой скоростью достигается кворум. Скорость диска влияет на запись и на чтение данных. Также на производительность может повлиять объем данных. Лимит по умолчанию — полтора мегабайта. Если увеличить его, то производительность может пострадать.
Скорость чтения и записи на диск влияет на скорость всей системы. Поэтому советую, во-первых, использовать отдельный диск для Etcd, а во-вторых, использовать высокоскоростные диски или SSD. Также важны настройки FS.
Геораспределенность тоже влияет на скорость сообщения между узлами. Чем выше пинг, тем ниже скорость на всех операциях, требующих кворума. Также некоторые проблемы в производительности были связаны с аутентификацией. Например, в старых версиях Etcd для аутентификации использовался Becrypt, которому требуется около 100 миллисекунд для сравнения хэшей паролей.
Масштабирование
Кластер Etcd ограничен по горизонтальному масштабированию, так как для записи и для чтения требуется консенсус между узлами. При этом, если мы увеличим количество узлов, скорость упадет. Лимита по количеству узлов нет, но максимально рекомендованное число — семь.
Можно сказать, что в ETCD нет шардинга, то есть в случае, если нам недостаточно семи узлов, нам придется создать еще один независимый кластер.
Геораспределенность
Для геораспределенного кластера нужна дополнительная настройка. В ней нужно обратить внимание на два временных параметра — хартбит-интервал и Election Timeout.
Хартбит-интервал — это, по сути, максимальное время обмена хартбитами между узлами.
Election Timeout — это время, которое проходит при неотклике узла, прежде чем начинается выбор нового лидера кластера.
Хартбит-интервал мы подобрали 200 миллисекунд, хотя по умолчанию он стоит 100. Election Timeout лучше ставить от 10 до 50 раз от хартбита. Мы поставили 500 миллисекунд.
Емкость хранилища
Хранилище Etcd по умолчанию ограничено объемом 2 ГБ. Объем хранилища можно настроить опцией Quota Backend Bytes. Но этого всё равно мало. Максимум — 8 ГБ.
Ограничение на размер KV-пары в Etcd — по умолчанию 1,5 Мб. За это отвечает опция Max Request Bytes. Когда свободное место кончается, в Etcd рассылается тревожное оповещение. После этого кластер Etcd переходит в режим обслуживания, где разрешено чтение и удаление.
В таком случае следует удалить лишние KV-пары, дефрагментировать базу данных командой etcdctl defrag и снять тревогу.
Безопасность
Etcd позволяет настраивать пользователям роли и включать аутентификацию. А в случае, если вы храните ключи-пароли и другую чувствительную информацию, можно включить шифрование.
Заключение
Etcd хорошо подходит, если нужно построить распределенную кластеризируемую систему. При этом вам не придется разрабатывать собственные кластерные алгоритмы. И у Etcd есть неоспоримые плюсы:
низкий порог вхождения;
поддержка большинства современных языков;
наличие библиотек;
простой интерфейс API;
высокая отказоустойчивость.
Как итог, Etcd позволяет обеспечить стабильную работу системы в условиях высоких нагрузок и сбоев.
Статья написана на основе доклада Петра Растегаева с HighLoad Conf 2024. Если у вас остались вопросы, задавайте в комментариях — постараюсь ответить.
Что еще почитать
Комментарии (2)
PetyaUmniy
19.07.2024 06:23+1А чем инжектите конфигурацию в простые "тупые" приложения только с текстовым конфигом, например nginx? Через confd? В нем последний осмысленный коммит был пару лет назад, а предпоследний 6 лет назад... Перспективы мягко говоря туманны.
Раньше пока consul не стал проприетарью, была отличная связка: consul + consul-template. Я для себя до сих пор не нашел сравнимой по функционалу открытой альтернативы.
У вас нет решения для этого?
321785
Возникла мысль, что бы исключить split-brain при чётном количестве участников, можно:
1. в установленный промежуток времени получить случайное число из заданного диапазона;
2. определить за установленное время количество участников (работаем с теми кто успел ответить) и получить от них значения ;
3. определить чётность этого числа, если не чётное то выход;
4. если число участников чётное, то сравнить полученные ранее числа. У кого больше тот получает +1 голос при голосовании;
5. если возникла коллизия и числа равны, то повторить п. 1-4
6. Повторить условно каждые 30 секунд.