Эта статья будет полезна DevOps-инженерам, SRE-специалистам и всем, кто работает с Kubernetes и хочет глубже понять его внутренние механизмы. Если вы настраиваете, масштабируете или устраняете неполадки в кластере K8s, важно разобраться в etcd — распределенном key-value-хранилище, которое лежит в основе отказоустойчивости Kubernetes.
Надо отметить, что etcd обеспечивает консистентность и надежное хранение критически важных данных: состояния нод, конфигураций, секретов и другой информации кластера. Без него Kubernetes не мог бы гарантировать высокую доступность и согласованность данных.
В этой статье мы разберем распространенные мифы о etcd, а также дадим практические рекомендации по его настройке и эксплуатации.
В основе материала — перевод опубликованных исследований инженеров Red Hat. Примечание редактора: Нам показалось, что авторы хорошо знакомы с механизмами etcd, но мало разбираются в работе СХД, поэтому мы дополнили перевод своими комментариями.
Миф о доступности и производительности etcd
В Kubernetes ноды могут иметь одну из двух базовых ролей: управляющую или рабочую.
Для кластера предусмотрены дополнительные роли, которые являются производными рабочих ролей и зависят от уникальных инфраструктурных характеристик (например, от типа центрального процессора, сетевых интерфейсов, графических процессоров, матриц FPGA и т.д.), а также от запускаемых приложений — это могут быть инфраструктурные сервисы, программы для разработки и т.д.
Управляющие ноды образуют управляющий слой в Kubernetes: на нем размещаются компоненты, ответственные за принятие решений на уровне кластера. Они отвечают за планирование, реагирование на события на нодах, хранение артефактов для всех объектов на уровне кластера — от манифестов приложений и сервисных аккаунтов до секретов и расширений для интерфейсов API Kubernetes в формате CustomResourceDefinitions и многое другое.

Компоненты управляющего слоя Kubernetes не сохраняют информацию о своем состоянии. Вся информация о кластере сохраняется в хранилищах «ключ-значение» (key-value) etcd.
Хранилище etcd представляет собой единственный источник достоверных данных для кластера, поэтому его утрата может повлечь за собой катастрофический отказ кластера Kubernetes. К счастью, компонент etcd можно запустить в различных конфигурациях, которые обеспечат высокую отказоустойчивость и сохранение работоспособности во множестве сценариев отказа.
Однако, несмотря на это, существует тонкая грань между отказоустойчивостью, производительностью записи и минимальным необходимым числом нод для обеспечения кворума.

Например, кластер etcd с двумя нодами не будет высокодоступным, поскольку в случае утраты одной из нод он лишится кворума и перейдет в режим read-only. В то же время трехнодовая конфигурация кластера etcd является минимально возможной для обеспечения высокой доступности, но в нем минимум две ноды должны функционировать на постоянной основе. Так или иначе трехнодовый кластер etcd соответствует минимальным требованиям к конфигурации, обеспечивающей высокую доступность.
Производительность записи и кворум
А что если использовать больше нод etcd?
По мере увеличения числа нод etcd снижается производительность записи. Это происходит из-за более интенсивного межсетевого взаимодействия и возрастания числа операций записи на диск, которые должны выполнить ноды, составляющие кворум, до того, как запись в базу будет произведена всеми нодами в составе etcd.
Чтобы понять принципы работы этого сценария, давайте рассмотрим, как работает запись в etcd. Сначала учтем несколько условий:
Для согласования работы всех участников в etcd назначается только одна ведущая нода на кластер.
Только ведущая нода может координировать запись пары «ключ-значение» в кластере etcd.
Клиент etcd случайным образом выбирает ноду в составе etcd для взаимодействия, поэтому заранее неизвестно, будет ли определенная нода ведущей или ведомой.
Когда ведомая нода получает запрос на создание или изменение пары «ключ-значение», она пересылает запрос ведущей ноде, затем ждет, пока запись будет внесена в брокер и подтверждена/отклонена ведущей нодой, и после этого отправляет подтверждение клиенту.
Только одна из нод кластера etcd, выбранная с помощью протокола консенсуса, назначается ведущей.
Ведущая нода в обязательном порядке отправляет периодические уведомления heartbeat (HEARTBEAT_INTERVAL) ведомым нодам для подтверждения своего статуса.
Если ведомые ноды не получают уведомления heartbeat в заданный период (ELECTION_TIMEOUT), запускается процедура выбора новой ведущей ноды.
При отсутствии кворума или недоступности ведущей ноды кластер etcd не допускает обновлений или записи. С точки зрения клиента etcd кластер переходит в режим read-only.
С учетом изложенных выше предпосылок давайте рассмотрим процедуру записи или создания «ключ-значение» в etcd:
?? Шаг 1. Ведущая нода добавляет запись пары «ключ-значение» в журнал предварительной записи (WAL). Для этого требуется совершить системный вызов fsync(), который завершается, только когда все записываемые данные оказываются на диске. Таким образом, время выполнения этого вызова напрямую зависит от задержки дискового устройства. Диск с низкой скоростью или использование диска для других процессов может увеличивать задержку для fsync.
?? Шаг 2а. Ведущая нода уведомляет ведомые ноды об изменениях. Это действие включает в себя сетевое взаимодействие и потому зависит от сетевой задержки.
?? Шаг 2b. Ведомые ноды добавляют запись в свой локальный журнал WAL. Для этого требуется совершить вызов fsync(), время выполнения которого напрямую зависит от задержки дискового устройства.
?? Шаг 2c. Ведомые ноды уведомляют ведущую о факте записи пары «ключ-значение» в локальный WAL. Это действие включает в себя сетевое взаимодействие и потому зависит от сетевой задержки.
?? Шаг 3. Ведущая нода ожидает подтверждения от большинства (кворума) нод и производит запись пары «ключ-значение». Для этого требуется совершить еще один вызов fsync(), время выполнения которого напрямую зависит от задержки дискового устройства.
?? Шаг 4а. Ведущая нода уведомляет ведомые о внесении записи. Это действие включает в себя сетевое взаимодействие и потому зависит от сетевой задержки.
?? Шаг 4b. После получения подтверждения от ведущих нод ведомые выполняют запись пары «ключ-значение». Для этого требуется совершить вызов fsync(), время выполнения которого напрямую зависит от задержки дискового устройства.

После рассмотрения описанных выше шагов становится очевидной прямая зависимость производительности etcd от числа нод в кластере etcd. Чем больше нод, тем больше сетевых взаимодействий и процедур записи на диск требуется для достижения консенсуса. Это дисковое и сетевое взаимодействие осложняется задержками записи в etcd и, кроме того, обуславливает количество операций записи, которое может быть выполнено за одну секунду.
Перечисленные ниже рекомендации гарантируют оптимальную производительность etcd:
Запускайте etcd на блочном устройстве, способном последовательно записывать минимум 50 операций ввода/вывода размером 8 Кб, в т.ч. fdatasync, с задержкой менее чем 10 мс. Для кластеров с высокой нагрузкой рекомендуется последовательная запись 500 операций ввода/вывода размером 8000 байт с задержкой менее 2 мс.
Используйте для etcd выделенные диски. Избегайте применения сетевых дисков, например, типа iSCSI (это сложновыполнимое условие, которое не имеет смысла на обсуждаемых требованиях к производительности. Просто избегайте использования сетевых протоколов на ненадежной сети, например через интернет, VPN, растянутые сети между удаленными ЦОДами и т.п. — прим. редактора). Не используйте эти диски для хранения журналов (логов) или для запуска прочих тяжелых рабочих нагрузок.
Отдавайте предпочтение дискам с низким уровнем задержки, чтобы гарантировать быстрое чтение и запись.
Выбирайте диски с высокой пропускной способностью на операциях чтения для более быстрого восстановления после отказа.
Избегайте использование любых накопителей, кроме твердотельных. Для производственной среды предпочтительнее выбирать диски NVMe (SSD вполне подойдет — прим редактора).
Для большей надежности используйте оборудование серверного уровня.
Не используйте сетевые хранилища NAS или SAN, а также жесткие диски с вращающимся приводом. Блочные устройства Ceph Rados и тому подобные сетевые хранилища данных могут вызывать непредсказуемую сетевую задержку. Для того чтобы обеспечить ноды etcd быстрой дисковой подсистемой в большом масштабе, используйте настройку PCI Passthrough для проброса NVM устройств непосредственно на ноды (опять-таки это избыточное требование, приносящее слишком много проблем. SSD-диски по iSCSI или FC поверх надежной сети подойдут ничуть не хуже — прим. редактора).
Всегда проверяйте работу системы с помощью утилит типа fio. Такие утилиты можно применять для регулярного контроля работы кластера по мере его расширения.
Миф о задержке и стабильности кластера etcd
Помимо производительности записи большое значение для общей стабильности кластера etcd имеют задержка дискового устройства и сетевая задержка.
Для etcd есть два важнейших параметра:
HEARTBEAT_INTERVAL (по умолчанию 100 мс): частота, с которой ведущая нода уведомляет ведомые о своей роли;
ELECTION_TIMEOUT (по умолчанию 1000 мс): столько ведомая нода выжидает без получения уведомления heartbeat, прежде чем попытается стать ведущей.
От этих таймеров зависит не только стабильность самого кластера, но и принятие решений, связанных с архитектурой и развертыванием кластеров Kubernetes.
Возьмем период heartbeat, который применяется при отправке ведущей нодой уведомлений о ее роли ведомым нодам. По умолчанию его продолжительность составляет 100 мс. Если период отправки сообщений больше, чем заданный интервал, то уведомления heartbeat могут поступать с задержкой. При этом система разработана таким образом, чтобы обрабатывать уведомления heartbeat, поступившие с задержкой или пропавшие из-за проблем в сети. Здесь вступает в игру election timeout — время ожидания выбора лидера. По умолчанию время ожидания составляет 1000 мс (1 секунду), т.е. в 10 раз больше, чем период heartbeat. Таким образом, даже если мы пропустим девять последовательных уведомлений heartbeat, ведущая нода сохранит свою роль.
Эти два параметра рассматриваются в первую очередь, когда речь заходит о развертывании управляющего слоя Kubernetes, который объединяет множество дата-центров. Но фокусируясь только на них, мы упускаем из виду влияние задержки на производительность записи в кластере ectd и количество операций, совершаемых за одну секунду API-сервером Kubernetes, а также на последствия при взаимодействии с ведущей нодой.
Задержка: общая картина
Выше мы рассмотрели основные принципы работы системы и разобрались, как именно задержка ввода/вывода на диске и сетевая задержка влияют на задержку выполнения операций при записи в хранилище etcd пары «ключ-значение». Теперь, с учетом параметров etcd, мы сможем лучше разобраться с тем, как именно задержка влияет на работу кластера.
Любой хороший проект etcd содержит рекомендованные требования к диску для кластера etcd, а также рекомендованные значения для настроек etcd с учетом сетевой задержки. Однако при учете таких рекомендаций не забывайте, что значения обоих параметров не являются фиксированными величинами — со временем они могут меняться. Сетевая задержка зависит от многих факторов: например, от сетевой загруженности канала, фрагментации, потери пакетов, перенаправления трафика и т.д. Задержка записи на диск также может быть обусловлена многими факторами: «шумные соседи» (процессы/приложения, которые пишут на тот же диск и могут влиять на время записи), перенасыщение буфера записи и т.д. Все перечисленные факторы могут возникать не сразу, с течением времени. Одну из вариаций задержки также называют «джиттер». Фактически джиттер можно описать как стандартное изменение задержки через определенный промежуток времени.
В чем важность джиттера? Давайте рассмотрим сеть с джиттером 15 мс и задержкой 90 мс между двумя нодами. Получается, пакеты поступают с задержкой от 75 мс до 105 мс. При задержке более 100 мс уведомления heartbeat могут не поступать в пределах заданного интервала. Кроме того, в том, что касается хранения, джиттер влияет и на задержку записи, увеличивая время, необходимое для установления консенсуса в системе, и тем самым отрицательно сказывается на производительности.
Теперь давайте объединим все параметры, которые мы обсудили, и рассмотрим реальные кейсы.

Пример №1. Типовое развертывание, при котором ноды управляющего слоя размещаются в одном и том же дата-центре. Этот вариант мы примем за контрольную группу — с идеальными сетью и диском — и зададим для нашей сети следующие параметры:
Сетевая задержка |
Сетевой джиттер |
|
Одна зона |
10 мс |
2 мс |
Белая — желтая |
75 мс |
10 мс |
Белая — зеленая |
90 мс |
15 мс |
Желтая — зеленая |
80 мс |
15 мс |
|
Задержка записи на диск |
Джиттер записи на диск |
Белая |
5 мс |
1 мс |
Желтая |
10 мс |
3 мс |
Зеленая |
20 мс |
5 мс |
Варианты 2 и 3 представляют собой развертывание с размещением трехнодового кластера etcd в двух дата-центрах, обозначенных белым и желтым цветами. Разница между этими сценариями в том, где ведущая нода находится в определенный момент времени.
Помните, да? При выборе ведущей ноды любая нода может получить эту роль при наличии консенсуса между членами etcd. В обоих сценариях (2 и 3) клиент etcd находится в локации, обозначенной белым цветом.
Рассмотрим поток записи для сценария №2 на примере приведенной ниже схемы:

На основании данных, представленных в таблице выше, рассчитываем задержку при записи клиентом etcd по второму сценарию: 335 мс ± 54 мс. Получается, что система может производить от 2,5 до 3,5 записей в секунду. (на самом деле, это производительность только одного потока. В реальности база пишет в многопоточном режиме, и общая производительность значительно выше. Но одно и то же значение (или другие однопоточные операции) будут выполняться с такой скоростью — прим. редактора).
Как мы получили такое число?
Шаг 1: (10 мс ± 2 мс) + (10 мс ± 2 мс) = 20 мс ± 4 мс
Шаг 2: (5 мс ± 1 мс) + (10 мс ± 2 мс) + (75 мс ± 10 мс) = 90 мс ± 13 мс
Шаг 3: (5 мс ± 1 мс) + (10 мс ± 3 мс) + (10 мс ± 2 мс) + (75 мс ± 10 мс) + (5 мс ± 1 мс) = 105 мс ± 17 мс
Шаг 4: (10 мс ± 2 мс) + (75 мс ± 10 мс) + (5 мс ± 1 мс) + (10 мс ± 3 мс) = 100 мс ± 16 мс
Шаг 5: (10 мс ± 2 мс) + (10 мс ± 2 мс) = 20 мс ± 4 мс
Затем делим 1 с (1000 мс) на (335 мс ± 54 мс), и получаем диапазон 2,5–3,5.
Обратите внимание: для этого примера мы предполагаем, что клиент etcd подключается к локальной ноде etcd. В другой ситуации задержка записи была бы больше.
Если мы проделаем все то же самое для третьего примера, разница будет в том, что ведущая нода окажется в желтой зоне, а задержка записи для клиента составит 660 мс ± 94 мс, что соответствует диапазону 1,3–1,7 записей в секунду (см. выше — прим. редактора). Иными словами, производительность записи будет примерно на 50% меньше.
Таким образом, общая задержка на шаг при работе по сценарию №3 составит:
Шаг 1: 85 мс ± 12 мс
Шаг 2: 160 мс ± 23 мс
Шаг 3: 160 мс ± 22 мс
Шаг 4: 170 мс ± 25 мс
Шаг 5: 85 мс ± 12 мс
Теперь, наконец, обратимся к четвертому примеру, в рамках которого клиент взаимодействует с ведомой нодой в зеленой зоне.

Если мы произведем для него аналогичные предыдущим сценариям расчеты, то увидим, что задержка для клиента etcd составит 1775 мс ± 153 мс, т.е. 0,5–0,6 записей в секунду. (см. выше — прим. редактора)
Итоги
Во всех случаях речь идет исключительно о трехнодовых кластерах etcd. Как было сказано выше, при увеличении количества нод задержка записи растет по экспоненте. Приведенные расчеты наглядно демонстрируют, как именно снижается производительность записи в кластере etcd.
Можно сделать вывод, что сетевая задержка, задержка диска, соответствующий джиттер и количество используемых членов etcd связаны между собой куда теснее, чем может показаться на первый взгляд. Тестирование и апробация конфигурации Kubernetes с применением определенных настроек etcd — непростая задача. Важно понимать, как именно система будет работать в заданных условиях. Более того, необходимо разбираться в том, как изменение характеристик etcd может сказаться на производительности.
Если вы еще не с нами, присоединяйтесь к нашему K8s-сообществу в Telegram — там много всего полезного вокруг кубера.