# kubelet - архитектура агента ноды Kubernetes _Контейнеры (бонус) · LinuxLab Knowledge Base_ **TL;DR:** kubelet - демон на каждой ноде. Получает PodSpec через API, запускает контейнеры через CRI, монтирует volumes через CSI, следит за health. При pressure делает eviction. Image GC и cgroup-tree - тоже его. ## Что такое kubelet **kubelet**, единственный k8s-компонент, который работает с контейнерами на ноде. Всё, что вы видите в `kubectl get pod` результат того, что kubelet: 1. Подписался на PodSpec'ы для своей ноды через kube-apiserver 2. Сравнивает desired (от API) и actual (что реально запущено) 3. Делает изменения через CRI/CSI/CNI 4. Репортит status обратно в API Не путать с container runtime (containerd), kubelet это **уровень выше**, он командует runtime'ом через **CRI gRPC**. ``` kube-apiserver ▲ ▼ list/watch + status kubelet (на каждой ноде) │ ├── CRI gRPC → containerd → [[runc-and-runsc|runc]] → контейнер ├── CSI gRPC → csi-driver → mount/attach volume └── CNI exec → calico/cilium → сеть pod'а ``` ## Главные подсистемы ### Pod sync loop Сердце kubelet'а, `syncLoop` в `pkg/kubelet/kubelet.go`. Цикл every-N-seconds (default 10s) или event-driven: 1. Получить **desired set of pods** из источников: API server, `--pod-manifest-path` (static pods для control plane), HTTP-URL. 2. Сравнить с running pods. 3. Для каждого pod, который должен быть, но не запущен, `syncPod`. 4. Для каждого, который запущен но не должен, `killPod`. Источников три: - **api**: основной, watch на kube-apiserver - **file**: static pods из `/etc/kubernetes/manifests/` (apiserver, etcd, controller-manager сами стартуют так на control-plane) - **http**: внешний URL с PodList ### CRI, Container Runtime Interface Разговор с container runtime через gRPC. Сокет, `unix:///var/run/containerd/containerd.sock` (или CRI-O аналог). Два сервиса: - **RuntimeService**, pods/containers lifecycle (RunPodSandbox, CreateContainer, StartContainer, StopPodSandbox) - **ImageService**, pull/list/remove image Pod в CRI, это **PodSandbox** (контейнер с pause-image, держит netns + ipc/uts namespace) плюс N `containers` внутри него, все шарят netns с pause. Pause container нужен, чтобы: - PID 1 в pod-namespace всегда был жив (для reap zombie) - Перезапуск application-контейнера не убивал pod-network ### Container Manager Управляет cgroup-tree на ноде: ``` /sys/fs/cgroup/ ├── system.slice/ # systemd ├── kubepods.slice/ # → kubelet root │ ├── kubepods-burstable.slice/ # QoS class │ │ └── kubepods-burstable-pod.slice/ │ │ └── cri-containerd-.scope/ │ ├── kubepods-besteffort.slice/ │ └── kubepods-pod.slice/ # Guaranteed ``` Иерархия, по **QoS-классу** pod'а: - **Guaranteed**: requests == limits на всех контейнерах. Высший приоритет. - **Burstable**: requests < limits или часть контейнеров без обоих - **BestEffort**: ни requests, ни limits При [[oom-killer|OOM]] на ноде первыми падают BestEffort, потом Burstable (по oom_score), Guaranteed, последние. Полезно: `--reserved-cpus`, `--cpu-manager-policy=static` выделять физические CPU под Guaranteed-pod'ы. ### Volume Manager Реактирует на pod'ы с volumes: вызывает CSI-drivers (Attach → Mount → unmount → Detach). Для CSI это всё gRPC к socket'у CSI-Node-plugin'а на ноде. Особенности: - **Mount/Unmount idempotent**, kubelet ретраит при ошибке - **Attach/Detach** делает не kubelet, а **external-attacher** sidecar при `attachRequired: true` в CSI driver'е (но решение принимает A/D Controller в kube-controller-manager) - Mount-флаги (`noatime`, `nodev`) задаются в [[kubernetes-storage|StorageClass]] ## Probes Health checking подов: - **livenessProbe**, pod жив? Если N раз провалил → kill + restart - **readinessProbe**, pod готов принимать трафик? Если нет → убрать из Endpoints (но не убивать) - **startupProbe**, заменяет liveness на старте, для медленно стартующих приложений (избегаем kill во время загрузки) Типы probe'ов: `httpGet`, `tcpSocket`, `exec` (запустить команду внутри контейнера, exit-code 0, ok), `grpc` (с k8s 1.27+). Probe-логика, целиком в kubelet, без сети к API. ## Image GC и Disk eviction kubelet **не оставляет неиспользуемые images вечно**, диск заполнится. Алгоритм: Периодически (`--image-gc-high-threshold`, default 85%) проверяет использование `/var/lib/containerd` (или CRI-эквивалент). Если >threshold, удаляет неиспользуемые images до `--image-gc-low-threshold` (default 80%). Сортировка: **LRU** по времени последнего использования. Image, на который ссылается running container, не удаляется. Для логов: kubelet ротирует stdout/stderr через CRI ( `--container-log-max-size`, default 10Mi, `--container-log-max-files` 5). ## Node-pressure eviction Когда ресурсы ноды на исходе, kubelet проактивно убивает pods. Соблюдается приоритет: | Сигнал | Default threshold | Что эвиктится | |--------|-------------------|----------------| | `memory.available` | < 100Mi | BestEffort → Burstable → Guaranteed | | `nodefs.available` | < 10% | то же по QoS | | `nodefs.inodesFree` | < 5% | то же | | `imagefs.available` | < 15% | image GC сначала, потом eviction | | `pid.available` | < 10% | то же | Soft eviction (`--eviction-soft`) с `--eviction-soft-grace-period` pod получает время на graceful shutdown. Hard eviction, сразу SIGKILL. Это **отдельно** от [[oom-killer|OOM-killer]] kernel'а: kernel убивает за секунды, kubelet проактивно эвиктит за минуты. ## kubelet config ConfigMap-based конфиг (вместо CLI-флагов): ```yaml apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd # должен совпадать с containerd containerRuntimeEndpoint: unix:///var/run/containerd/containerd.sock clusterDNS: [10.96.0.10] clusterDomain: cluster.local podCIDR: 10.244.1.0/24 # из node-spec evictionHard: memory.available: "200Mi" nodefs.available: "10%" systemReserved: cpu: "500m" memory: "1Gi" kubeReserved: cpu: "500m" memory: "1Gi" maxPods: 110 serializeImagePulls: false # параллельно pull ``` cgroupDriver, частая боль. **systemd** (default современных) vs **cgroupfs**. Должен совпадать с containerd-config'ом, иначе kubelet видит pods «не свои» и творится бардак. ## Static pods Pods, которые kubelet запускает **независимо от API server'а**, читая YAML из `/etc/kubernetes/manifests/`. Используется для control-plane (kube-apiserver, etcd, controller-manager, scheduler, каждый из них static-pod на control plane node'е). В API появляются **зеркальные pods** (`mirror pod`), но kubelet не слушает их edits, источник истины файл. Полезно: для self-bootstrapping control plane (как kubeadm), для standalone-агентов. ## CSI / CNI / Device Plugin kubelet вызывает три внешних: - **CSI** через gRPC (mounted в well-known path `/var/lib/kubelet/plugins//csi.sock`) - **CNI** через exec бинарника (`/opt/cni/bin/`) - **Device Plugin** через gRPC (для GPU, FPGA, RDMA, DaemonSet регистрируется в kubelet, kubelet пробрасывает в pod) ## Когда что-то пошло не так - **`Node not ready` сразу после старта**, kubelet не достучался до CRI socket'а, или CNI не настроен (`/etc/cni/net.d/` пустой). `journalctl -u kubelet`. - **`failed to get system container stats: failed to get cgroup stats`** cgroup driver mismatch (systemd vs cgroupfs) между kubelet и containerd. - **Pod `Evicted` без видимой причины**, `kubectl describe node` → `Conditions:` (MemoryPressure/DiskPressure/PIDPressure). Чаще всего, disk full из-за кэша images. `crictl rmi --prune`. - **`exec format error` в pause container**, multi-arch image не подходит ноде. `kubectl describe pod` → image arch. - **Pod stuck in Terminating**, finalizer держит, или CRI не отвечает на StopPodSandbox. `crictl ps` → есть ли container, `crictl logs `. - **kubelet OOM на самом себе**, слишком много pod'ов и больших PodSpec'ов в RAM. Tune `--max-pods`, добавить `--kube-api-qps/burst`. - **CSI mount висит**, csi-node-plugin pod упал. `kubectl get pods -n kube-system | grep csi`. - **kubelet log shows `syncPod errored`**, частое и общее. Полезно `--v=4` для verbose, но логи раздуются. ## Полезные диагностические артефакты - `journalctl -u kubelet -f`, основные логи - `crictl ps` (вместо `docker ps` для CRI runtime'ов) - `crictl logs `, логи контейнера - `crictl images`, что pulled на ноде - `cat /var/lib/kubelet/config.yaml`, текущий конфиг - `curl -k https://localhost:10250/metrics`, Prometheus метрики - `curl -k https://localhost:10250/healthz` ## Команды ```bash systemctl status kubelet ``` Статус демона - живой/мёртвый, последние ошибки в журнале ```bash journalctl -u kubelet -f --since '5 min ago' ``` Стрим логов kubelet - первое место смотреть при проблемах с подами на ноде ```bash crictl ps -a ``` Все контейнеры через CRI - alternative для docker ps когда runtime containerd/CRI-O ```bash crictl logs ``` stdout/stderr контейнера прямо через CRI без k8s API ```bash crictl rmi --prune ``` Удалить unused images - быстрый способ освободить диск перед eviction ```bash kubectl describe node ``` Conditions, аллокации, события - первое место смотреть когда нода unhealthy ```bash ls /etc/kubernetes/manifests/ ``` Static pods - control-plane на kubeadm-кластерах ## См. также - [Kubernetes pod lifecycle - от Pending до Terminated](/kb/kubernetes-pod-lifecycle.md) - [runc, runsc, kata - container runtimes](/kb/runc-and-runsc.md) - [cgroups v2 - unified hierarchy, PSI, eBPF control](/kb/cgroups-v2-deep.md) - [eBPF - программируемый kernel](/kb/ebpf-basics.md) - [CNI plugins - сеть Kubernetes (calico, cilium, flannel)](/kb/cni-plugins.md) - [Prometheus: scrape, TSDB, PromQL и production-pitfalls](/kb/prometheus-basics.md)