1. Архитектура отказоустойчивого кластера Kubernetes: компоненты Control Plane и механизмы высокой доступности
Архитектура отказоустойчивого кластера Kubernetes: компоненты Control Plane и механизмы высокой доступности
В любой распределенной системе отказ оборудования — это не вероятность, а вопрос времени. Серверы теряют питание, сетевые коммутаторы выходят из строя, а дисковые массивы повреждают данные. Если кластер Kubernetes управляется единственным сервером (мастер-нодой), выход этого сервера из строя означает полную потерю контроля над всей инфраструктурой: приложения продолжат работать, но вы не сможете их обновить, масштабировать или перезапустить упавшие контейнеры. Чтобы система выдерживала аппаратные сбои без деградации управления, архитектуру изначально проектируют как отказоустойчивую (High Availability, HA).
Кубернетес логически разделен на две плоскости: Control Plane (плоскость управления) и Data Plane (плоскость данных, или воркер-ноды). Воркер-ноды выполняют полезную нагрузку — запускают контейнеры. Control Plane принимает решения: где запустить контейнер, как маршрутизировать к нему трафик и что делать, если воркер-нода перестала отвечать.
В этой статье мы разберем анатомию Control Plane, заглянем внутрь каждого управляющего компонента и поймем, за счет каких механизмов кластер выживает при потере серверов. Эта теория станет фундаментом для нашей практической задачи — развертывания HA-кластера на базе Debian 12 с тремя мастер-нодами, двумя воркерами и балансировщиком нагрузки.
Топология отказоустойчивости
Чтобы Control Plane стал отказоустойчивым, мы должны запустить его компоненты в нескольких экземплярах на разных физических или виртуальных машинах. Однако просто скопировать процессы недостаточно. Разные компоненты Kubernetes требуют разных подходов к обеспечению отказоустойчивости. Одни могут работать в режиме Active-Active (когда все копии обрабатывают запросы одновременно), другие — только в режиме Active-Passive (когда работает один, а остальные ждут его падения), а третьи требуют строгих математических алгоритмов для достижения консенсуса.
!Архитектура отказоустойчивого кластера Kubernetes
В нашей целевой архитектуре мы будем использовать внешний балансировщик нагрузки (HAProxy + Keepalived) и три независимые мастер-ноды. На каждой мастер-ноде будет запущен полный набор компонентов Control Plane. Давайте разберем каждый из них.
etcd: Хранилище состояния и источник истины
Сердце любого кластера Kubernetes — это etcd. Это распределенное, надежное хранилище пар «ключ-значение». Абсолютно всё состояние кластера хранится здесь: конфигурации, секреты, информация о том, какие поды должны работать, какие работают по факту, и на каких узлах они находятся. Если данные в etcd будут потеряны, кластер перестанет существовать как единое целое. Ни один другой компонент Kubernetes не хранит состояние (они stateless), все они опираются на etcd.
Механизм отказоустойчивости: Алгоритм Raft и Кворум
Поскольку etcd хранит состояние, его экземпляры не могут просто записывать данные независимо друг от друга — это приведет к рассинхронизации (split-brain). Чтобы несколько узлов etcd работали как единое целое, они используют алгоритм консенсуса Raft.
Raft гарантирует, что все узлы согласны с текущим состоянием данных. В каждый момент времени среди узлов etcd выбирается один Лидер (Leader), а остальные становятся Ведомыми (Followers). Все запросы на изменение данных (запись) всегда проходят через Лидера. Лидер получает запрос, отправляет его Ведомым и ждет подтверждения. Запись считается успешной только тогда, когда большинство узлов кластера подтвердят сохранение данных на диск.
Это «большинство» называется кворумом. Кворум — это минимальное количество узлов etcd, которые должны быть доступны для того, чтобы кластер мог принимать решения и записывать данные. Размер кворума вычисляется по формуле:
Где — это размер кворума, — общее количество узлов в кластере etcd, а оператор означает округление вниз до целого числа.
Рассмотрим примеры, чтобы понять, почему в нашей архитектуре именно 3 мастер-ноды:
Именно поэтому etcd всегда разворачивают нечетным числом узлов (3, 5, 7). Четное количество не добавляет отказоустойчивости по сравнению с предыдущим нечетным числом, но увеличивает сетевые задержки на синхронизацию. В нашем проекте мы используем 3 мастер-ноды, что позволяет нам безболезненно пережить отказ любого одного сервера ESXi или виртуальной машины.
!Процесс репликации данных и выбора лидера в etcd
kube-apiserver: Центральный коммуникационный узел
kube-apiserver — это фасад Control Plane. Это единственный компонент, который имеет прямой доступ к etcd. Все остальные компоненты (как управляющие, так и воркер-ноды), а также внешние пользователи (через утилиту kubectl) общаются с кластером исключительно через REST API, предоставляемый kube-apiserver.
Когда вы выполняете команду kubectl apply -f pod.yaml, запрос поступает в API-сервер. Сервер выполняет три этапа:
etcd.Механизм отказоустойчивости: Active-Active и Балансировка нагрузки
В отличие от etcd, kube-apiserver абсолютно не хранит в себе никакого состояния (stateless). Вся память вынесена в etcd. Это архитектурное решение делает масштабирование API-сервера тривиальной задачей: мы можем запустить любое количество экземпляров kube-apiserver, и все они могут обрабатывать запросы одновременно (Active-Active).
Однако возникает проблема маршрутизации: если у нас есть три сервера labs-k8s-m01, labs-k8s-m02 и labs-k8s-m03, куда именно должны отправлять запросы воркер-ноды и администраторы? Если жестко прописать IP-адрес первого мастера, то при его падении кластер станет недоступен, даже если два других мастера работают отлично.
Решением является внешний балансировщик нагрузки. В нашей топологии это сервер labs-k8s-balancer (IP: 10.3.60.50). На нем работает прокси-сервер HAProxy, который принимает весь трафик на порт 6443 и распределяет его (например, по алгоритму Round Robin) между тремя нашими мастер-нодами.
Все компоненты кластера настраиваются на обращение к одному виртуальному IP-адресу (VIP) балансировщика. Если один kube-apiserver выходит из строя, HAProxy замечает это (через health-check) и перестает отправлять на него трафик, перераспределяя нагрузку на оставшиеся два. В enterprise-средах сам балансировщик тоже дублируется с помощью протокола VRRP (Keepalived), чтобы избежать единой точки отказа на уровне проксирования. В нашем проекте мы закладываем Keepalived на одном узле как архитектурный паттерн, который легко масштабируется добавлением второго LB-узла.
kube-scheduler: Диспетчер ресурсов
Когда API-сервер сохраняет в etcd информацию о том, что нужно создать новый Pod, этот Pod изначально не привязан ни к одной воркер-ноде. Его статус — Pending. Здесь в игру вступает kube-scheduler.
Планировщик непрерывно наблюдает за API-сервером на предмет появления подов без назначенной ноды. Как только такой Pod обнаруживается, kube-scheduler запускает сложный процесс принятия решений:
Механизм отказоустойчивости: Active-Passive (Leader Election)
Что произойдет, если мы запустим три экземпляра kube-scheduler и позволим им работать одновременно? Возникнет состояние гонки (race condition). Два планировщика могут одновременно увидеть новый Pod, независимо друг от друга провести вычисления и попытаться назначить его на разные ноды.
Чтобы избежать хаоса, kube-scheduler (как и следующий компонент, Controller Manager) работает в режиме Active-Passive. Используется механизм выбора лидера (Leader Election).
На практике это работает так: все три экземпляра планировщика запускаются, но первым делом они пытаются создать (или захватить) специальный объект блокировки (Lease) в etcd через API-сервер. Тот экземпляр, чей запрос дойдет первым, становится Лидером. Он начинает выполнять свою работу — планировать поды. Два других экземпляра переходят в режим ожидания.
Лидер обязан регулярно обновлять объект Lease (отправлять heartbeat), подтверждая, что он жив. Если сервер с активным планировщиком падает, обновление Lease прекращается. Срок действия блокировки истекает, и оставшиеся в живых экземпляры немедленно пытаются захватить Lease. Новый победитель становится Лидером и продолжает работу с того места, где остановился предыдущий.
kube-controller-manager: Автоматика и циклы согласования
Kubernetes работает по декларативному принципу: вы описываете желаемое состояние (Desired State), а система сама приводит к нему текущее фактическое состояние (Actual State). За это приведение отвечает kube-controller-manager.
Это единый бинарный файл, внутри которого крутятся десятки независимых циклов управления (Control Loops) — контроллеров. Примеры контроллеров:
Как и планировщик, kube-controller-manager использует механизм Leader Election. В каждый момент времени в кластере работает только один активный Controller Manager. Если бы их было несколько, они бы дублировали реакции на одни и те же события, создавая лишние поды или отправляя конфликтующие команды.
Плоскость данных (Data Plane): Краткий обзор
Хотя фокус этой статьи — отказоустойчивость Control Plane, для полноты картины необходимо понимать, с чем взаимодействуют мастера. На серверах labs-k8s-w01 и labs-k8s-w02 будут работать следующие компоненты:
Сценарии отказов: Как архитектура спасает кластер
Чтобы закрепить понимание, смоделируем несколько аварийных ситуаций в нашей будущей инфраструктуре (1 LB, 3 Master, 2 Worker) и проследим за реакцией системы.
Сценарий 1: Полный выход из строя мастер-ноды m01
Допустим, на сервере labs-k8s-m01 сгорела материнская плата.
etcd: В кластере остается 2 узла из 3. Кворум () сохраняется. База данных продолжает принимать операции чтения и записи. Если m01 был Лидером Raft, оставшиеся узлы за несколько миллисекунд выберут нового Лидера.kube-apiserver: Балансировщик labs-k8s-balancer видит, что порт 6443 на m01 не отвечает, и исключает его из пула. Трафик от kubectl и воркеров бесшовно направляется на m02 и m03.kube-scheduler и kube-controller-manager: Если m01 держал блокировку Лидера (Lease), он перестает ее обновлять. Через короткий таймаут компоненты на m02 или m03 перехватывают лидерство.Сценарий 2: Сетевое разделение (Split-Brain)
Представим, что сетевой коммутатор настроен неверно, и мастер-нода m03 теряет связь с m01 и m02, но продолжает «видеть» воркер-ноды.
Это самая опасная ситуация для распределенных систем. Узел m03 считает, что m01 и m02 упали. Он пытается стать Лидером etcd. Однако, чтобы стать Лидером, ему нужны голоса большинства (кворум = 2). Будучи изолированным, он голосует сам за себя (1 голос) и не может собрать кворум. В итоге etcd на m03 переходит в состояние ожидания и отказывается принимать любые изменения.
Тем временем m01 и m02 прекрасно видят друг друга. У них есть 2 голоса из 3 — кворум собран. Они продолжают управлять кластером.
Итог: Алгоритм Raft математически предотвращает ситуацию, когда две части кластера начинают принимать независимые решения, что привело бы к необратимому повреждению данных.
Понимание этих механизмов критически важно для администратора. Разворачивая кластер с помощью kubeadm в следующих главах, мы будем не просто вводить команды по шаблону, а осознанно настраивать сертификаты для общения между тремя etcd, указывать IP-адрес балансировщика для kube-apiserver и проверять статусы захвата лидерства у контроллеров. Архитектура Kubernetes сложна, но эта сложность — плата за возможность системы самостоятельно восстанавливаться после тяжелых инфраструктурных сбоев.