Kubernetes Internals: От архитектуры Control Plane до разработки собственных операторов

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

1. Архитектура Control Plane и жизненный цикл API-запроса: механизмы аутентификации, авторизации и Admission Control

Когда администратор выполняет команду kubectl run nginx --image=nginx, в кластере не запускается ни одного контейнера. Kubernetes вообще не знает, что такое Docker, containerd или процессы операционной системы. Единственное, что происходит в этот момент — клиент формирует JSON-документ, описывающий желаемое состояние, и отправляет его через HTTP POST-запрос к единственному открытому порту кластера. Всё, что мы называем «магией оркестрации», — это лишь каскад асинхронных реакций системных компонентов на изменение данных в центральном хранилище.

Центральным узлом этой системы выступает Control Plane — управляющий слой кластера, который принимает решения, хранит состояние и координирует работу узлов. Понимание того, как именно Control Plane обрабатывает входящие запросы, является фундаментом для траблшутинга, настройки безопасности и разработки собственных расширений (операторов).

Архитектура Control Plane: единая точка входа

Управляющий слой Kubernetes спроектирован вокруг принципа декларативного управления состоянием. Он состоит из нескольких независимых компонентов, которые взаимодействуют друг с другом исключительно через REST API.

  • kube-apiserver — сердце кластера. Это единственный компонент, с которым напрямую общаются пользователи, рабочие узлы (через kubelet) и другие элементы Control Plane. Он не выполняет никакой бизнес-логики по управлению контейнерами, его задача — валидация запросов, применение политик безопасности и сохранение состояния.
  • etcd — распределённое key-value хранилище, выступающее единственным источником истины (Source of Truth) для кластера. Важнейшее архитектурное правило: никто, кроме kube-apiserver, не имеет прямого доступа к etcd.
  • kube-scheduler — компонент, который непрерывно опрашивает API-сервер на предмет появления новых Pod'ов без назначенного узла (поле nodeName пустое). Найдя такой Pod, планировщик вычисляет оптимальный узел и отправляет обратно в API-сервер запрос на обновление (Binding).
  • kube-controller-manager — набор фоновых процессов (контроллеров), которые следят за расхождением между текущим состоянием кластера и желаемым (описанным в etcd). Если вы создаете объект Deployment, именно контроллер Deployment заметит это и создаст соответствующий ReplicaSet, который, в свою очередь, создаст Pod.
  • !Архитектура Control Plane Kubernetes

    Эта архитектура реализует паттерн «Hub and Spoke», где API-сервер является ступицей (hub), а все остальные компоненты — спицами (spokes). Ни один контроллер не вызывает другой контроллер напрямую. Все они работают по принципу Level-Triggered логики: подписываются на изменения ресурсов через механизм Watch (постоянное HTTP-соединение с потоковой передачей событий) и реагируют на новые или изменённые объекты.

    Жизненный цикл API-запроса

    Поскольку kube-apiserver является единственной дверью в кластер, он должен быть абсолютно надёжным стражем. Когда HTTP-запрос достигает API-сервера, он проходит через строгий конвейер обработки (Request Pipeline). Если запрос не проходит хотя бы один этап, он немедленно отклоняется.

    !Конвейер обработки API-запроса

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

    1. HTTP-обработчик и терминация TLS

    Любое взаимодействие с kube-apiserver происходит по протоколу HTTPS. На первом этапе сервер принимает соединение, выполняет TLS-рукопожатие и расшифровывает трафик. Здесь же срабатывают базовые HTTP-фильтры:

  • Timeout filter: предотвращает бесконечные соединения (по умолчанию таймаут составляет 60 секунд для обычных запросов).
  • Max in-flight requests: защита от перегрузки (Rate Limiting). API-сервер ограничивает количество одновременно обрабатываемых мутирующих (POST, PUT, DELETE) и немутирующих (GET) запросов. Если лимит превышен, сервер возвращает статус 429 Too Many Requests.
  • После успешного прохождения базовых сетевых фильтров сервер извлекает HTTP-заголовки, URI и тело запроса, чтобы передать их подсистеме безопасности.

    2. Аутентификация (Authentication / AuthN)

    Задача этого этапа — ответить на вопрос: «Кто делает этот запрос?».

    В Kubernetes нет встроенного объекта User или Group в виде ресурсов, которые можно было бы создать через kubectl create user. Кластер делегирует управление пользователями внешним системам. API-сервер последовательно прогоняет запрос через цепочку настроенных аутентификаторов. Как только один из них успешно распознает пользователя, процесс останавливается, и запрос передается дальше.

    Основные механизмы аутентификации:

    X.509 Клиентские сертификаты Это стандартный метод аутентификации администраторов и компонентов кластера. API-сервер проверяет, подписан ли сертификат клиента доверенным удостоверяющим центром (CA кластера). Если подпись верна, сервер извлекает идентификационные данные прямо из полей сертификата:

  • Поле CN (Common Name) становится именем пользователя (например, CN=admin).
  • Поля O (Organization) интерпретируются как группы (например, O=system:masters).
  • Bearer Tokens (OIDC / ServiceAccounts) Для интеграции с корпоративными системами (Active Directory, Google Workspace) используется OpenID Connect (OIDC). Пользователь получает JWT-токен от внешнего провайдера и прикрепляет его к запросу в заголовке Authorization: Bearer <token>. API-сервер проверяет криптографическую подпись токена с помощью публичного ключа провайдера и извлекает из поля claims имя пользователя и группы.

    Отдельный класс токенов — токены ServiceAccount. В отличие от обычных людей-пользователей, ServiceAccounts — это полноценные объекты внутри Kubernetes, предназначенные для аутентификации самих приложений (Pod'ов). Когда Pod запускается, kubelet монтирует токен ServiceAccount внутрь контейнера по пути /var/run/secrets/kubernetes.io/serviceaccount. Приложение может использовать этот токен для легитимного обращения к API-серверу.

    Если ни один аутентификатор не смог опознать субъекта, запрос либо отклоняется со статусом 401 Unauthorized, либо помечается как system:anonymous (если анонимный доступ разрешен в конфигурации).

    3. Авторизация (Authorization / AuthZ)

    Узнав, кто пришел, API-сервер должен выяснить: «Имеет ли этот субъект право выполнить запрошенное действие?».

    Запрос трансформируется во внутреннюю структуру AttributesRecord, которая содержит:

  • Имя пользователя и список его групп.
  • HTTP-глагол, преобразованный в API-глагол (POST → create, GET → get или list, PUT → update).
  • Запрашиваемый ресурс (например, pods) и его API-группу.
  • Пространство имен (Namespace).
  • В Kubernetes используется парадигма Deny-by-Default (запрещено всё, что не разрешено явно). Запрос проходит через модули авторизации (Node, ABAC, RBAC, Webhook). Самым распространенным и гибким является RBAC (Role-Based Access Control).

    Механизм RBAC строится на трех китах:

  • Subject — субъект (User, Group или ServiceAccount).
  • Role / ClusterRole — набор правил. Правило описывает, что можно делать. Например: «разрешено выполнять get и list для ресурсов pods в группе core». Role действует в рамках одного Namespace, а ClusterRole — на уровне всего кластера.
  • RoleBinding / ClusterRoleBinding — связка. Она соединяет Subject с Role.
  • Когда приходит запрос, модуль RBAC ищет все Binding'и, связанные с данным пользователем и его группами. Затем он проверяет, покрывают ли правила из связанных ролей запрашиваемое действие. Если найдено хотя бы одно совпадение — запрос авторизован. Если после проверки всех политик совпадений нет, возвращается 403 Forbidden.

    > Важный нюанс: RBAC не умеет проверять содержимое запроса. Он может разрешить пользователю создавать Pod'ы, но не может запретить создавать Pod'ы, требующие больше гигабайт памяти, или Pod'ы, использующие образ nginx:latest. Для инспекции полезной нагрузки используется следующий этап.

    4. Admission Control (Контроль допуска)

    Это самый сложный и мощный этап конвейера. В отличие от авторизации, Admission Control имеет доступ к полному JSON-телу запроса. Именно здесь реализуются корпоративные политики, мутации объектов и глубокая валидация.

    Admission Control работает только для мутирующих запросов (CREATE, UPDATE, DELETE). Запросы на чтение (GET, LIST) этот этап пропускают.

    Процесс разделен на две последовательные фазы: Mutating Admission (изменение) и Validating Admission (проверка).

    #### Фаза 4.1: Mutating Admission На этом этапе контроллеры могут модифицировать входящий объект до его сохранения. В Kubernetes встроено множество таких контроллеров. Например, DefaultStorageClass автоматически добавляет класс хранилища по умолчанию к объектам PersistentVolumeClaim, если пользователь его не указал.

    Помимо встроенных плагинов, администраторы могут создавать собственные с помощью MutatingAdmissionWebhook. Это механизм, позволяющий API-серверу сделать внешний HTTP-вызов к вашему собственному сервису.

    Классический пример использования Mutating Webhook — инъекция sidecar-контейнеров в Service Mesh (например, Istio).

  • Пользователь отправляет манифест Pod'а с одним контейнером.
  • API-сервер отправляет этот манифест в вебхук Istio.
  • Вебхук Istio анализирует манифест и возвращает ответ в формате JSONPatch, который гласит: «добавь в массив containers еще один контейнер с образом envoy-proxy».
  • API-сервер применяет патч к объекту в памяти.
  • Поскольку мутирующие вебхуки могут изменять объект последовательно, и один вебхук может изменить данные, которые важны для другого, API-сервер может прогнать объект через цепочку мутаций несколько раз, пока состояние не стабилизируется.

    #### Фаза 4.2: Object Schema Validation После завершения всех мутаций API-сервер проверяет итоговый объект на соответствие OpenAPI-схеме. Если в манифесте указано строковое значение там, где ожидается число (например, replicas: "три"), запрос будет отклонен на этом этапе.

    #### Фаза 4.3: Validating Admission Финальный этап проверки. Здесь объект уже нельзя изменить — можно только сказать «да» или «нет».

    Встроенные контроллеры, такие как ResourceQuota или LimitRanger, проверяют, не превышает ли создаваемый объект выделенные лимиты ресурсов в Namespace.

    Как и в случае с мутациями, здесь можно использовать ValidatingAdmissionWebhook. Это основной инструмент для обеспечения безопасности и комплаенса. Инструменты вроде OPA Gatekeeper или Kyverno работают именно как Validating Webhooks. Они могут, например, отклонить запрос, если образ контейнера скачивается не из доверенного приватного реестра, или если контейнер пытается запуститься в привилегированном режиме (securityContext.privileged: true).

    Если вебхук недоступен (например, сервис упал), поведение API-сервера определяется параметром failurePolicy в конфигурации вебхука. Значение Fail означает, что запрос будет отклонен (строгая безопасность), а Ignore — что запрос пройдет дальше (высокая доступность, но риск нарушения политик).

    5. Сохранение в etcd (Persistence)

    Если запрос успешно прошел аутентификацию, авторизацию и обе фазы Admission Control, API-сервер сериализует итоговый объект и инициирует транзакцию записи в etcd.

    Здесь вступает в игру механизм оптимистичной блокировки (Optimistic Concurrency Control), который предотвращает состояние гонки (race conditions) при одновременном обновлении одного объекта разными контроллерами.

    Каждый объект в Kubernetes имеет скрытое поле metadata.resourceVersion. Это строковое представление внутреннего счетчика ревизий etcd. Когда контроллер хочет обновить объект, он отправляет PUT-запрос, в котором указывает ту resourceVersion, которую он прочитал ранее. API-сервер передает этот запрос в etcd. Если текущая версия в базе данных совпадает с присланной, запись происходит успешно, и resourceVersion инкрементируется. Если же другой контроллер успел обновить объект долей секунды раньше, версия в базе уже изменилась. В этом случае etcd отклоняет транзакцию, а API-сервер возвращает клиенту ошибку 409 Conflict.

    Получив 409 Conflict, клиент (например, ваш кастомный оператор) обязан заново прочитать актуальное состояние объекта из API, заново применить свои изменения и повторить попытку записи.

    Завершение жизненного цикла

    После успешного сохранения данных в etcd, API-сервер формирует HTTP-ответ (например, 201 Created для новых объектов) и отправляет его клиенту.

    Но на этом работа кластера только начинается. Как только транзакция зафиксирована в etcd, API-сервер генерирует событие Added или Modified и рассылает его всем компонентам, которые удерживают открытое соединение Watch для данного типа ресурсов. Планировщик видит новый Pod и начинает искать для него узел. Kubelet на выбранном узле видит, что ему назначен Pod, и начинает скачивать образы. Контроллер конечных точек (Endpoint Controller) замечает появление новых IP-адресов и обновляет правила маршрутизации.

    Каждый компонент выполняет свою узкую задачу, реагируя на изменение состояния в базе данных. Именно эта хореография, управляемая строгими правилами API-сервера, делает Kubernetes столь масштабируемой и расширяемой платформой. Понимая каждый шаг конвейера обработки запроса, инженер получает возможность не просто использовать кластер, но и органично встраивать в него собственную логику на любом этапе — от кастомной аутентификации до динамической мутации ресурсов на лету.