Архитектура control plane: api-server, etcd, scheduler

Рейтинг: 57.8% · 13 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 55
Зарегистрирован: 12 май 2026, 03:23

Архитектура control plane: api-server, etcd, scheduler

Сообщение anton_k8s »

Оглавление курса (48)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
  20. Архитектура control plane: api-server, etcd, scheduler (вы здесь)
  21. Узел кластера: kubelet, kube-proxy и container runtime
  22. Декларативная модель: reconciliation, контроллеры, CRD
  23. kubectl профессионально: get, describe, explain, jsonpath
  24. Под глубже: init, sidecar, lifecycle hooks и QoS
  25. Метки, селекторы и организация ресурсов
  26. Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices
  27. Ingress, ingress-контроллеры и Gateway API
  28. Секреты в кластере: шифрование, External Secrets, Vault
  29. Хранилище глубже: PV, StorageClass, CSI и StatefulSet
  30. Сеть кластера: CNI, NetworkPolicy и CoreDNS
  31. Планировщик: affinity, taints, topology spread
  32. Ресурсы, QoS и вытеснение: requests, limits, eviction
  33. Helm глубже: шаблоны, хуки, зависимости, OCI
  34. Kustomize и управление конфигурацией без шаблонов
  35. RBAC и аутентификация глубже
  36. Admission и Pod Security: контроль на входе
  37. Policy as code: Kyverno и OPA Gatekeeper
  38. GitOps: Argo CD и Flux
  39. Операторы и CRD: расширяем Kubernetes
  40. Сервис-меш: Istio, Linkerd и когда он нужен
  41. Эксплуатация кластера: апгрейд, узлы, бэкап etcd
  42. Где запускать кластер: managed, self-hosted, k3s, Deckhouse
  43. Траблшутинг кластера: Pending, CrashLoop, узлы NotReady
  44. Стоимость и эффективность кластера: FinOps
  45. CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
  46. Лучшие практики и антипаттерны Kubernetes
  47. Сквозной проект и путь дальше: от манифеста до прода
  48. Деплой приложений через Argo CD: Application, sync и App-of-Apps
Ты уже умеешь катить Deployment, ходить kubectl-ом, чинить упавшие поды. Но в какой-то момент кластер ломается не на уровне "под не стартует", а глубже: kubectl висит на таймауте, ничего не планируется, узлы помечаются NotReady пачкой. И тут выясняется, что без понимания, кто в кластере за что отвечает и кто кому звонит, ты просто тыкаешь палкой в тёмную комнату. Этот урок про мозг кластера. Разберём kubernetes архитектуру control plane по косточкам: как устроен api-server, зачем etcd и почему его бэкап - это вопрос жизни и смерти, как kube-scheduler выбирает узел, что делают контроллеры и что конкретно отвалится, если убить каждый из компонентов.

Сразу зафиксируем главную мысль, без неё дальше будет каша. Kubernetes - это не "набор демонов, которые как-то договариваются". Это система с единственной точкой правды (etcd) и единственными воротами к ней (api-server). Всё остальное - клиенты, которые смотрят на состояние через эти ворота и пытаются подтянуть реальность к желаемому. Запомни эту картинку, она объясняет 90 процентов поведения кластера.

Control plane kubernetes: кто где живёт

Узлы кластера делятся на два сорта. Worker-узлы (где крутятся твои поды) несут на себе kubelet, kube-proxy и container runtime (containerd через CRI - dockershim выпилен ещё в 1.24, забудь про него). Control plane - это отдельный набор компонентов, обычно на выделенных master-узлах (их же зовут control plane nodes). В managed-кластерах (Yandex Managed Service for Kubernetes, VK Cloud, GKE) control plane от тебя спрятан: его держит провайдер, ты платишь и не видишь мастера вообще. В self-hosted (kubeadm, Deckhouse, k3s на своих ВМ) - управляешь сам, и тогда всё нижесказанное твоя прямая зона ответственности.

Состав control plane:
  • kube-apiserver - единая точка входа, REST-фасад над состоянием кластера.
  • etcd - распределённое key-value хранилище, где лежит ВСЁ состояние.
  • kube-scheduler - решает, на какой узел поставить новый под.
  • kube-controller-manager - пачка контроллеров, которые сводят желаемое с фактическим.
  • cloud-controller-manager - контроллеры, которые ходят в API облака (балансировщики, диски, узлы).
В kubeadm-кластере эти компоненты сами работают как статические поды (static pods): kubelet на master-узле читает манифесты из /etc/kubernetes/manifests/ и поднимает их без участия api-server. Это важная деталь: api-server не может запустить сам себя через api-server, поэтому загрузка идёт через kubelet напрямую. Курица и яйцо решены статическими подами.

Изображение

kube-apiserver: ворота, через которые ходят все

api-server - сердце всего. Это stateless HTTP/JSON (и protobuf для внутренних клиентов) сервер, который слушает обычно 6443. Любое действие - kubectl, контроллер, kubelet, твой оператор - идёт через него. Прямого доступа к etcd нет ни у кого, кроме api-server. Это не случайность, а сознательная архитектура: одна точка для аутентификации, авторизации, валидации и аудита.

Каждый входящий запрос проходит конвейер из трёх стадий:
  • Authentication - кто ты. Сертификаты (mTLS), bearer-токены ServiceAccount (JWT), OIDC, webhook. api-server не хранит пароли - он проверяет предъявленное удостоверение.
  • Authorization - что тебе можно. В подавляющем большинстве кластеров это RBAC: твой (Cluster)Role даёт глаголы (get, list, watch, create) на ресурсы. Нет правила - отказ.
  • Admission control - можно ли это в принципе и не подправить ли. Сначала mutating-плагины (могут менять объект - например, дописать sidecar или дефолты), потом validating (только да/нет). Сюда же встроены Pod Security Admission (преемник умерших PodSecurityPolicy), внешние Kyverno/Gatekeeper и нативная ValidatingAdmissionPolicy на CEL-выражениях - без вебхуков, прямо движком api-server.
Только пройдя все три, объект пишется в etcd. Отсюда мораль: если под "не создаётся" с ошибкой forbidden или denied - смотри не на под, а на эту цепочку.

Ещё одна ключевая способность api-server - механизм watch. Клиент открывает долгоживущее соединение и говорит "уведомляй меня обо всех изменениях подов с этой меткой". api-server держит это соединение и шлёт события (ADDED/MODIFIED/DELETED). На этом построено вообще всё: scheduler watch-ит неназначенные поды, kubelet watch-ит поды своего узла, контроллеры watch-ят свои ресурсы. Никто не опрашивает базу в цикле - все подписаны на поток событий. Поэтому api-server должен быть быстрым и его легко перегрузить тысячами watch-ей (привет, list-watch шторм при рестарте).

Посмотрим, кто вообще доступен через api:

Код: Выделить всё

kubectl api-resources | head
NAME          SHORTNAMES   APIVERSION   NAMESPACED   KIND
bindings                   v1           true         Binding
configmaps    cm           v1           true         ConfigMap
endpoints     ep           v1           true         Endpoints
events        ev           v1           true         Event
namespaces    ns           v1           false        Namespace
nodes         no           v1           false        Node
pods          po           v1           true         Pod
Колонка APIVERSION показывает группу/версию ресурса (core - просто v1, остальные вида apps/v1, gateway.networking.k8s.io/v1). NAMESPACED говорит, живёт ли ресурс в namespace или он кластерного уровня (Node, Namespace - кластерные). Это та самая карта REST-эндпойнтов, которую отдаёт api-server.

etcd kubernetes: единственная правда кластера

etcd - это распределённое консистентное key-value хранилище. Всё, что ты создаёшь в кластере (Deployment, Secret, ServiceAccount, статус каждого пода), сериализуется и лежит ключами в etcd. Грубо говоря, /registry/pods/default/nginx -> сериализованный объект. Убери etcd - и кластер забыл вообще всё. Поэтому etcd kubernetes - это не "ещё один компонент", это твоя база данных, и относиться к ней надо как к проду с критичными данными.

Консистентность держится на консенсусе Raft. Среди членов etcd выбирается лидер, он принимает все записи, реплицирует их на фолловеров, и запись считается зафиксированной только когда её подтвердило большинство (quorum). Отсюда железное правило по числу узлов: бери НЕЧЁТНОЕ. Кворум - это N/2 + 1.
  • 3 узла: кворум 2, переживает падение 1 узла.
  • 5 узлов: кворум 3, переживает падение 2 узлов.
  • 2 узла: кворум 2, не переживает НИ ОДНОГО падения - хуже, чем один узел. Поэтому чётные числа бессмысленны.
Потерял кворум (в кластере из 3 упало 2) - etcd уходит в read-only по записи, api-server не может ничего записать, кластер замерзает: существующие поды живут, но создать/изменить ничего нельзя. Лечится только восстановлением кворума или рестором из снапшота.

Проверка здоровья и снятие бэкапа (на kubeadm-узле, где etcd - статический под):

Код: Выделить всё

ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  endpoint status --write-out=table

+----------------+------------------+---------+---------+-----------+-----------+------------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+----------------+------------------+---------+---------+-----------+-----------+------------+
| 127.0.0.1:2379 | 8e9e05c52164694d |  3.5.16 |   12 MB |      true |         8 |     104523 |
+----------------+------------------+---------+---------+-----------+-----------+------------+
Разбор полей: IS LEADER true - этот член сейчас лидер Raft. RAFT TERM - номер "эпохи" выборов, скачет вверх при каждых перевыборах лидера (растёт быстро - значит лидер флапает, ищи сеть/диск). DB SIZE - размер базы, упёрся в quota-backend-bytes (по умолчанию ~2-8 ГБ) - база встанет, нужен дефраг и compaction.

Снимаем снапшот - это и есть твой бэкап:

Код: Выделить всё

ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save /backup/etcd-$(date +%F).db
{"level":"info","msg":"saved","path":"/backup/etcd-2026-06-15.db"}
Бэкап etcd - регулярный, в отдельное хранилище, и обязательно с проверенным восстановлением (snapshot status покажет hash, revision, total keys). В managed-кластерах об этом думает провайдер - там etcd тебе вообще недоступен, и это одна из причин платить за managed. Но в self-hosted без рабочего рестора etcd ты в одном плохом дне от полной потери кластера.

kube-scheduler: куда поставить под

Когда ты создаёшь под, в нём поле spec.nodeName пустое - под "висит" в состоянии Pending без узла. Тут вступает kube-scheduler. Он watch-ит поды без nodeName и для каждого прогоняет двухфазный алгоритм:
  • Filtering (предикаты) - выкидывает узлы, на которые под в принципе не влезет: не хватает CPU/памяти под requests, не подходит nodeSelector/affinity, узел кордонлен, есть taint без матчащего toleration, нет нужного тома.
  • Scoring (приоритеты) - оставшиеся узлы оцениваются по баллам (балансировка нагрузки, spread по зонам, affinity-предпочтения), выбирается лучший.
Дальше scheduler не запускает контейнер сам - это вообще не его работа. Он создаёт объект Binding (или пишет nodeName в под) через api-server. Всё. На этом его участие кончается. kubelet нужного узла увидит через watch, что появился под с его nodeName, и уже он дёрнет containerd запустить контейнеры. Запомни это разделение: scheduler решает ГДЕ, kubelet делает ЧТО.

Посмотреть решение scheduler-а можно прямо в событиях пода:

Код: Выделить всё

kubectl describe pod web-7d9f -n shop | grep -A4 Events
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  12s   default-scheduler  Successfully assigned shop/web-7d9f to node-2
  Normal  Pulled     11s   kubelet            Container image already present on machine
Если под висит Pending - тут будет FailedScheduling с причиной (Insufficient cpu, node(s) had untolerated taint). Это первое место, куда смотреть.

Контроллеры: бесконечный цикл сверки

kube-controller-manager - это один бинарь, внутри которого крутится десяток с лишним контроллеров: Deployment, ReplicaSet, Node, Job, EndpointSlice, ServiceAccount и другие. У каждого одна и та же логика - reconciliation loop (петля согласования): сравнить желаемое состояние (что в spec) с фактическим (что в status и в реальности) и сделать шаг к их совпадению. Желаешь 3 реплики, а живых 2 - ReplicaSet-контроллер создаёт ещё один под. Узел не шлёт heartbeat дольше таймаута - Node-контроллер метит его NotReady, потом эвиктит поды.

Это и есть фундаментальная идея Kubernetes - декларативность. Ты не командуешь "запусти под на node-3". Ты декларируешь "хочу 3 реплики", записываешь это в etcd через api-server, а контроллеры в фоне бесконечно сводят реальность к твоему желанию. Упал под - петля сама поднимет, потому что желаемое (3) больше фактического (2). Никто не выполняет скриптов - все просто чинят расхождение.

cloud-controller-manager - отдельный бинарь (его вынесли из kube-controller-manager, чтобы код облаков не жил в ядре). Он переводит абстракции k8s в API конкретного облака: Service type LoadBalancer -> заказать сетевой балансировщик в Yandex Cloud; PersistentVolume -> создать диск; узел удалён в облаке -> удалить Node-объект. В bare-metal/k3s его может не быть вовсе, и тогда LoadBalancer тебе даст что-то вроде MetalLB.

Все контроллеры общаются ТОЛЬКО через api-server. Scheduler не звонит etcd напрямую, controller-manager не звонит kubelet напрямую. Звезда, а не паутина: в центре api-server, остальные - спицы. Это упрощает безопасность и аудит (один контур авторизации) и развязывает компоненты - любой можно перезапустить независимо.

HA control plane и что ломается при падении каждого

В проде control plane делают отказоустойчивым: минимум 3 master-узла, каждый со своим экземпляром etcd (кворум 2 из 3), api-server за балансировщиком (он stateless - бери любой). А вот scheduler и controller-manager не должны работать параллельно - иначе два scheduler-а назначат один под дважды. Поэтому у них leader election: все экземпляры запущены, но активен один, остальные в горячем резерве и перехватывают лидерство через Lease-объект в api-server, если активный умер.

Теперь самое полезное - что конкретно отвалится при смерти каждого компонента:
  • api-server упал (все экземпляры) - kubectl мёртв, ничего не создать/изменить, но УЖЕ запущенные поды продолжают работать: kubelet и kube-proxy на узлах живут своей жизнью по последнему известному состоянию. Кластер "ослеп", но трафик идёт.
  • etcd потерял кворум - api-server жив, но не может писать. Чтения частично идут, записи нет. Кластер заморожен: новые поды не создаются, упавшие не заменяются. Чинится восстановлением кворума или рестором снапшота.
  • scheduler упал - новые поды зависают в Pending, никто их не назначает. Уже работающие - не трогает. Подними scheduler - очередь Pending разгребётся.
  • controller-manager упал - перестаёт работать самовосстановление: упавший под не заменится, Deployment не докатится, узел не пометится NotReady. Существующее живёт, но кластер теряет способность чинить себя.
  • Worker-узел упал - это не control plane; Node-контроллер заметит пропажу heartbeat и переназначит поды на другие узлы (если есть куда).
Видишь закономерность: падение control plane почти никогда не роняет работающий трафик мгновенно - оно отнимает у кластера способность меняться и самовосстанавливаться. Поэтому "api-server лежит" - это серьёзно, но не "всё умерло", и паниковать с выкатом не надо.

Мини-лаба: пощупать мозг руками

Подними локальный кластер (kind или minikube) и пройди по слоям:
  • Посмотри компоненты как поды: kubectl get pods -n kube-system - увидишь kube-apiserver, etcd, kube-scheduler, kube-controller-manager (в kind они статические поды).
  • Создай под и поймай работу scheduler-а: kubectl run probe --image=nginx, затем kubectl describe pod probe - найди событие Scheduled и узел.
  • Спровоцируй Pending: добавь огромный requests.cpu (например 100 ядер) в под и посмотри FailedScheduling в событиях - это filtering отбраковал все узлы.
  • Понаблюдай reconciliation: kubectl create deployment web --image=nginx --replicas=3, удали один под kubectl delete pod <имя> и сразу kubectl get pods -w - увидишь, как контроллер тут же поднимает замену.
  • Если кластер на VM с kubeadm - сними снапшот etcd командой из урока и проверь его snapshot status.
Контрольные вопросы
  • Почему scheduler и controller-manager не ходят в etcd напрямую, а всё делают через api-server? Что это даёт?
  • В кластере 3 узла etcd, два внезапно умерли. Что произойдёт с кластером и почему именно так?
  • Чем отличается роль scheduler-а от kubelet-а в процессе запуска пода - кто решает "где", а кто делает "что"?
  • api-server лёг во всех репликах, но трафик на сайт идёт. Как это возможно?
Итог

Control plane - это звезда с api-server в центре, единственной правдой в etcd и роем контроллеров вокруг, которые бесконечно сводят желаемое с фактическим. api-server фильтрует всех через authn/authz/admission и раздаёт состояние через watch. etcd хранит всё и держится на кворуме Raft - его бэкап критичен. scheduler решает где, kubelet делает что, контроллеры чинят расхождения. Поймёшь эту механику - и большинство аварий кластера будешь читать не как магию, а как сбой конкретного звена с предсказуемыми последствиями.
👍1 ❤️2 🔥 😄 🤔1
Аватара пользователя
HaskellChan
Сообщения: 1
Зарегистрирован: 12 май 2026, 22:02

Re: Архитектура control plane: api-server, etcd, scheduler

Сообщение HaskellChan »

Только сейчас дошло, почему 2 ноды etcd хуже одной - кворум 2 из 2, и любое падение это смерть записи. Раньше тупо ставил четное и думал что надежнее, спс что разжевали.
👍3 ❤️ 🔥 😄 🤔1
Аватара пользователя
robz04
Сообщения: 1
Зарегистрирован: 15 май 2026, 11:35

Re: Архитектура control plane: api-server, etcd, scheduler

Сообщение robz04 »

А в Yandex Managed мы etcd вообще не видим - выходит весь этот бэкап и кворум на стороне провайдера? Получается за это и платим, а не только за удобство?
👍1 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
Следующая глава →
Узел кластера: kubelet, kube-proxy и container runtime

Все главы курса «Kubernetes: оркестрация контейнеров от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Установка и первый конфиг nginxТюнинг и производительность nginx

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость