Что такое кластер 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-apiserver | REST API, аутентификация, авторизация, запись в etcd | kubectl cluster-info, curl -k .../healthz |
| etcd | Хранение состояния, кворум Raft | etcdctl 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
Сводка проверок, которые стоит прогнать после установки кластера и повторять после обновлений.
- Все компоненты control plane запущены: kubectl get pods -n kube-system.
- API-сервер отвечает: kubectl cluster-info и curl -k https://<адрес-api>:6443/healthz.
- kubelet и container runtime используют один cgroup driver: ps aux | grep kubelet | grep cgroup-driver и crictl info | grep -i cgroupDriver.
- Runtime совместим с CRI, а не dockershim: kubectl get nodes -o wide, в колонке CONTAINER-RUNTIME ожидается containerd:// или cri-o://.
- Настроен и проверен бэкап etcd: etcdctl snapshot save и тестовое восстановление на отдельном стенде.
- kube-proxy работает, Service находят endpoints: kubectl get endpoints -A.
- Мониторинг собирает метрики состояния: Prometheus видит kube-state-metrics, алерты на NotReady и Terminating настроены.
- Новые поды запускаются и обновляются: kubectl run test --image=nginx, затем kubectl rollout status deployment/<имя>.
- На control plane есть taints, рабочие нагрузки туда не планируются: kubectl describe node <control-plane-node> | grep -i taint.
Прогоните эти девять пунктов сразу после развёртывания и сохраните вывод в тикет или runbook. Кластер, у которого проверены cgroup driver, бэкап etcd и алерты на состояние объектов, переживает обновление версии без сюрпризов, а разбор пути пода из этой статьи даёт точку опоры при отладке: вы всегда знаете, на каком шаге искать причину.