Kubernetes

Архітектура Kubernetes — анатомія кластера

Внутрішня будова Kubernetes-кластера — control plane, worker nodes, ключові компоненти та їхня взаємодія

Архітектура Kubernetes — анатомія кластера

Від одного демона до розподіленої системи

Коли ви працювали з Docker, вся взаємодія зводилась до однієї простої схеми: ви виконували docker run, Docker-демон (dockerd) на вашому сервері отримував команду, завантажував образ і запускав контейнер. Один хост — один демон — одна точка управління. Усе прозоро і зрозуміло.

Kubernetes принципово інший. Він не є одним процесом і не працює на одній машині. Kubernetes — це розподілена система: сукупність кількох взаємодіючих компонентів, розгорнутих на різних машинах, які разом утворюють єдиний керований простір.

Розуміння цієї архітектури є необхідною умовою для ефективної роботи з Kubernetes. Без нього навіть прості помилки стають незрозумілими, а усунення несправностей перетворюється на вгадування. У цій статті ми пройдемо анатомію кластера від зовнішнього вигляду до внутрішніх механізмів — крок за кроком, без пропусків.

У цій статті ми розглядаємо архітектуру концептуально. Практичне встановлення локального кластера та перші команди — тема наступної статті.

Кластер — основна одиниця Kubernetes

Перша і найважливіша концепція, яку необхідно засвоїти: у Kubernetes ви не працюєте з окремими серверами. Ви працюєте з кластером.

Кластер (cluster) — це сукупність машин (фізичних або віртуальних серверів), обʼєднаних під управлінням Kubernetes і представлених як єдиний обчислювальний ресурс. З точки зору оператора, кластер — це «один великий комп'ютер»: ви описуєте, що хочете запустити і скільки ресурсів це потребує, а Kubernetes сам вирішує, де і як це розмістити.

Кожна машина, що входить до кластера, називається вузлом (node). Вузли бувають двох типів — але про це трохи пізніше. Спочатку познайомимось з інструментом, яким оператор взаємодіє з кластером.


kubectl — інструмент оператора

kubectl (вимовляється «кюб-контрол» або «кюбектл») — це командний рядковий інтерфейс (CLI) для взаємодії з Kubernetes-кластером. Якщо проводити паралель із Docker, kubectl відіграє ту саму роль, що і команда docker — він формує запити до API кластера та відображає відповіді.

Аналогія точна, але не повна: docker CLI спілкується з демоном на тому ж хості, тоді як kubectl може взаємодіяти з кластером з будь-якої машини — з вашого ноутбука, з CI/CD-системи, з іншого сервера — через захищене HTTPS-підключення.

Загальна анатомія команди kubectl:

kubectl [дієслово] [тип-ресурсу] [імʼя] [прапорці]
ЧастинаОписПриклад
дієсловоЩо зробитиget, apply, delete, describe
тип-ресурсуЗ яким об'єктомnode, pod, deployment, service
імʼяКонкретний екземпляр (необов'язково)my-app-pod
прапорціДодаткові параметри-n production, -o yaml

Перші команди, які ви виконаєте у будь-якому кластері:

# Переглянути вузли кластера та їхній стан
kubectl get nodes

# Переглянути поточний контекст (до якого кластера підключено)
kubectl config current-context
kubectl зберігає налаштування підключення (адреси кластерів, credentials, поточний контекст) у файлі ~/.kube/config. Завдяки цьому ви можете перемикатися між кластерами однією командою: kubectl config use-context <назва-контексту>.
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

actor "Ваш ноутбук" as L
package "Хмара / Датацентр" {
    package "Kubernetes Cluster" {
        [API Server] as API
        [Worker Nodes] as WN
    }
}

L -down-> API : HTTPS (port 6443)
note right of L
  ~/.kube/config
  (сертифікати та адреси)
end note

API <-> WN : Керування
@enduml

Тепер, коли ми знаємо інструмент оператора, розглянемо архітектуру того, з чим цей інструмент взаємодіє.


Два типи вузлів: управління та виконання

Як зазначалось вище, кожна машина у кластері — це вузол. Але не всі вузли однакові: вони поділяються на два принципово різні типи залежно від ролі, яку виконують.

Control Plane (площина управління) — це «мозок» кластера. Вузли цього типу не запускають ваші застосунки. Їхнє завдання — зберігати стан усієї системи, приймати рішення (що запустити, де розмістити, як реагувати на збої) та автоматично підтримувати бажаний стан. У невеликих кластерах control plane розгортається на одному вузлі; у production — зазвичай на трьох, для відмовостійкості.

Worker Nodes (робочі вузли) — це «м'язи» кластера. Саме тут виконуються ваші застосунки. Кожен worker-вузол отримує інструкції від control plane і запускає контейнери відповідно до цих інструкцій. Кількість worker-вузлів визначає обчислювальну потужність кластера і може змінюватись динамічно — вузли можна додавати або видаляти без зупинки системи.

Зверніть увагу на принципову різницю в порівнянні з Docker Compose: там усе виконувалось на одній машині. Тут «управління» і «виконання» фізично розділені.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff
skinparam NodeBackgroundColor #f8f9fa
skinparam PackageBackgroundColor #e9ecef

actor "Operator (kubectl)" as Op

package "Control Plane (Brain)" {
    [API Server] as API
}

package "Worker Nodes (Body)" {
    node "Node 1" as N1 {
        [Pod A]
    }
    node "Node 2" as N2 {
        [Pod B]
    }
}

Op -down-> API : HTTPS
API <-down-> N1 : Instructions/Status
API <-down-> N2 : Instructions/Status
@enduml

Зі схеми видно два потоки комунікації:

  • Оператор → Control Plane: ваші kubectl-команди надходять до control plane через HTTPS.
  • Control Plane ↔ Worker Nodes: control plane надсилає інструкції вузлам (що запускати), а вузли повертають актуальний статус (що реально виконується).

Ніколи не навпаки: оператор ніколи не спілкується з worker-вузлами напряму. Вся взаємодія відбувається через control plane.


Що таке Pod — базова одиниця виконання

Перш ніж розглядати компоненти вузлів детально, необхідно ввести одне ключове поняття, яке з'являтиметься у кожному наступному розділі.

Pod (від англ. «стручок») — це найменша розгортана одиниця у Kubernetes. Не контейнер, а саме Pod. Розуміння цього є принципово важливим для тих, хто приходить із Docker.

У Docker мінімальна одиниця — це контейнер. У Kubernetes — Pod, який є обгорткою над одним або кількома контейнерами. Pod гарантує, що всі контейнери всередині нього:

  • Запускаються і зупиняються разом
  • Знаходяться на одному вузлі
  • Мають спільну мережу (один IP на Pod)
  • Можуть мати спільні volumes (файли)
У переважній більшості випадків Pod містить один контейнер. Кілька контейнерів у Pod — це окремий патерн (sidecar), який розглядається у статті про Podʼи. Поки що сприймайте Pod як «контейнер у Kubernetes».

Аналогія: якщо Docker-контейнер — це окремий процес в ізольованому середовищі, то Pod — це «мінімальна логічна одиниця вашого застосунку», яку Kubernetes розміщує, масштабує та перезапускає як ціле.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "Pod" #f8f9fa {
    package "Спільний контекст" #e9ecef {
        [Мережа (один IP)]
        [Volumes (спільні диски)]
    }
    
    package "Контейнери" {
        [Головний застосунок]
        [Sidecar-контейнер]
    }
}
@enduml

Компоненти Control Plane

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "Control Plane" {
    [API Server] as API <<HTTP>>
    database "etcd" as ETCD <<Storage>>
    [Scheduler] as SCH <<Logic>>
    [Controller Manager] as CM <<Automation>>

    API <-> ETCD : State Storage
    SCH -up-> API : Watch / Bind
    CM -up-> API : Watch / Reconcile
}

[kubectl] -down-> API : HTTPS
[Kubelet] -up-> API : Heartbeat
@enduml

Control plane складається з чотирьох основних процесів. Кожен з них має чітко визначену відповідальність — жоден не виконує «все поспіль». Саме така модульність і дозволяє системі бути відмовостійкою та розширюваною.

kube-apiserver — єдина точка входу

kube-apiserver — це HTTP-сервер, через який проходять абсолютно всі операції у кластері. Ваш kubectl, внутрішні компоненти Kubernetes, зовнішні інструменти — всі вони звертаються виключно до API-сервера. Ніхто не спілкується з etcd або планувальником напряму.

Аналогія з Docker: kube-apiserver — це dockerd, але для цілого кластера. Так само, як docker CLI спілкується з dockerd через Unix-socket, kubectl спілкується з kube-apiserver через захищений HTTPS-канал.

Потік обробки запиту

Кожен запит до API-сервера проходить через кілька етапів обробки. Розглянемо детально на прикладі команди kubectl get pods:

Loading diagram...
sequenceDiagram
    participant K as kubectl
    participant A as API Server
    participant Auth as Authentication
    participant Authz as Authorization
    participant E as etcd

    K->>A: "GET /api/v1/namespaces/default/pods"
    A->>Auth: "Перевірити сертифікат клієнта"
    Auth-->>A: "Користувач: admin"
    A->>Authz: "Чи має admin право читати pods?"
    Authz-->>A: "Дозволено ✓"
    A->>E: "Читати /registry/pods/default/*"
    E-->>A: "Список Pod (JSON)"
    A-->>K: "HTTP 200 + JSON response"

Етапи обробки запиту:

1. Authentication (Аутентифікація)
етап
API-сервер визначає, хто надіслав запит. Підтримуються різні методи: клієнтські сертифікати, bearer tokens, basic auth. У більшості кластерів використовуються X.509 сертифікати. Уявіть це як цифровий паспорт: коли kubectl звертається до сервера, він автоматично пред'являє цей документ. Якщо паспорт недійсний або його немає — сервер навіть не почне розмову.
2. Authorization (Авторизація)
етап
API-сервер перевіряє, чи має цей користувач право виконати цю дію. Використовується RBAC (Role-Based Access Control): користувач → роль → дозволи.
3. Admission Control (Контроль прийняття)
етап
Серія плагінів, які можуть змінити або відхилити запит. Наприклад, LimitRanger перевіряє, чи не перевищує Pod ліміти ресурсів namespace. Це працює як автокорекція у смартфоні: якщо ви забули вказати важливий параметр, Admission Controller може підставити значення за замовчуванням ще до того, як дані потраплять у базу.
4. Validation (Валідація)
етап
Перевірка структури об'єкта: чи всі обов'язкові поля присутні, чи правильні типи даних, чи валідні значення.
5. Persistence (Збереження)
етап
Для запитів на створення/оновлення — збереження об'єкта у etcd. Для запитів на читання — отримання даних з etcd.
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

(*) --> "Аутентифікація / Авторизація"
--> "Mutating Admission"
note right: "Автокорекція" / Дефолти
--> "Валідація об'єкта"
--> "Validating Admission"
note right: Фінальна перевірка
--> "Збереження (etcd)"
--> (*)
@enduml

Приклад: створення Deployment

Тепер розглянемо складніший сценарій — kubectl apply -f deployment.yaml:

Loading diagram...
sequenceDiagram
    participant K as kubectl
    participant A as API Server
    participant AC as Admission Controllers
    participant E as etcd
    participant W as Watchers<br/>(scheduler, controller-manager)

    K->>A: "POST /apis/apps/v1/namespaces/default/deployments"
    Note over A: Authentication ✓<br/>Authorization ✓
    A->>AC: "MutatingAdmissionWebhook"
    AC-->>A: "Додано default values"
    A->>AC: "ValidatingAdmissionWebhook"
    AC-->>A: "Валідація пройдена ✓"
    A->>E: "Зберегти Deployment"
    E-->>A: "Збережено, revision=1"
    A-->>K: "HTTP 201 Created"

    Note over W: Watchers отримують подію
    W->>A: "Watch: нові Deployment?"
    A-->>W: "Event: Deployment створено"

Ключові моменти:

  1. Admission Controllers можуть змінювати об'єкт перед збереженням (наприклад, додавати default значення)
  2. etcd зберігає об'єкт і повертає номер ревізії
  3. Watchers (scheduler, controller-manager) отримують подію про новий об'єкт через механізм watch

Watch API — механізм реального часу

API-сервер підтримує watch API — механізм, який дозволяє компонентам отримувати події про зміни у реальному часі, без постійного опитування (polling).

# Приклад watch-запиту
kubectl get pods --watch

Під капотом це HTTP-запит з параметром ?watch=true, який залишається відкритим, і API-сервер надсилає події через цей канал:

{"type": "ADDED", "object": {"kind": "Pod", "metadata": {"name": "nginx-1"}}}
{"type": "MODIFIED", "object": {"kind": "Pod", "metadata": {"name": "nginx-1"}, "status": {"phase": "Running"}}}
{"type": "DELETED", "object": {"kind": "Pod", "metadata": {"name": "nginx-1"}}}

Саме завдяки watch API scheduler дізнається про нові Pod, а controller-manager — про зміни у Deployment.

Stateless природа API-сервера

API-сервер є stateless (без внутрішнього стану): він не запам'ятовує нічого між запитами. Весь стан кластера живе у etcd. Завдяки цьому можна запускати кілька екземплярів API-сервера паралельно для відмовостійкості — всі вони читають і пишуть до одного etcd.

Якщо один екземпляр API-сервера падає — Load Balancer перенаправляє трафік на інші. Жоден стан не втрачається, бо все зберігається у etcd.

etcd — розподілена база даних стану

etcd (вимовляється «ет-сі-ді») — це розподілене сховище типу «ключ-значення», яке є єдиним джерелом правди для всього кластера. Тут зберігається все: поточний стан Podʼів, конфігурації, секрети, інформація про вузли, права доступу.

Назва «etcd» походить від Unix-конвенції: конфігурації традиційно зберігаються у директорії /etc, а суфікс -d означає «distributed» (розподілений).

etcd — найкритичніший компонент кластера. Якщо etcd повністю втрачає дані — кластер втрачає стан: Kubernetes вже не знає, які Podʼи мали бути запущені, які Deployment існували, які секрети були збережені. Відновлення можливе лише з резервної копії. У production etcd обовʼязково розгортається у кластері з трьох вузлів та має регулярні резервні копії.

Структура даних у etcd

etcd зберігає всі об'єкти Kubernetes у ієрархічній структурі ключів. Кожен об'єкт має унікальний шлях:

/registry/
├── pods/
│   ├── default/
│   │   ├── nginx-deployment-7d6b8c9f4d-8xk2p
│   │   ├── nginx-deployment-7d6b8c9f4d-m5n7q
│   │   └── nginx-deployment-7d6b8c9f4d-z9w3r
│   └── kube-system/
│       ├── coredns-7db6d8ff4d-8xk2p
│       └── kube-apiserver-minikube
├── deployments/
│   └── default/
│       └── nginx-deployment
├── services/
│   └── default/
│       └── kubernetes
├── secrets/
│   └── default/
│       └── default-token-xxxxx
└── configmaps/
└── configmaps/
    └── kube-system/
        └── kubeadm-config
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "etcd Keyspace" {
  [registry] --> [pods]
  [registry] --> [deployments]
  [registry] --> [services]
  
  [pods] --> [default]
  [pods] --> [kube-system]
  
  [default] --> [nginx-pod-1]
  [default] --> [nginx-pod-2]
}
@enduml

Кожен ключ містить повну специфікацію об'єкта у JSON-форматі. Наприклад, /registry/pods/default/nginx-deployment-7d6b8c9f4d-8xk2p містить:

{
  "kind": "Pod",
  "apiVersion": "v1",
  "metadata": {
    "name": "nginx-deployment-7d6b8c9f4d-8xk2p",
    "namespace": "default",
    "uid": "a3f8c9d1-2e45-4b6c-8d7e-9f0a1b2c3d4e",
    "resourceVersion": "12345"
  },
  "spec": { ... },
  "status": { ... }
}

Консенсус Raft — як etcd залишається узгодженим

etcd використовує алгоритм консенсусу Raft для забезпечення узгодженості даних між вузлами кластера. Розглянемо, як це працює.

Loading diagram...
sequenceDiagram
    participant API as API Server
    participant L as etcd Leader
    participant F1 as etcd Follower 1
    participant F2 as etcd Follower 2

    API->>L: "Записати Pod (nginx-1)"
    L->>F1: "Реплікувати запис"
    L->>F2: "Реплікувати запис"
    F1-->>L: "ACK (підтверджено)"
    F2-->>L: "ACK (підтверджено)"
    Note over L: "Кворум досягнуто (2 з 3)"
    L->>L: "Commit запис"
    L-->>API: "Успішно збережено"
    L->>F1: "Commit запис"
    L->>F2: "Commit запис"

Ключові концепції Raft:

Leader (Лідер)
роль
Один вузол у кластері etcd є лідером. Всі запити на запис надходять до лідера. Він координує реплікацію даних на follower-вузли.
Follower (Послідовник)
роль
Решта вузлів є follower. Вони отримують реплікації від лідера та можуть обслуговувати запити на читання.
Quorum (Кворум)
механізм
Для підтвердження запису потрібна більшість вузлів (N/2 + 1). У кластері з 3 вузлів — мінімум 2. Кворум потрібен для вирішення проблеми Split Brain (роздвоєння мозку). Якщо через збій мережі кластер розділиться навпіл, лише та частина, що має більшість (кворум), зможе продовжувати роботу. Це гарантує, що в системі не виникне хаосу, коли дві групи вузлів приймають суперечливі рішення одночасно.
Leader Election (Вибори лідера)
механізм
Якщо лідер падає, follower автоматично проводять вибори нового лідера. Процес займає кілька секунд.

Чому непарна кількість вузлів?

etcd рекомендується розгортати у кластері з непарною кількістю вузлів (3, 5, 7). Розглянемо чому:

Кількість вузлівКворумВитримує відмовКоментар
110Немає відмовостійкості
220Якщо один впаде — кворум втрачено
321✅ Оптимально для невеликих кластерів
431Витримує стільки ж відмов, що й 3, але дорожче
532✅ Оптимально для production
642Витримує стільки ж відмов, що й 5, але дорожче
743✅ Для критичних систем

Висновок: Кластер з 4 вузлів витримує лише 1 відмову (так само, як 3 вузли), але споживає більше ресурсів. Тому завжди використовуйте непарну кількість.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

node "etcd Cluster" {
    package "Leader" #LightGreen {
        [State Store L]
    }
    package "Follower 1" {
        [State Store F1]
    }
    package "Follower 2" {
        [State Store F2]
    }

    [State Store L] -down-> [State Store F1] : Replicate (Raft)
    [State Store L] -down-> [State Store F2] : Replicate (Raft)
    [State Store F1] .up.> [State Store L] : ACK
    [State Store F2] .up.> [State Store L] : ACK
}

note right of Leader
  Кворум = N/2 + 1
  (2 з 3 вузлів)
end note
@enduml

Продуктивність та обмеження

etcd оптимізовано для узгодженості (consistency), а не для продуктивності. Типові характеристики:

  • Пропускна здатність запису: ~10,000 записів/сек (залежить від розміру об'єктів)
  • Латентність запису: 10-50 мс (залежить від мережі між вузлами)
  • Розмір бази даних: рекомендовано до 8 ГБ (за замовчуванням ліміт — 2 ГБ)
  • Кількість об'єктів: до ~100,000 Pod у кластері
Обмеження etcd: Якщо база даних etcd перевищує 8 ГБ або кластер містить понад 5,000 вузлів — продуктивність деградує. Для таких масштабів потрібна федерація кластерів або інші рішення.

Резервне копіювання etcd

Оскільки etcd — єдине джерело правди, регулярні резервні копії критично важливі:

# Створити snapshot etcd
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.db \
  --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
ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-snapshot.db

У production рекомендується автоматизувати резервне копіювання (наприклад, через CronJob у Kubernetes) та зберігати копії у віддаленому сховищі (S3, GCS).

kube-scheduler — планувальник розміщення

Коли у кластері зʼявляється новий Pod, якому ще не призначено вузол (статус Pending), завдання kube-scheduler — знайти для нього найбільш підходящий worker-вузол.

Scheduler не призначає вузол навмання. Він аналізує кожен доступний вузол і перевіряє цілий ряд умов через двофазний процес: фільтрація та оцінювання.

Процес планування: від Pending до Running

Loading diagram...
sequenceDiagram
    participant D as Deployment Controller
    participant API as API Server
    participant S as Scheduler
    participant K as Kubelet

    D->>API: "Створити Pod (nodeName: null)"
    API->>API: "Зберегти у etcd (status: Pending)"

    S->>API: "Watch: нові Pod без nodeName?"
    API-->>S: "Pod nginx-1 (nodeName: null)"

    Note over S: "Фаза 1: Фільтрація"
    S->>S: "Перевірити вузли<br/>(ресурси, taints, affinity)"
    Note over S: "Вузли: [node-1 ✓, node-2 ✗, node-3 ✓]"

    Note over S: "Фаза 2: Оцінювання"
    S->>S: "Ранжувати вузли<br/>(балансування, locality)"
    Note over S: "node-1: 85 балів<br/>node-3: 72 бали"

    S->>API: "PATCH Pod: nodeName = node-1"
    API->>API: "Оновити у etcd"

    K->>API: "Watch: Pod для мого вузла?"
    API-->>K: "Pod nginx-1 (nodeName: node-1)"
    K->>K: "Запустити контейнер"
    K->>API: "PATCH Pod: status = Running"

Фаза 1: Фільтрація (Predicate)

Scheduler перевіряє кожен вузол на відповідність обов'язковим вимогам. Якщо вузол не проходить хоча б одну перевірку — він виключається з розгляду.

PodFitsResources
фільтр
Чи достатньо на вузлі CPU та памʼяті для задоволення requests Podʼу? Якщо Pod потребує 500m CPU, а на вузлі залишилось лише 200m — вузол відхиляється.
Як вимірюються ресурси в Kubernetes?
  • CPU вимірюється в мілікорах (m). 1000m дорівнює одному повному ядру процесора. Якщо Pod просить 500m, йому потрібно рівно половина потужності одного ядра.
  • Пам'ять вимірюється в байтах з префіксами. Найчастіше ви побачите Mi (Мебібайти, 1024 KB), на відміну від звичайних MB (1000 KB).

::

PodFitsHostPorts
фільтр
Чи не зайнятий порт, який Pod хоче відкрити на вузлі через hostPort? Два Pod не можуть використовувати один і той самий hostPort на одному вузлі.
NodeSelector
фільтр
Чи має вузол мітки, які вимагає Pod через nodeSelector? Наприклад, якщо Pod вимагає disktype=ssd, вузли без цієї мітки відхиляються. Це найпростіший спосіб сказати: "Хочу працювати тільки на вузлі з SSD".
TaintToleration
фільтр
Чи має Pod toleration для всіх taints вузла? Taint — це "табличка на дверях" вузла: "Тільки для GPU" або "Тут ремонт". Вузол відштовхує всі Pod, які не мають спеціальної перепустки (toleration).
PodAffinity/AntiAffinity
фільтр
Правила "сусідства". Affinity дозволяє запустити контейнер поруч із певним Pod (наприклад, щоб вони спілкувалися через локальну мережу швидше). Anti-Affinity навпаки — забороняє сусідство. Наприклад: "Ніколи не запускай основну базу і її бекап на одному фізичному сервері", щоб уникнути втрати всього при збої заліза.
VolumeBinding
фільтр
Чи може вузол змонтувати всі volumes, які потребує Pod? Наприклад, якщо Pod потребує PersistentVolume, прив'язаний до зони us-east-1a, вузли з інших зон відхиляються.

::

Приклад фільтрації:

Кластер: 5 вузлів
Pod потребує: 2 CPU, 4 Gi RAM, мітка disktype=ssd

Вузол 1: 1 CPU доступно   → ✗ Відхилено (PodFitsResources)
Вузол 2: 3 CPU, 8 Gi RAM  → ✓ Пройшов
Вузол 3: 4 CPU, 6 Gi RAM, disktype=hdd → ✗ Відхилено (NodeSelector)
Вузол 4: 3 CPU, 8 Gi RAM, disktype=ssd → ✓ Пройшов
Вузол 5: 2 CPU, 4 Gi RAM, disktype=ssd → ✓ Пройшов

Результат фільтрації: [Вузол 2, Вузол 4, Вузол 5]

Фаза 2: Оцінювання (Scoring)

Серед вузлів, що пройшли фільтрацію, scheduler ранжує їх за балами (0-100). Вузол з найвищим балом обирається для розміщення Pod.

LeastRequestedPriority
оцінка
Надає перевагу вузлам з меншим використанням ресурсів. Формула: (capacity - requests) / capacity * 100. Мета — рівномірно розподілити навантаження.
BalancedResourceAllocation
оцінка
Надає перевагу вузлам, де CPU та Memory використовуються пропорційно. Якщо на вузлі 80% CPU зайнято, але лише 20% RAM — бал знижується.
NodeAffinityPriority
оцінка
Надає перевагу вузлам, які відповідають preferredDuringSchedulingIgnoredDuringExecution affinity правилам (м'які вимоги).
InterPodAffinityPriority
оцінка
Надає перевагу вузлам, де вже працюють Pod з певними мітками (для locality) або, навпаки, де їх немає (для anti-affinity).
ImageLocalityPriority
оцінка
Надає перевагу вузлам, де образ контейнера вже завантажено. Це зменшує час запуску Pod.

Приклад оцінювання:

Вузли після фільтрації: [Вузол 2, Вузол 4, Вузол 5]

Вузол 2:
  - LeastRequestedPriority: 60 балів (40% ресурсів зайнято)
  - BalancedResourceAllocation: 80 балів (CPU 45%, RAM 40%)
  - ImageLocalityPriority: 0 балів (образ не завантажено)
  Загальний бал: (60 + 80 + 0) / 3 = 47 балів

Вузол 4:
  - LeastRequestedPriority: 80 балів (20% ресурсів зайнято)
  - BalancedResourceAllocation: 90 балів (CPU 22%, RAM 18%)
  - ImageLocalityPriority: 100 балів (образ вже є)
  Загальний бал: (80 + 90 + 100) / 3 = 90 балів ← Переможець

Вузол 5:
  - LeastRequestedPriority: 50 балів (50% ресурсів зайнято)
  - BalancedResourceAllocation: 70 балів (CPU 60%, RAM 40%)
  - ImageLocalityPriority: 0 балів (образ не завантажено)
  Загальний бал: (50 + 70 + 0) / 3 = 40 балів

Результат: Pod призначається Вузлу 4

Візуалізація процесу планування

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

(*) --> "New Pod (Pending)"
--> "Phase 1: Filtering (Predicates)"
note right: Resources, Labels, Taints...
--> "Phase 2: Scoring (Priorities)"
note right: Least Requested, Image Locality...
--> "Binding"
--> "Pod Assigned to Node"
--> (*)

@enduml

Що робити, якщо жоден вузол не підходить?

Якщо після фільтрації не залишилось жодного вузла — Pod залишається у стані Pending з причиною Unschedulable. Scheduler періодично повторює спробу планування (кожні кілька секунд).

Типові причини:

  • Insufficient CPU/Memory: Немає вузлів з достатніми ресурсами
  • No nodes available: Усі вузли мають taints, які Pod не толерує
  • PersistentVolume not available: Немає доступних volumes у потрібній зоні

Переглянути причину:

kubectl describe pod <назва-podʼу>

У секції Events буде повідомлення на кшталт:

Warning  FailedScheduling  Pod cannot be scheduled: 0/3 nodes are available:
         3 Insufficient cpu.

kube-controller-manager — двигун самовідновлення

Цей підхід має назву reconciliation loop (цикл узгодження) і є фундаментальним принципом Kubernetes. Уявіть це як круїз-контроль в автомобілі: ви виставляєте бажану швидкість (наприклад, 100 км/год), а система постійно перевіряє реальну швидкість і автоматично додає або скидає газ, щоб вона збігалася з вашим бажанням.

Reconciliation Loop — серце Kubernetes

Loading diagram...
stateDiagram-v2
    [*] --> Watch: Контролер запущено
    Watch --> Compare: Отримано подію
    Compare --> Match: Стани збігаються?
    Match --> Watch: Так ✓
    Match --> Reconcile: Ні ✗
    Reconcile --> APICall: Створити/Оновити/Видалити
    APICall --> Watch: Повернутись до спостереження

    note right of Watch
        Контролер відстежує зміни
        через Watch API
    end note

    note right of Compare
        Порівняння:
        Desired State vs Current State
    end note

    note right of Reconcile
        Усунення розбіжностей:
        - Створити відсутні Pod
        - Видалити зайві Pod
        - Оновити конфігурацію
    end note

    classDef blue fill:#3b82f6,stroke:#1d4ed8,color:#fff
    classDef orange fill:#f59e0b,stroke:#b45309,color:#fff
    classDef teal fill:#0f766e,stroke:#134e4a,color:#fff

    class Watch blue
    class Compare orange
    class Reconcile teal

Приклад: ReplicaSet Controller

Розглянемо, як ReplicaSet Controller підтримує задану кількість реплік:

Бажаний стан (Desired State):
  ReplicaSet "nginx" має мати 3 репліки

Поточний стан (Current State):
  Реально працює 2 Pod з міткою app=nginx

Розбіжність (Drift):
  3 (бажано) - 2 (реально) = 1 (не вистачає)

Дія контролера (Reconciliation):
  Створити 1 новий Pod через API-сервер

Новий стан:
  3 репліки ✓ (стани збіглися)
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

state "Спостереження (Watch)" as Watch
state "Порівняння (Compare)" as Compare
state "Дія (Reconcile)" as Action

[*] --> Watch
Watch --> Compare : Подія
Compare --> Watch : Збігається ✓
Compare --> Action : Розбіжність ✗
Action --> Watch : Виправлено
@enduml

Основні контролери у kube-controller-manager

У kube-controller-manager працює понад 20 контролерів. Розглянемо найважливіші:

ReplicaSet Controller
контролер
Підтримує задану кількість реплік Pod. Якщо Pod видаляється — створює новий. Якщо Pod більше, ніж потрібно — видаляє зайві.
Deployment Controller
контролер
Керує оновленнями Deployment через створення нових ReplicaSet та поступове зменшення старих. Реалізує rolling updates.
Node Controller
контролер
Відстежує стан вузлів. Якщо вузол не відповідає протягом 40 секунд — позначає його як NotReady. Якщо не відповідає 5 хвилин — евакуює Pod на інші вузли.
Job Controller
контролер
Запускає Pod для одноразових задач. Стежить за успішним завершенням і створює нові Pod при невдачах (згідно з backoffLimit).
Service Account Controller
контролер
Автоматично створює default ServiceAccount для кожного нового namespace та генерує токени для автентифікації Pod.
Endpoint Controller
контролер
Відстежує Pod, які відповідають селектору Service, та оновлює об'єкт Endpoints зі списком IP-адрес цих Pod.
Namespace Controller
контролер
При видаленні namespace видаляє всі ресурси всередині нього (Pod, Service, ConfigMap тощо).

Приклад: Node Controller у дії

Розглянемо детально, як Node Controller реагує на недоступність вузла:

Loading diagram...
sequenceDiagram
    participant NC as Node Controller
    participant API as API Server
    participant K as Kubelet (Node 2)
    participant S as Scheduler

    loop Кожні 5 секунд
        NC->>API: "Перевірити heartbeat від вузлів"
    end

    Note over K: "Вузол 2 втратив мережу"

    NC->>API: "Heartbeat від Node 2?"
    API-->>NC: "Останній heartbeat: 45 секунд тому"
    NC->>API: "PATCH Node 2: status = NotReady"

    Note over NC: "Чекає 5 хвилин..."

    NC->>API: "Node 2 досі NotReady?"
    API-->>NC: "Так, 5 хвилин 10 секунд"

    NC->>API: "Отримати Pod на Node 2"
    API-->>NC: "[pod-1, pod-2, pod-3]"

    NC->>API: "DELETE pod-1, pod-2, pod-3"
    Note over API: "Pod позначені як Terminating"

    Note over S: "Scheduler бачить нові Pod<br/>без nodeName"
    S->>API: "Призначити Pod на інші вузли"

Таймлайн подій:

  1. 0 сек: Вузол 2 втрачає мережеве з'єднання
  2. 40 сек: Node Controller позначає вузол як NotReady
  3. 5 хв 10 сек: Node Controller видаляє Pod з недоступного вузла
  4. 5 хв 15 сек: Scheduler призначає нові Pod на здорові вузли
  5. 5 хв 30 сек: Kubelet на здорових вузлах запускає контейнери
Налаштування таймаутів: Таймаути Node Controller можна змінити через прапорці kube-controller-manager:
  • --node-monitor-period=5s — як часто перевіряти heartbeat
  • --node-monitor-grace-period=40s — скільки чекати перед NotReady
  • --pod-eviction-timeout=5m — скільки чекати перед евакуацією Pod

Чому контролери працюють в одному процесі?

Усі контролери запускаються в одному процесі kube-controller-manager з кількох причин:

  1. Економія ресурсів: Спільне використання пам'яті та мережевих з'єднань до API-сервера
  2. Спрощення розгортання: Один бінарник замість десятків окремих процесів
  3. Узгоджена конфігурація: Всі контролери використовують одні й ті ж налаштування (kubeconfig, rate limits)

Але кожен контролер працює у власній goroutine (легковаговий потік у Go) і не блокує інші контролери.


Компоненти Worker Node

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

node "Worker Node" {
    [Kubelet] as K <<Agent>>
    [kube-proxy] as KP <<Network>>
    
    package "Container Runtime" as CR {
        [Pod 1]
        [Pod 2]
    }

    K -down-> CR : Manage lifecycle
    KP -down-> CR : Configure networking
}

[API Server] <-up-> K : PULL Spec / PUSH Status
@enduml

Якщо control plane — це мозок, що приймає рішення, то worker node — це тіло, що їх виконує. На кожному worker-вузлі запущені три компоненти Kubernetes.

kubelet — агент вузла

kubelet — це основний «агент» Kubernetes, який працює на кожному вузлі кластера. Його можна порівняти з виконробом (будівельним менеджером) на об'єкті: він отримує плани будівель (Pod Spec) від головного офісу (Control Plane) і стежить за тим, щоб робітники (Container Runtime) побудували все точно за схемою.

Він постійно спілкується з API-сервером: отримує список Podʼів, які мають виконуватись на його вузлі, і гарантує, що вони реально запущені та справні.

Аналогія: kubelet схожий на dockerd у тому сенсі, що саме він фізично запускає контейнери. Але замість того, щоб отримувати накази від вас напряму — він отримує їх від API-сервера у вигляді специфікацій Podʼів.

Життєвий цикл Pod з точки зору kubelet

Loading diagram...
sequenceDiagram
    participant API as API Server
    participant K as Kubelet
    participant CRI as containerd (CRI)
    participant CNI as CNI Plugin
    participant CSI as CSI Plugin

    K->>API: "Watch: Pod для мого вузла?"
    API-->>K: "Pod "nginx-1" (nodeName: "node-1")"

    Note over K: Фаза 1: Підготовка
    K->>CSI: "Змонтувати volumes"
    CSI-->>K: "Volumes готові"
    K->>CNI: "Створити мережу для Pod"
    CNI-->>K: "IP: 10.244.1.5"

    Note over K: Фаза 2: Запуск контейнерів
    K->>CRI: "PullImage("nginx:1.25")"
    CRI-->>K: "Image завантажено"
    K->>CRI: "CreateContainer(nginx-1)"
    CRI-->>K: "Container створено"
    K->>CRI: "StartContainer(nginx-1)"
    CRI-->>K: "Container запущено"

    Note over K: Фаза 3: Моніторинг
    loop Кожні 10 секунд
        K->>CRI: "ContainerStatus(nginx-1)"
        CRI-->>K: "Running ✓"
        K->>K: "Виконати liveness probe"
        K->>K: "Виконати readiness probe"
    end

    K->>API: "PATCH Pod: status = Running"

Основні обов'язки kubelet

Pod Lifecycle Management
обов'язок
Запуск, зупинка та перезапуск контейнерів згідно з специфікацією Pod. Kubelet гарантує, що контейнери працюють відповідно до restartPolicy.
Health Checks
обов'язок
Виконання liveness, readiness та startup probes. Якщо liveness probe не проходить — kubelet перезапускає контейнер. Якщо readiness probe не проходить — Pod не отримує трафік.
Volume Management
обов'язок
Монтування volumes у контейнери через CSI (Container Storage Interface). Підтримка emptyDir, hostPath, PersistentVolume тощо.
Resource Monitoring
обов'язок
Збір метрик використання CPU та памʼяті через cAdvisor (вбудований у kubelet). Ці метрики використовуються Metrics Server для HPA.
Status Reporting
обов'язок
Регулярне оновлення статусу Pod у API-сервері: фаза (Pending/Running/Succeeded/Failed), IP-адреса, стан контейнерів.
Node Status
обов'язок
Надсилання heartbeat до API-сервера кожні 10 секунд. Якщо heartbeat не надходить — Node Controller позначає вузол як NotReady.

Взаємодія з Container Runtime через CRI

Kubelet не запускає контейнери напряму. Він використовує стандартний інтерфейс CRI (Container Runtime Interface) для взаємодії з container runtime (containerd, CRI-O).

Шари абстракції:

  1. Kubelet → отримує Pod Spec від API-сервера
  2. CRI (containerd) → перетворює Pod Spec у виклики container runtime
  3. OCI Runtime (runc) → створює Linux namespaces, cgroups та запускає процес
  4. Container Process → ваш застосунок працює в ізольованому середовищі

Статичні Pod (Static Pods)

Kubelet може запускати Pod не лише з API-сервера, а й з локальних файлів на вузлі. Це називається Static Pods.

# /etc/kubernetes/manifests/nginx.yaml
apiVersion: v1
kind: Pod
metadata:
    name: nginx-static
spec:
    containers:
        - name: nginx
          image: nginx:1.25

Kubelet відстежує директорію /etc/kubernetes/manifests/ (налаштовується через --pod-manifest-path) та автоматично запускає Pod з файлів у цій директорії.

Використання Static Pods: Саме через Static Pods запускаються компоненти control plane у кластерах, створених через kubeadm. Файли kube-apiserver.yaml, etcd.yaml, kube-scheduler.yaml знаходяться у /etc/kubernetes/manifests/, і kubelet на control plane вузлі запускає їх як звичайні Pod.
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

node "Worker Node" {
    folder "/etc/kubernetes/manifests/" as M {
        file "nginx.yaml"
    }
    [kubelet]
    [Container Runtime] as CR
}

[kubelet] -up-> M : Читає файли кожні 20с
[kubelet] -down-> CR : Запускає контейнер
@enduml

kube-proxy — мережевий проксі

kube-proxy відповідає за реалізацію мережевих правил на вузлі. Щоразу, коли у кластері створюється або видаляється Service (мережева абстракція для доступу до Pod), kube-proxy оновлює правила маршрутизації на своєму вузлі.

Як працює Service: від віртуальної IP до реальних Pod

Коли ви створюєте Service, Kubernetes призначає йому ClusterIP — віртуальну IP-адресу, яка не існує на жодному інтерфейсі. Завдання kube-proxy — перехопити трафік до цієї віртуальної IP та перенаправити його до реальних Pod.

Loading diagram...
sequenceDiagram
    participant P as Pod (Client)
    participant IP as iptables/IPVS
    participant Pod1 as Backend Pod 1
    participant Pod2 as Backend Pod 2
    participant Pod3 as Backend Pod 3

    Note over P: Запит до Service<br/>ClusterIP: 10.96.0.10:80

    P->>IP: "TCP connect 10.96.0.10:80"

    Note over IP: kube-proxy налаштував правила:<br/>10.96.0.10:80 → [10.244.1.5, 10.244.2.8, 10.244.3.2]

    IP->>Pod2: "Перенаправити на 10.244.2.8:8080"
    Pod2-->>IP: "HTTP Response"
    IP-->>P: "HTTP Response"

    Note over IP: "Наступний запит піде до іншого Pod<br/>(round-robin балансування)"

Режими роботи kube-proxy

kube-proxy підтримує три режими реалізації мережевих правил:

iptables (за замовчуванням)
режим
Використовує Linux iptables для NAT. Це як паперовий список у охоронця, який він має переглядати рядок за рядком для кожного запиту. Простий метод, але при тисячах правил (сервісів) він починає сильно "гальмувати".
IPVS (рекомендовано)
режим
Використовує Linux IPVS — спеціальний модуль ядра для балансування. Це як швидкісний електронний турнікет: він миттєво знає, куди направити трафік, незалежно від кількості правил. Підтримує різні алгоритми балансування (round-robin, least connection тощо).
userspace (застарілий)
режим
kube-proxy працює як проксі у user space. Повільний, використовувався у ранніх версіях Kubernetes. Не рекомендується.

Приклад iptables правил для Service:

# Service: nginx-service (ClusterIP: 10.96.0.10)
# Backends: 10.244.1.5:8080, 10.244.2.8:8080, 10.244.3.2:8080

# Правило 1: Перехопити трафік до ClusterIP
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m tcp --dport 80 -j KUBE-SVC-NGINX

# Правило 2: Балансування між Pod (33% ймовірність кожен)
-A KUBE-SVC-NGINX -m statistic --mode random --probability 0.33 -j KUBE-SEP-POD1
-A KUBE-SVC-NGINX -m statistic --mode random --probability 0.50 -j KUBE-SEP-POD2
-A KUBE-SVC-NGINX -j KUBE-SEP-POD3

# Правило 3: DNAT до реальних Pod
-A KUBE-SEP-POD1 -p tcp -m tcp -j DNAT --to-destination 10.244.1.5:8080
-A KUBE-SEP-POD2 -p tcp -m tcp -j DNAT --to-destination 10.244.2.8:8080
-A KUBE-SEP-POD3 -p tcp -m tcp -j DNAT --to-destination 10.244.3.2:8080
Обмеження iptables: У кластерах з 5,000+ Service iptables правил стає настільки багато, що оновлення займає секунди. Для таких масштабів обов'язково використовуйте IPVS режим.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

actor Client
rectangle "Service (Virtual IP)" as SVC #SkyBlue

node "Worker Node" {
    [iptables / IPVS] as NET
    [Pod A]
    [Pod B]
}

Client -> SVC : 10.96.0.10:80
SVC -down-> NET : Redirect
NET -down-> [Pod A] : LB (Round Robin)
NET -down-> [Pod B] : LB (Round Robin)

[kube-proxy] -right-> NET : Update Rules
@enduml

Container Runtime — виконавець контейнерів

Container Runtime — це програма, яка безпосередньо запускає контейнери. Kubernetes не запускає контейнери сам — він делегує цю роботу container runtime через стандартний інтерфейс CRI (Container Runtime Interface).

Еволюція: від Docker до containerd

Історично Kubernetes використовував Docker як container runtime. Але Docker — це повноцінна платформа з CLI, API, image registry тощо. Kubernetes потребував лише частину функціональності — запуск контейнерів.

Loading diagram...
graph TD
    subgraph "Kubernetes до 1.24 (з Docker)"
        K1[Kubelet] --> DS[dockershim]
        DS --> D[Docker Engine]
        D --> C1[containerd]
        C1 --> R1[runc]
    end

    subgraph "Kubernetes після 1.24 (без Docker)"
        K2[Kubelet] --> C2[containerd]
        C2 --> R2[runc]
    end

    style DS fill:#ef4444,stroke:#b91c1c,color:#fff
    style D fill:#ef4444,stroke:#b91c1c,color:#fff
    style C1 fill:#3b82f6,stroke:#1d4ed8,color:#fff
    style C2 fill:#22c55e,stroke:#15803d,color:#fff

Що змінилось у Kubernetes 1.24:

  • ❌ Видалено dockershim — проміжний шар між kubelet та Docker
  • ✅ Kubelet тепер спілкується з containerd напряму через CRI
  • ✅ Зменшено латентність запуску контейнерів (менше шарів абстракції)
  • ✅ Спрощено архітектуру (менше компонентів)
Важливо: Видалення Docker як runtime не впливає на розробку. Ви досі можете використовувати docker build для створення образів. Образи, зібрані Docker, повністю сумісні з containerd, бо обидва дотримуються стандарту OCI (Open Container Initiative).

CRI — Container Runtime Interface

CRI — це gRPC API, який визначає стандарт взаємодії. Уявіть це як стандартну розетку (USB). Kubernetes — це ноутбук, а Container Runtime (containerd, CRI-O) — це периферія. Завдяки CRI ви можете підключити будь-який сумісний runtime, не змінюючи код самого Kubernetes.

Основні операції CRI:

ImageService
API
Управління образами: завантаження (PullImage), видалення (RemoveImage), перегляд (ListImages).
RuntimeService
API
Управління контейнерами та Pod: створення (CreateContainer), запуск (StartContainer), зупинка (StopContainer), видалення (RemoveContainer).
PodSandbox
концепція
CRI вводить концепцію "sandbox" — ізольоване середовище для Pod. Спочатку створюється sandbox (мережа, IPC namespace), потім у ньому запускаються контейнери.

Приклад gRPC виклику через CRI:

// Kubelet → containerd
service RuntimeService {
  rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
  rpc StartContainer(StartContainerRequest) returns (StartContainerResponse);
  rpc StopContainer(StopContainerRequest) returns (StopContainerResponse);
  rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse);
  rpc ListContainers(ListContainersRequest) returns (ListContainersResponse);
  rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse);
}

containerd — найпопулярніший CRI runtime

containerd — це високопродуктивний container runtime, який використовується у більшості Kubernetes-кластерів. Він є частиною CNCF (Cloud Native Computing Foundation) і підтримується спільнотою.

Архітектура containerd:

Роль кожного компонента:

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

[Kubelet] -down-> [CRI Plugin] : gRPC Request
[CRI Plugin] -down-> [containerd / CRI-O] : Lifecycle
[containerd / CRI-O] -down-> [containerd-shim]
[containerd-shim] -down-> [runc (OCI)] : System Calls
[runc (OCI)] -down-> [Linux Container] : Namespaces / Cgroups
@enduml
  1. CRI Plugin: Реалізує CRI API для kubelet
  2. containerd Core: Управління життєвим циклом контейнерів, образами, snapshots
  3. containerd-shim: Проміжний процес між containerd та runc. Дозволяє containerd перезапускатись без впливу на контейнери
  4. runc: OCI-сумісний runtime, який створює Linux namespaces, cgroups та запускає процес

OCI — Open Container Initiative

OCI — це відкритий стандарт для контейнерів, який визначає:

  1. Image Spec: Формат образів контейнерів (layers, manifest, config)
  2. Runtime Spec: Як запускати контейнер (namespaces, cgroups, capabilities)

Завдяки OCI образи, зібрані Docker, працюють у containerd, CRI-O, Podman тощо. Всі вони дотримуються одного стандарту.

Приклад OCI Runtime Spec:

{
    "ociVersion": "1.0.0",
    "process": {
        "terminal": false,
        "user": { "uid": 0, "gid": 0 },
        "args": ["nginx", "-g", "daemon off;"],
        "env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
        "cwd": "/"
    },
    "root": {
        "path": "/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/42/fs",
        "readonly": false
    },
    "mounts": [
        { "destination": "/proc", "type": "proc", "source": "proc" },
        { "destination": "/dev", "type": "tmpfs", "source": "tmpfs" }
    ],
    "linux": {
        "namespaces": [
            { "type": "pid" },
            { "type": "network" },
            { "type": "ipc" },
            { "type": "uts" },
            { "type": "mount" }
        ],
        "resources": {
            "memory": { "limit": 268435456 },
            "cpu": { "quota": 50000, "period": 100000 }
        }
    }
}

Цей JSON передається runc, який створює контейнер згідно зі специфікацією.

Альтернативи containerd

CRI-O
runtime
Мінімалістичний CRI runtime, створений спеціально для Kubernetes. Не має зайвої функціональності, яка не потрібна Kubernetes. Використовується у OpenShift.
Docker Engine (через cri-dockerd)
runtime
Після видалення dockershim з'явився проєкт cri-dockerd — зовнішній адаптер CRI для Docker. Дозволяє продовжувати використовувати Docker як runtime.
Kata Containers
runtime
Запускає контейнери у легких віртуальних машинах замість Linux namespaces. Забезпечує сильнішу ізоляцію для multi-tenant кластерів.

Повна картина: всі компоненти разом

Тепер, коли кожен компонент детально розглянуто, подивимось на повну топологію кластера з усіма внутрішніми зв'язками:


Резюме

Kubernetes — це розподілена система з чіткою архітектурою. Кластер складається з двох типів вузлів:

  • Control Plane: kube-apiserver (єдина точка входу), etcd (сховище стану), kube-scheduler (планування Podʼів), kube-controller-manager (reconciliation loop)
  • Worker Nodes: kubelet (агент вузла), kube-proxy (мережа), containerd (запуск контейнерів)

Pod — це найменша розгортана одиниця, обгортка над одним або кількома контейнерами.

Ключовий принцип — reconciliation loop: система безперервно порівнює бажаний стан з поточним і усуває розбіжності. Це основа self-healing.

У наступній статті ми переходимо від теорії до практики: встановимо локальний кластер за допомогою minikube та виконаємо перші команди kubectl.


Практичні завдання

Рівень 1 (Розуміння)

Завдання 1. Намалюйте (на папері або в будь-якому інструменті) схему Kubernetes-кластера з одним control plane вузлом і двома worker-вузлами. Позначте на кожному вузлі всі компоненти, що на ньому виконуються. Проведіть стрілки між компонентами, що взаємодіють між собою.

Завдання 2. Заповніть таблицю аналогій «Docker → Kubernetes»:

Концепція DockerАналог у Kubernetes
dockerd?
docker CLI?
Контейнер?
docker run?
docker ps?

Рівень 2 (Аналіз)

Завдання 3. Ваш кластер має один control plane вузол і три worker-вузли. Worker Node 2 раптово стає недоступним. Опишіть покроково: який компонент першим виявляє проблему, яким чином, що відбувається з Podʼами, що виконувались на цьому вузлі, і скільки орієнтовно часу займе відновлення.

Завдання 4. Чому etcd рекомендується розгортати у кластері з непарною кількістю вузлів (3, 5, 7)? Знайдіть відповідь у концепції «кворум» (quorum) у розподілених системах та поясніть своїми словами: що відбудеться з кластером з 4 вузлями etcd, якщо одночасно впадуть 2?

Рівень 3 (Архітектурне мислення)

Завдання 5. Ви проектуєте production-кластер для критичного застосунку з вимогою 99.99% uptime. Визначте мінімальну топологію: скільки вузлів control plane та скільки worker-вузлів потрібно, щоб витримати одночасний вихід з ладу одного control plane вузла і одного worker-вузла без деградації сервісу? Поясніть логіку свого рішення.