Архитектура Kubernetes: компоненты кластера и путь пода от манифеста до запуска | AdminWiki

Архитектура Kubernetes: компоненты кластера и путь пода от манифеста до запуска

25 сентября 2026 12 мин. чтения
Содержание статьи

Что такое кластер Kubernetes и из чего он состоит

Кластер Kubernetes - это набор машин, которыми управляют централизованно. Каждая машина называется узлом (node), и все узлы делятся на две группы. Control plane принимает решения и хранит состояние, worker-ноды запускают рабочие нагрузки. Такое разделение позволяет вводить и выводить машины из строя, не переписывая приложения.

Короткий ответ на главный вопрос: кластер состоит из control plane (kube-apiserver, etcd, kube-scheduler, kube-controller-manager) и worker-нод (kubelet, kube-proxy, container runtime). Путь пода от манифеста до запуска проходит через все эти компоненты: kubectl отправляет YAML в kube-apiserver, тот проверяет объект и сохраняет его в etcd, kube-scheduler выбирает ноду, kubelet на этой ноде через CRI вызывает container runtime, CNI-плагин выдаёт поду IP, kube-proxy добавляет правила для доступа через Service.

Обмен данными идёт только через kube-apiserver. Scheduler, kubelet и controller-manager не читают etcd напрямую, они подписываются на изменения через API-сервер. RBAC, аудит и admission-контроллеры работают в одной точке, что упрощает разграничение прав.

kubectl get nodes
kubectl get pods -n kube-system

Первая команда покажет список узлов и столбец ROLES (control-plane, worker), вторая - системные поды. В dev-кластере control plane и worker часто уживаются на одной машине. В продакшене их разносят минимум на три узла, чтобы etcd сохранял кворум при отказе одной машины.

С версии 1.24 dockershim удалён из проекта, и это поменяло требования к нодам: Kubernetes требует container runtime, совместимый с CRI. Подойдут containerd или CRI-O. До 1.24 интеграция с Docker Engine шла через dockershim, а Docker Engine сам по себе CRI не реализует, поэтому старые инструкции с docker-флагами в kubelet ломают обновление кластера.

Объекты API, с которых начинается любой деплой, разобраны отдельно: устройство кластера и ключевые объекты API Kubernetes.

Control plane: мозг кластера

Четыре компонента отвечают за состояние кластера. kube-apiserver принимает запросы и пишет объекты в etcd. etcd хранит состояние в виде key-value и обеспечивает согласованность через Raft. kube-scheduler назначает поды на узлы. kube-controller-manager запускает контроллеры, которые приводят текущее состояние к желаемому. Отдельно может работать cloud-controller-manager: он связывает кластер с API облачного провайдера (балансировщики, диски, зоны доступности) и нужен в управляемых кластерах.

В отказоустойчивой схеме три и более узла control plane стоят за балансировщиком, а etcd держит нечётное число участников. Проверить состав системных подов можно одной командой:

kubectl get pods -n kube-system | grep -E 'apiserver|etcd|scheduler|controller-manager'

Worker-ноды: где живут приложения

На каждой рабочей ноде работают три процесса. kubelet получает спецификации подов от apiserver и управляет их жизненным циклом. kube-proxy настраивает правила iptables или IPVS, чтобы Service направлял трафик на поды. Container runtime запускает контейнеры и общается с kubelet через CRI. Состав ноды проверяется локально, без доступа к API:

systemctl status kubelet
crictl ps

crictl - отдельная утилита для отладки runtime, она показывает контейнеры так, как их видит CRI, включая sandbox-контейнеры подов.

Компоненты control plane: зона ответственности и проверка

Разберём каждый компонент отдельно: что он делает, что произойдёт при его отказе и какой командой проверить состояние.

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

kube-apiserver даёт REST API для управления кластером. Он аутентифицирует запрос (сертификаты, токены, OIDC), авторизует через RBAC, прогоняет объект через admission-контроллеры (LimitRanger, ResourceQuota, PodSecurity), валидирует схему и только потом пишет результат в etcd. kubectl, kubelet, scheduler и controller-manager подключаются исключительно к нему, поэтому API-сервер масштабируется горизонтально: несколько экземпляров читают и пишут один etcd.

kubectl cluster-info
kubectl api-resources | head -20
curl -k https://<адрес-api>:6443/healthz

Ответ ok на /healthz означает, что процесс жив. Если kubectl отвечает The connection to the server was refused, смотрите логи apiserver и срок действия сертификатов, а не ищите причину в подах приложения.

etcd: хранилище состояния кластера

etcd хранит манифесты, конфигурации, секреты и статусы объектов. Это распределённое key-value хранилище с алгоритмом Raft: запись подтверждается, когда её принял кворум участников. Потеря etcd означает потерю управления кластером: приложения ещё какое-то время работают, но создать, обновить или удалить объект уже не получится. Бэкап etcd - обязательный пункт эксплуатации.

etcdctl endpoint health --endpoints=https://127.0.0.1:2379
etcdctl member list --endpoints=https://127.0.0.1:2379
etcdctl snapshot save /backup/etcd-$(date +%F).db

Если etcd развёрнут как статический под, команды выполняют через kubectl exec в kube-system с указанием сертификатов CA, cert и key. Снимок стоит периодически проверять через etcdctl snapshot restore на отдельном стенде: непроверенный бэкап ничего не гарантирует.

kube-scheduler: выбор ноды для пода

kube-scheduler следит за подами без назначенной ноды (поле nodeName пустое) и выбирает узел в два шага: фильтрация по ресурсам, taints и tolerations, node affinity, topology spread, а затем оценка кандидатов по весам. Сам планировщик контейнеры не запускает, он только записывает имя ноды в объект пода.

kubectl get pods -n kube-system | grep scheduler
kubectl describe pod <имя-пода>
kubectl get events --sort-by=.metadata.creationTimestamp

Под, которому не нашлось места, остаётся в статусе Pending, а в секции Events видна причина: Insufficient cpu, node(s) had taint, didn't match node selector. Практический вывод: Pending почти всегда объясняется requests, taints или selector, а не сбоем кластера.

kube-controller-manager: поддержание желаемого состояния

kube-controller-manager запускает в одном процессе десятки контроллеров: Deployment, ReplicaSet, StatefulSet, DaemonSet, Node, Endpoints, ServiceAccount, Job. Каждый работает в цикле: сравнивает желаемое состояние из манифеста с текущим и вносит правки. Удалите под из Deployment, и ReplicaSet создаст новый, потому что в spec.replicas указано нужное число реплик.

Второй важный цикл - Node controller. Если нода не отправляет heartbeat дольше таймаута, он помечает её NotReady, а поды переносятся на другие узлы (по умолчанию примерно через 300 секунд).

kubectl get pods -n kube-system | grep controller-manager
kubectl logs  -n kube-system | tail -50
kubectl describe node <имя-ноды> | grep -A5 Conditions

Команда kubectl get componentstatuses встречается в старых статьях, но она устарела и в актуальных версиях не даёт полезных данных. Проверяйте control plane через поды kube-system, эндпоинты /healthz и /readyz.

КомпонентЗона ответственностиКоманда проверки
kube-apiserverREST API, аутентификация, авторизация, запись в etcdkubectl cluster-info, curl -k .../healthz
etcdХранение состояния, кворум Raftetcdctl endpoint health, etcdctl member list
kube-schedulerНазначение ноды для новых подовkubectl describe pod, секция Events
kube-controller-managerПриведение текущего состояния к желаемомуkubectl logs <pod> -n kube-system

Worker-нода: kubelet, kube-proxy и container runtime

kubelet: агент на каждой ноде

kubelet регистрирует ноду в кластере, получает PodSpec от apiserver, монтирует тома, выполняет liveness, readiness и startup-пробы, перезапускает контейнеры и отправляет статус пода обратно в API. С runtime он общается через CRI, то есть не знает деталей containerd или CRI-O. Контейнеры, запущенные вручную (docker run или ctr run вне Kubernetes), kubelet не видит и не перезапускает: они живут вне контроля.

systemctl status kubelet
journalctl -u kubelet -n 50 --no-pager
kubectl describe node <имя-ноды>

В разделе Conditions команды describe node смотрите Ready, MemoryPressure, DiskPressure и PIDPressure. Статус Ready со значением False и причиной KubeletNotReady обычно означает проблему с runtime, диском или сертификатом.

kube-proxy: сетевые правила для сервисов

kube-proxy реализует виртуальный IP для Service и перенаправляет трафик на поды. Он работает в режиме iptables или IPVS: iptables проще и подходит для небольших кластеров, IPVS быстрее при тысячах сервисов. Проверить правила можно на самой ноде:

kubectl get pods -n kube-system | grep kube-proxy
kubectl logs  -n kube-system | tail -30
iptables -t nat -L KUBE-SERVICES -n | head

В части кластеров kube-proxy заменяют на eBPF-реализацию (например, Cilium) и отключают его вовсе, но это не стандартная конфигурация: перед таким шагом проверьте требования вашего CNI.

Container runtime: containerd, CRI-O и требования CRI

Kubernetes работает с runtime, совместимым с CRI. Практические варианты - containerd и CRI-O. Проверить версию и активный runtime можно так:

crictl --version
crictl info | grep -i cgroupDriver

Критично согласовать cgroup driver: kubelet и container runtime должны использовать один и тот же cgroup driver и одинаковую конфигурацию. Доступны два драйвера: cgroupfs и systemd. Для containerd в /etc/containerd/config.toml включают SystemdCgroup = true, для kubelet драйвер задают флагом --cgroup-driver=systemd. Пошаговая установка и настройка containerd с нуля есть в нашем руководстве по развёртыванию кластера через kubeadm.

Путь пода: от манифеста до запуска контейнера

Проследим процесс на конкретном манифесте. Сохраните его в pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: demo-nginx
  labels:
    app: demo
spec:
  containers:
  - name: nginx
    image: nginx:1.27
    ports:
    - containerPort: 80
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 256Mi

Создание манифеста и отправка в apiserver

kubectl конвертирует YAML в JSON и отправляет POST-запрос на kube-apiserver. Сервер проверяет права по RBAC, прогоняет объект через admission-контроллеры, валидирует схему и записывает его в etcd. При ошибке в манифесте ответ приходит сразу, до записи:

kubectl apply -f pod.yaml
kubectl get pod demo-nginx -o yaml
kubectl apply -f pod.yaml --v=8

Флаг --v=8 показывает HTTP-запросы kubectl, что удобно при разборе ошибок авторизации и валидации.

Планирование пода на ноду

kube-scheduler замечает новый под без nodeName и подбирает узел. Если под не влезает по ресурсам или не проходит по taints и affinity, он остаётся Pending, а причина пишется в события:

kubectl get pods -w
kubectl describe pod demo-nginx | tail -20

Типичная ошибка на этом шаге - Insufficient cpu: суммарные requests контейнеров в поде превышают свободные ресурсы всех нод. Уменьшите requests или добавьте узел, но не убирайте requests совсем, иначе планировщик потеряет ориентир.

Запуск контейнера через kubelet и container runtime

kubelet на выбранной ноде получает PodSpec, вызывает CRI-плагин, runtime скачивает образ и создаёт контейнеры. После старта kubelet выполняет post-start хуки и пробы, а затем сообщает статус в apiserver. Если образ недоступен или неверный тег, под переходит в ImagePullBackOff или ErrImagePull.

kubectl logs demo-nginx
kubectl exec -it demo-nginx -- sh
crictl ps | grep demo
crictl logs 

crictl на ноде показывает контейнеры на уровне runtime, включая те, что kubectl уже не отображает. Это помогает понять, существует ли контейнер физически или уже удалён.

Настройка сети и доступ к поду

После запуска контейнера kubelet вызывает CNI-плагин (Calico, Cilium, Flannel), который выдаёт поду IP и настраивает маршруты. kube-proxy добавляет правила, чтобы Service направлял трафик на готовые поды. Проверка сквозного доступа:

kubectl get svc
kubectl describe svc <имя-сервиса>
kubectl get endpoints <имя-сервиса>
kubectl run test --image=busybox --rm -it -- wget -qO- http://<имя-сервиса>

Пустой список endpoints означает, что селектор Service не совпадает с метками подов или пробы readiness не проходят. Сеть в этом случае ни при чём. Если вы только осваиваете оркестрацию, начните с локального стенда: запуск сервисов в Kubernetes с минимальной конфигурацией.

Типичные ошибки при проектировании архитектуры Kubernetes

Несогласованный cgroup driver

kubelet и container runtime обязаны использовать одинаковый cgroup driver. Если kubelet работает с systemd, а runtime с cgroupfs, возможны сбои при ограничении ресурсов CPU и памяти, а также расхождения в статистике. Сравните настройки:

ps aux | grep kubelet | grep -o -- '--cgroup-driver=[a-z]*'
crictl info | grep -i cgroupDriver
kubectl get node <имя-ноды> -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}'

Рекомендация прямая: используйте systemd, если в системе systemd как init. cgroupfs driver не рекомендуется при systemd, потому что systemd ожидает единый cgroup manager, а с версии 1.22 kubeadm по умолчанию выставляет systemd, если параметр cgroupDriver не задан вручную.

Устаревший dockershim и переход на CRI

Docker Engine не реализует CRI, и после удаления dockershim в 1.24 напрямую подключить его к kubelet нельзя. Проверьте, какой runtime используют ноды:

kubectl get nodes -o wide

Значение docker:// в колонке CONTAINER-RUNTIME укажет на legacy-схему. План миграции: вывести ноду из балансировки (cordon), дождаться переноса подов (drain), установить и настроить containerd, переключить kubelet на CRI-эндпоинт и вернуть узел в строй (uncordon). Такой подход для продакшена с высокой доступностью, RBAC и политиками сети описан в материале про проектирование и управление промышленными кластерами Kubernetes.

Отсутствие мониторинга состояния кластера

Ошибки планирования, зависшие поды и проблемы с нодами дешевле ловить по метрикам, чем по жалобам пользователей. kube-state-metrics подключается к API-серверу и отдаёт метрики о состоянии объектов кластера через HTTP-эндпоинт, включая метки, аннотации, время запуска и завершения, статус и текущую фазу объекта. Метрики собирает Prometheus, визуализирует Grafana, алерты отправляет Alertmanager.

kubectl apply -f kube-state-metrics-manifests.yaml
kubectl get pods -n kube-system | grep kube-state-metrics
curl -s http://kube-state-metrics:8080/metrics | head

Учитывайте, что kube-state-metrics - сторонний проект и не входит в состав Kubernetes, поэтому его версию стоит держать совместимой с версией кластера.

Бэкап etcd, requests и limits, taints control plane

Ещё четыре ошибки, которые всплывают уже в эксплуатации. Отсутствие регулярного бэкапа etcd лишает возможности восстановиться после потери кворума. Requests без limits дают риск OOMKilled на ноде, limits без requests мешают планировщику. Игнорирование taints control plane приводит к попыткам планировать рабочие нагрузки на управляющие узлы. Проверки простые:

kubectl describe node  | grep -i taint
kubectl get pods -A -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}'
kubectl top pods -A --sort-by=memory | head

Современные возможности: Dynamic Resource Allocation и мониторинг

Dynamic Resource Allocation: гибкое управление ресурсами

DRA позволяет подам запрашивать и совместно использовать ресурсы, которые не сводятся к CPU и памяти: GPU, сетевые адаптеры, ускорители FPGA. Функция стабильна начиная с версии 1.35, впервые появилась в 1.30, а фильтрация устройств выполняется через Common Expression Language (CEL), что даёт точный отбор по свойствам конкретной модели.

kubectl get resourceslices
kubectl get resourceclaims -A
kubectl describe resourceclaim <имя>

Ограничение, о котором нужно знать заранее: планировщик Kubernetes не поддерживает вытеснение для ресурсов DRA, поэтому конфликт за устройство не разрешится автоматически. Для работы DRA нужны DRA-совместимые драйверы устройств, их ставят отдельно от самого Kubernetes.

Мониторинг с kube-state-metrics и Prometheus

Базовый набор наблюдаемости строится вокруг двух источников: метрики ресурсов (kubelet, cAdvisor) и метрики состояния объектов. Второй слой закрывает агент kube-state-metrics, который подключается к API-серверу и публикует HTTP-эндпоинт с метриками состояния объектов кластера.

Два запроса, которые стоит поставить на дашборд с первого дня. Количество подов, которые не готовы, по неймспейсам:

count(kube_pod_status_ready{condition="false"}) by (namespace, pod)

И алерт на поды, зависшие в Terminating дольше 5 минут, с исключением случая потерянной ноды:

count(kube_pod_deletion_timestamp) by (namespace, pod)
  * count(kube_pod_status_reason{reason="NodeLost"} == 0) by (namespace, pod) > 0

Чек-лист для проверки архитектуры Kubernetes

Сводка проверок, которые стоит прогнать после установки кластера и повторять после обновлений.

  1. Все компоненты control plane запущены: kubectl get pods -n kube-system.
  2. API-сервер отвечает: kubectl cluster-info и curl -k https://<адрес-api>:6443/healthz.
  3. kubelet и container runtime используют один cgroup driver: ps aux | grep kubelet | grep cgroup-driver и crictl info | grep -i cgroupDriver.
  4. Runtime совместим с CRI, а не dockershim: kubectl get nodes -o wide, в колонке CONTAINER-RUNTIME ожидается containerd:// или cri-o://.
  5. Настроен и проверен бэкап etcd: etcdctl snapshot save и тестовое восстановление на отдельном стенде.
  6. kube-proxy работает, Service находят endpoints: kubectl get endpoints -A.
  7. Мониторинг собирает метрики состояния: Prometheus видит kube-state-metrics, алерты на NotReady и Terminating настроены.
  8. Новые поды запускаются и обновляются: kubectl run test --image=nginx, затем kubectl rollout status deployment/<имя>.
  9. На control plane есть taints, рабочие нагрузки туда не планируются: kubectl describe node <control-plane-node> | grep -i taint.

Прогоните эти девять пунктов сразу после развёртывания и сохраните вывод в тикет или runbook. Кластер, у которого проверены cgroup driver, бэкап etcd и алерты на состояние объектов, переживает обновление версии без сюрпризов, а разбор пути пода из этой статьи даёт точку опоры при отладке: вы всегда знаете, на каком шаге искать причину.

Поделиться:
Сохранить гайд? В закладки браузера