Узел кластера: kubelet, kube-proxy и container runtime

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

Узел кластера: kubelet, kube-proxy и container runtime

Сообщение 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 и видеть, как поды волшебным образом появляются на нодах. Но если под застрял в Pending, нода ушла в NotReady, а Service вдруг перестал отвечать - ты упрешься в стенку, пока не поймешь, что реально происходит внутри worker-узла. Control-plane только записывает желаемое состояние в etcd. А вот превратить строчку "запусти nginx на node-3" в живой процесс с сетью, дисками и пробами - это работа трех демонов на самой ноде: kubelet, container runtime и kube-proxy. Плюс CNI-плагин, который дает поду IP. Разберем каждого по косточкам, потому что 80 процентов реальных инцидентов в эксплуатации - это именно про worker-узел, а не про красивые YAML.

Что такое kubernetes узел и кто на нем живет

Worker-узел (node) - это просто Linux-машина (VM или железо) с тремя обязательными компонентами. Если хоть одного нет или он сломан - узел бесполезен.
  • kubelet - главный агент. Это процесс-демон, который общается с api-server и отвечает за то, чтобы на этой конкретной ноде крутились ровно те поды, которые ему назначены. Он НЕ запускает контейнеры сам - он командует runtime через стандартный интерфейс.
  • container runtime - то, что реально создает и запускает контейнеры: containerd, CRI-O. Снизу под ними - runc, который и делает системные вызовы для namespaces и cgroups.
  • kube-proxy - программирует сетевые правила ядра так, чтобы виртуальный IP Service-а превращался в реальные IP подов. По сути это контроллер сетевых правил, а не прокси в привычном смысле.
Отдельно стоит CNI-плагин (Calico, Cilium, Flannel) - он не демон Kubernetes, а плагин, который kubelet дергает при создании пода, чтобы выдать поду IP и воткнуть его в сеть. Без CNI узел встанет в NotReady с жалобой на отсутствие сети.

Аналогия: control-plane - это диспетчер, который раздает наряды по рации. kubelet - бригадир на объекте, который наряды принимает и распределяет. runtime - рабочие с инструментом. kube-proxy - электрик, который протягивает провода так, чтобы по одному адресу (Service) ток шел к нужной бригаде. CNI - тот, кто вообще завел электричество на объект.

Изображение

kubelet: что он делает на самом деле

kubelet - это reconcile-цикл, как и любой контроллер Kubernetes. Он постоянно сравнивает "что мне назначено" с "что реально запущено" и устраняет разницу.

Откуда он берет список подов? Основной источник - api-server: kubelet держит watch на поды, у которых spec.nodeName равен имени этой ноды (то есть те, что уже распределил scheduler). Есть и второй источник - static pods: манифесты, лежащие прямо в файле на ноде (обычно /etc/kubernetes/manifests). Их kubelet запускает БЕЗ участия scheduler и api-server. Именно так на control-plane поднимаются сами api-server, etcd, scheduler - курица и яйцо решаются через static pods.

Что kubelet делает с каждым подом:
  • вызывает CNI, чтобы под получил сетевой namespace и IP;
  • через CRI создает pause-контейнер (sandbox), который держит сетевой и IPC namespace пода - остальные контейнеры подключаются к нему;
  • тянет образы и стартует контейнеры в нужном порядке (initContainers, потом основные, нативные sidecar с 1.29 как initContainer с restartPolicy: Always запускаются раньше и глушатся позже);
  • следит за пробами: livenessProbe (перезапустить контейнер, если завис), readinessProbe (убрать под из эндпоинтов Service, пока не готов), startupProbe (дать медленному приложению прогреться, не убивая его раньше времени);
  • монтирует volumes, прокидывает Secret/ConfigMap, считает использование ресурсов;
  • репортит статус пода и ноды обратно в api-server.
Важный момент про пробы: их выполняет сам kubelet локально, а не api-server и не kube-proxy. Поэтому проба httpGet идет с самой ноды на IP пода напрямую. Если проба у тебя ходит через Service - это антипаттерн, kubelet так не делает.

container runtime и почему dockershim выпилили

Раньше kubelet умел разговаривать с Docker напрямую через встроенный костыль - dockershim. Проблема: это был специальный код именно под Docker внутри kubelet, который команда Kubernetes была вынуждена тащить и чинить. С версии 1.24 dockershim удален полностью. Это НЕ значит, что Docker-образы перестали работать - образы как были OCI-совместимыми, так и остались. Просто kubelet больше не говорит с Docker напрямую.

Теперь все runtime подключаются через CRI (Container Runtime Interface) - это gRPC-контракт. kubelet шлет вызовы вроде RunPodSandbox, CreateContainer, StartContainer на Unix-сокет runtime, и runtime сам решает, как их выполнить. Стандартный сокет containerd - /run/containerd/containerd.sock, у CRI-O - /var/run/crio/crio.sock.

Кто сейчас в проде:
  • containerd kubernetes - дефолт почти везде (Yandex Managed Kubernetes, VK Cloud, EKS, GKE, k3s). Легкий, родом из Docker, но без лишней обвязки.
  • CRI-O - заточен строго под Kubernetes, любим в OpenShift/Deckhouse-окружениях.
Под runtime сидит runc - низкоуровневый OCI-runtime, который непосредственно делает clone() с нужными флагами namespaces, настраивает cgroups, pivot_root в rootfs контейнера и запускает процесс. Полный поток выглядит так: api-server -> kubelet -> CRI (gRPC) -> containerd -> runc -> процесс контейнера в изолированном namespace.

Посмотреть, что реально крутится на ноде, можно через crictl - это CRI-аналог docker ps, ходит прямо в runtime мимо Kubernetes:

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

crictl ps
CONTAINER      IMAGE          CREATED        STATE     NAME         POD ID
a1b2c3d4e5f6   nginx@sha256   2 minutes ago  Running   nginx        9f8e7d6c5b4a
Поле POD ID тут - это id того самого pause-sandbox. Если crictl ps пустой, а под якобы Running - проблема между kubelet и runtime, копай туда.

kube-proxy: как ClusterIP превращается в реальные поды

ClusterIP Service-а - это виртуальный адрес, которого физически нет ни на одном интерфейсе. Никто на нем не слушает. Магия в том, что kube-proxy на каждой ноде следит за объектами Service и EndpointSlice и программирует ядро так, чтобы трафик на этот виртуальный IP перенаправлялся (DNAT) на IP одного из живых подов.

Раньше kube-proxy подписывался на Endpoints, теперь - на EndpointSlice: вместо одного гигантского объекта со всеми эндпоинтами Service-а они нарезаны на куски примерно по 100 адресов. Для Service на тысячи подов это снимает огромную нагрузку с api-server и etcd при каждом изменении.

Режимы kube-proxy:
  • iptables - дефолт по сей день. kube-proxy генерит цепочки iptables-правил. Минус: число правил растет линейно числу Service-ов и эндпоинтов, на больших кластерах обновление ruleset тормозит.
  • IPVS - использует L4-балансировщик ядра на dummy-интерфейсе kube-ipvs0, поиск по хеш-таблице, сложность примерно O(1), плюс реальные алгоритмы балансировки (rr, lc, wrr). Важно: IPVS объявлен deprecated, новые кластеры на него ставить уже не стоит.
  • nftables - новый бэкенд, стал GA (стабильным) в Kubernetes 1.33. Структура с map-lookup примерно O(1), масштабируется заметно лучше iptables. Требует ядро Linux 5.13+. Дефолтом пока остается iptables - ради совместимости, но для крупных кластеров на свежем ядре nftables это правильный выбор. Включается так: mode: nftables в конфиге kube-proxy или флаг --proxy-mode nftables.
Грабля nftables-режима, о которой спотыкаются при миграции: NodePort-сервисы становятся доступны только на дефолтных IP ноды, а не на всех адресах включая 127.0.0.1, как было в iptables. Если у тебя что-то ходило на NodePort через localhost - сломается.

Посмотреть правила для конкретного Service в iptables-режиме:

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

iptables -t nat -L KUBE-SERVICES -n | grep 10.96.0.10
KUBE-SVC-ERIFXISQEP7F7OF4  tcp -- 0.0.0.0/0  10.96.0.10  /* kube-system/kube-dns:dns-tcp */ tcp dpt:53
Цепочка KUBE-SVC-... дальше через probability-правила раскидывает трафик по KUBE-SEP-... (по одному на каждый под-эндпоинт) - это и есть балансировка на уровне правил ядра.

Ресурсы узла: allocatable и reserved

Нода с 8 ГБ RAM НЕ отдает все 8 ГБ под поды. Часть зарезервирована, иначе системные демоны и сам kubelet помрут от OOM раньше пользовательских подов. Формула:

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

Allocatable = Capacity - kube-reserved - system-reserved - eviction-threshold
  • Capacity - вся память/CPU ноды.
  • kube-reserved - под kubelet, runtime, container-демоны.
  • system-reserved - под ОС: sshd, systemd, логи.
  • eviction-threshold - буфер, ниже которого kubelet начинает выселять поды, чтобы не свалиться в системный OOM-killer.
Именно Allocatable, а не Capacity, scheduler использует при планировании. Поэтому под с requests на 7.5 ГБ может не влезть на ноду с 8 ГБ Capacity - allocatable там, скажем, 7.2 ГБ. Смотрим:

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

kubectl describe node node-3
Capacity:
  cpu:                4
  memory:             8141816Ki
Allocatable:
  cpu:                3920m
  memory:             7290936Ki
Разница между Capacity и Allocatable по памяти тут почти 850 МБ - это и есть резервы. Если поды на ноде начали массово выселяться (Evicted) - первым делом смотри сюда и на реальное потребление: скорее всего узел уперся в eviction-threshold по памяти или по disk.

Регистрация узла и диагностика NotReady

При старте kubelet сам регистрирует ноду в api-server (создает объект Node) и дальше шлет heartbeat. Механизм heartbeat - объект Lease в namespace kube-node-lease: kubelet обновляет его раз в несколько секунд. Если api-server перестал видеть свежий Lease - через node-monitor-grace-period (по умолчанию около 40 секунд) нода помечается NotReady, а еще позже ее поды начинают выселять на другие ноды.

Что чаще всего загоняет узел в NotReady:
  • сеть не настроена - CNI-плагин не установлен или его DaemonSet упал. Классика на свежем кластере: все ноды NotReady, пока не накатишь Calico/Cilium.
  • kubelet не может достучаться до api-server (firewall, протухший сертификат, неверный kubeconfig).
  • runtime лежит - сокет containerd недоступен, kubelet не может ни создать, ни проверить контейнеры.
  • узел уперся в ресурсы - DiskPressure, MemoryPressure, PIDPressure. Эти условия kubelet выставляет сам.
Алгоритм диагностики на самой ноде (по SSH):

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

systemctl status kubelet
journalctl -u kubelet -f --no-pager
crictl info
crictl ps
Если systemctl говорит, что kubelet активен, но journalctl сыплет ошибками про CNI - проблема в сети. Если crictl info ругается на сокет - лежит runtime. Если kubelet вообще не стартует - смотри сертификаты и kubeconfig в /etc/kubernetes/kubelet.conf.

Типичные грабли и антипаттерны
  • "Установлю обратно Docker как runtime" - не надо. Образы Docker и так работают, а dockershim мертв. Ставь containerd, это дефолт.
  • livenessProbe, которая ходит наружу - проба должна проверять только сам процесс. Если она зависит от БД или другого сервиса, при их недоступности kubelet начнет бесконечно перезапускать здоровый под. Для зависимостей - readinessProbe.
  • Поды без requests - scheduler считает по requests. Без них он думает, что под ничего не ест, набивает ноду под завязку, и при пике все валится в OOM/eviction. Всегда ставь хотя бы memory requests.
  • Перезапуск kube-proxy "на всякий случай" в проде - на больших iptables-кластерах полная перестройка ruleset дает заметный сетевой провал. Понимай режим (iptables/IPVS/nftables) и его цену.
  • Диагностика NotReady с ноутбука - kubectl покажет только статус. Корень почти всегда виден лишь в journalctl -u kubelet и crictl на самой ноде.
Мини-лаба: разбираем узел руками

Подойдет любой кластер - kind, minikube, k3s или managed (Yandex/VK Cloud).
  • Посмотри ноды и их статус: kubectl get nodes -o wide. Запиши версии runtime в колонке CONTAINER-RUNTIME - убедись, что там containerd, а не docker.
  • Зайди в детали ноды: kubectl describe node <имя>. Найди секции Capacity и Allocatable, посчитай разницу - это твои резервы. Глянь Conditions: Ready, MemoryPressure, DiskPressure.
  • Если есть SSH-доступ к ноде (k3s/kind подойдут) - выполни crictl ps и crictl pods. Сопоставь sandbox (POD ID) с тем, что показывает kubectl get pods.
  • Создай Service: kubectl expose deployment <твой> --port=80. Затем iptables -t nat -L KUBE-SERVICES -n | grep <ClusterIP> (или nft list ruleset, если режим nftables) - найди цепочку своего сервиса.
  • Сломай специально: kubectl scale deployment <твой> --replicas=0. Снова посмотри правила kube-proxy - SEP-цепочки эндпоинтов исчезнут, потому что живых подов больше нет.
  • Посмотри Lease ноды: kubectl -n kube-node-lease get lease - это пульс твоего узла.
Контрольные вопросы
  • Почему kubelet с версии 1.24 не запускает контейнеры через Docker напрямую и что пришло на смену dockershim?
  • В чем разница между Capacity и Allocatable, и какое из значений видит scheduler при планировании пода?
  • Чем kube-proxy в режиме nftables принципиально лучше iptables на больших кластерах и почему iptables все еще дефолт?
  • Узел ушел в NotReady. Какие 3-4 причины проверишь в первую очередь и какими командами на самой ноде?
Итог

Worker-узел - это не магия, а три демона плюс плагин. kubelet принимает поды от api-server и гонит их через CRI в containerd и дальше в runc. kube-proxy превращает виртуальные ClusterIP в DNAT-правила ядра (iptables по умолчанию, nftables - будущее, IPVS уходит). CNI дает подам IP. allocatable, а не capacity, определяет, влезет ли под. А когда узел падает в NotReady, ответ почти всегда живет в journalctl -u kubelet и crictl - на самой ноде, а не в дашборде. Понимаешь этот слой - перестаешь бояться продакшен-инцидентов.
👍3 ❤️2 🔥2 😄 🤔
Аватара пользователя
kube77
Сообщения: 1
Зарегистрирован: 21 май 2026, 22:02

Re: Узел кластера: kubelet, kube-proxy и container runtime

Сообщение kube77 »

Долго не мог понять почему мой под Running а приложение недоступно. Оказалось crictl ps на ноде пустой, kubelet и runtime разъехались. Без этого урока тыкался бы вслепую, спасибо за crictl.
👍1 ❤️1 🔥2 😄 🤔
Аватара пользователя
Manger
Сообщения: 1
Зарегистрирован: 19 май 2026, 20:00

Re: Узел кластера: kubelet, kube-proxy и container runtime

Сообщение Manger »

А правильно понял что если поставить mode: nftables то NodePort через localhost отвалится? У нас как раз healthcheck балансировщика ходит на 127.0.0.1:NodePort, надо будет переделать перед миграцией.
👍1 ❤️1 🔥1 😄 🤔1
Ответить
← Предыдущая глава
Архитектура control plane: api-server, etcd, scheduler
Следующая глава →
Декларативная модель: reconciliation, контроллеры, CRD

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: minikube или kind что выбрать для локального кластера kuberneteskubectl get describe logs основные команды для работы с кластеромчто такое service в kubernetes и как поды находят друг другаnamespace в kubernetes зачем нужен и как разделить ресурсыhelm chart как установить приложение в kuberneteshelm глубже шаблоны хуки и зависимости чартов

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

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

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