Kubernetes

Kubernetes — коли Docker Compose більше не вистачає

Від одного сервера до кластера — чому виникає потреба в оркестрації, що таке Kubernetes і яке місце він займає у сучасній інфраструктурі

Kubernetes — коли Docker Compose більше не вистачає

П'ятниця, 23:47. Дзвінок від клієнта

Уявіть: ваш застосунок працює у production вже три місяці. Ви з командою налаштували Docker Compose зі стеком ASP.NET Core + PostgreSQL + Redis, розгорнули все на одному сервері, і система справно обслуговує кількасот користувачів на день. Усе добре.

Але в п'ятницю ввечері щось іде не так. Сервер втрачає з'єднання з мережею. Контейнери зупиняються. Усі три компоненти застосунку — API, база даних і кеш — недоступні одночасно. Клієнт телефонує: «Сайт не працює». Ви підключаєтесь до сервера, вручну перезапускаєте контейнери, чекаєте, поки PostgreSQL пройде ініціалізацію. Через 12 хвилин система відновлює роботу.

А тепер запитання: чи можна було уникнути цих 12 хвилин простою? Чи міг би застосунок відновитися сам, без вашого втручання? Чи міг би він автоматично перемкнутися на інший сервер, поки перший недоступний?

Відповідь — так. Але Docker Compose на це не здатний.

Ця стаття завершує цикл Docker і відкриває новий — Kubernetes. Якщо ви ще не знайомі з Docker, контейнерами та Docker Compose, рекомендуємо спочатку опрацювати відповідний розділ курсу, починаючи зі статті «Контейнеризація — від проблеми до рішення».

Як усе працювало до Kubernetes (Підводимо до болю)

Типова архітектура додатку: Зазвичай додаток складається з Frontend (веб- чи мобільний інтерфейс) та Backend, де працюють різні сервіси (наприклад, сервіс авторизації, маркетплейс, сервіс сповіщень) та база даних.

Проблема №1: Відсутність відмовостійкості

Якщо всі сервіси розмістити на одному сервері, то у разі його падіння весь додаток перестає працювати. Це призводить до простоїв, втрати грошей та репутації.

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

node "Один Сервер (VPS)" as server #f8f9fa {
    [Frontend]
    [Auth Service]
    [Marketplace]
    database "Database"
}
note bottom of server : Якщо сервер падає - падає все
@enduml

Часткове рішення: Сервіси розподіляють по кількох різних серверах або віртуальних машинах.

Проблема №2: Ручне балансування

Щоб розподіляти навантаження між цими серверами, ставлять балансувальник трафіку. Але в ньому доводиться вручну прописувати IP-адреси та порти всіх серверів, щоб він знав, куди перенаправляти користувачів.

Loading diagram...
graph TD
    User(("Користувач")) --> LB["Балансувальник"]
    LB -->|"IP: 10.0.0.1"| S1["Сервер 1"]
    LB -->|"IP: 10.0.0.2"| S2["Сервер 2"]
    LB -.->|"Треба вручну додати IP"| S3["Сервер 3"]
    
    style LB fill:#8b5cf6,stroke:#5b21b6,color:#ffffff
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

actor "Користувач" as User
rectangle "Балансувальник" as LB #8b5cf6
node "Сервер 1 (10.0.0.1)" as S1
node "Сервер 2 (10.0.0.2)" as S2
node "Сервер 3 (10.0.0.3)" as S3 #LightGray

User -down-> LB
LB -down-> S1 : OK
LB -down-> S2 : OK
LB ..> S3 : Треба додати вручну
@enduml

Проблема №3: Складність масштабування

Коли навантаження зростає, потрібно додати новий екземпляр сервісу. Це робиться руками: розробник заходить на сервер, розгортає додаток і знову вручну прописує в балансувальнику, щоб туди йшло, наприклад, 20% трафіку.

Проблема №4: Падіння сервісів (Downtime)

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

Loading diagram...
graph LR
    LB["Балансувальник"] --> S1["Сервер 1 (OK)"]
    LB --> S2["Сервер 2 (OK)"]
    LB -.->|"Шле запити"| S3["Сервер 3 (Впав)"]
    
    style S3 fill:#ef4444,stroke:#991b1b,color:#ffffff
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

rectangle "Балансувальник" as LB #8b5cf6
node "Сервер 1" as S1 #LightGreen
node "Сервер 2" as S2 #LightGreen
node "Сервер 3" as S3 #LightPink

LB -down-> S1 : Працює
LB -down-> S2 : Працює
LB .down.> S3 : Шле запити на "мертвий" сервер
@enduml

Проблема №5: Оновлення та відкати (Rollbacks)

Щоб випустити нову фічу без зупинки всього додатку, доводиться по черзі вимикати один сервіс, оновлювати його, перевіряти, і тільки потім переходити до інших. А якщо в новому коді критична помилка — доводиться так само вручну та по черзі вбивати і перезапускати стару версію.

Проміжний висновок для студентів: У невеликих компаніях так ще можна працювати, але у великих проєктах керувати цією інфраструктурою вручну просто неможливо.

Що не так з Docker Compose у production?

Docker Compose — чудовий інструмент. Він вирішує реальну проблему: дозволяє описати multi-container застосунок у одному файлі та керувати ним однією командою. Для локальної розробки, для невеликих проєктів, для CI/CD pipelines — він справляється відмінно.

Але Docker Compose має фундаментальне обмеження, яке випливає з його природи: він оркеструє контейнери на одному хості. Один файл docker-compose.yml, один сервер, один Docker-демон.

Розглянемо конкретні проблеми, з якими стикаються команди, коли їхній застосунок переростає single-host підхід.

Проблема 1: Відсутність автовідновлення (no self-healing)

Коли контейнер аварійно завершується у Docker Compose, поведінка залежить від параметру restart. Але якщо сам хост стає недоступним — через збій мережі, перегрів, відключення живлення — Compose нічого не може зробити. Він сам є частиною цього хоста.

# docker-compose.yml
services:
    api:
        image: myapp:latest
        restart:
            on-failure # перезапускає контейнер при падінні процесу
            # але безсилий, якщо впав сам сервер

У Kubernetes, якщо вузол (сервер) стає недоступним, система автоматично перенесе Podʼи (контейнери) на інший вузол кластера. Без вашого втручання. Без дзвінків о 23:47.

Loading diagram...
graph LR
    subgraph Node1 ["Worker Node 1"]
        P1["Pod: API"]
    end
    subgraph Node2 ["Worker Node 2"]
        P2["Pod: API"]
        P3["Pod: API"] -.-> |"Crash"| Crash((fa:fa-skull))
    end

    Controller(("K8s Controller")) --> |"1. Виявляє падіння"| P3
    Controller --> |"2. Перестворює"| P4["Pod: API"]

    subgraph Node3 ["Worker Node 3"]
        P4
    end

    style P3 fill:#ef4444,stroke:#991b1b,color:#ffffff
    style Controller fill:#8b5cf6,stroke:#5b21b6,color:#ffffff
    style P4 fill:#10b981,stroke:#047857,color:#ffffff
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "Кластер" {
    node "Вузол 1" as N1 {
        [Pod API]
    }
    node "Вузол 2" as N2 #LightPink {
        [Pod API (Впав)]
    }
    node "Вузол 3" as N3 {
        [Pod API (Новий)] #LightGreen
    }
}

[K8s Controller] -down-> N2 : 1. Помічає збій
[K8s Controller] -down-> N3 : 2. Запускає заміну
@enduml

Проблема 2: Ручне масштабування

У статті про Docker Compose ми розглядали команду --scale:

Масштабування у Docker Compose
$ docker compose up --scale api=3 -d
Container myapp-api-1 Running
Container myapp-api-2 Started
Container myapp-api-3 Started

Це ручна операція. Ви самі вирішуєте, коли і скільки екземплярів запустити. Але як визначити потрібну кількість? Якщо навантаження зросте о 3 годині ночі під час акції — хто збільшить кількість реплік?

Kubernetes вирішує це через HorizontalPodAutoscaler (HPA) — компонент, який відстежує завантаженість CPU та памʼяті і автоматично змінює кількість реплік. Застосунок масштабується сам.

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

actor "Наплив користувачів" as T
package "Autoscaling Group" {
    [Pod 1]
    [Pod 2]
    [Pod 3] <<Auto>> #LightGreen
    [Pod 4] <<Auto>> #LightGreen
}

T -down-> [HPA] : Зростання CPU / RAM
[HPA] -down-> [Autoscaling Group] : Додає репліки
@enduml

Проблема 3: Обмеження одним хостом

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

package "Docker Compose" {
  node "VPS (Єдина точка відмови)" as VPS #f8f9fa {
    [API]
    database "PostgreSQL"
    database "Redis"
  }
}

note bottom of VPS
  Якщо цей сервер падає —
  падає весь застосунок
end note
@enduml

Docker Compose передбачає, що всі сервіси розміщені на одному хості. Це означає: якщо цей хост падає — падає весь застосунок. Немає резервування, немає failover, немає географічного розподілу.

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

Проблема 4: Відсутність rolling updates без downtime

Щоб оновити версію застосунку у Docker Compose, потрібно:

Оновлення у Docker Compose
$ docker compose pull
$ docker compose up -d
Recreating myapp-api-1 ... done
# В цей момент виникає вікно недоступності (downtime)

Між зупинкою старого і запуском нового контейнера існує вікно недоступності. Для production-систем навіть 5 секунд downtime — це втрачені запити та негативний досвід користувачів.

Kubernetes реалізує Rolling Update за замовчуванням: нові Podʼи запускаються і перевіряються перед тим, як старі зупиняються. Завжди є хоча б один працюючий екземпляр.

Loading diagram...
graph TD
    subgraph V1 ["Поточна версія (v1)"]
        P1["Pod v1"]
        P2["Pod v1"]
        P3["Pod v1"]
    end

    subgraph V2 ["Нова версія (v2)"]
        P4["Pod v2"]
    end

    P4 -.-> |"1. Створюється і перевіряється"| Ready{"Ready?"}
    Ready -.-> |"2. Якщо OK"| Kill["3. Видаляється старий Pod v1"]
    Kill -.-> P3

    style V2 stroke:#10b981,stroke-width:2px
    style V1 stroke:#ef4444,stroke-width:2px
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "Rolling Update" {
    package "v1 (Старі)" #LightPink {
        [Pod v1.1]
        [Pod v1.2]
    }
    package "v2 (Нові)" #LightGreen {
        [Pod v2.1]
        [Pod v2.2]
    }
}

note right of "v2 (Нові)"
  1. Запуск нового v2
  2. Перевірка Health Check
  3. Видалення старого v1
  4. Повторення до завершення
end note
@enduml

Що таке Kubernetes?

Kubernetes (скорочено K8s, де 8 — кількість літер між «K» і «s») — це відкрита платформа для автоматизованого розгортання, масштабування та управління контейнеризованими застосунками.

Kubernetes був розроблений у Google на основі внутрішньої системи Borg, яка керувала мільярдами контейнерів на тисячах серверів щотижня. У 2014 році Google відкрив код Kubernetes, а у 2016 році передав проєкт під управління Cloud Native Computing Foundation (CNCF).

Назва «Kubernetes» походить від грецького слова «κυβερνήτης» — кермовий, той, хто керує судном. Скорочення K8s стало загальноприйнятим у спільноті: замість восьми літер між K і s пишуть цифру 8.

Офіційне визначення: Kubernetes — це система оркестрації контейнерів (container orchestration system). Слово «оркестрація» тут точне: як диригент оркестром, Kubernetes координує роботу десятків і сотень контейнерів на кількох серверах одночасно.

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

package "Control Plane (Мозок)" #e9ecef {
    [API / Scheduler / Controllers]
}

package "Worker Nodes (Тіло)" {
    node "Node A" {
        [Pod 1]
    }
    node "Node B" {
        [Pod 2]
    }
}

[Control Plane (Мозок)] -down-> "Worker Nodes (Тіло)" : Оркестрація / Управління
@enduml

Що Kubernetes робить конкретно?

Автоматичне розміщення

Kubernetes сам вирішує, на якому вузлі кластера запустити контейнер, враховуючи доступні ресурси (CPU, пам'ять) та обмеження, які ви задали.

Self-healing

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

Горизонтальне масштабування

Kubernetes може автоматично збільшувати або зменшувати кількість екземплярів застосунку залежно від навантаження.

Rolling updates та rollback

Оновлення застосунку відбувається поступово, без downtime. Якщо нова версія має проблеми — один командою повертається попередня.

Service discovery та балансування

Kubernetes автоматично направляє трафік між репліками, рівномірно розподіляючи навантаження. Сервіси знаходять один одного за іменем через вбудований DNS.

Управління конфігурацією та секретами

Паролі, токени та конфігурації зберігаються окремо від образів контейнерів, що спрощує управління різними середовищами.

Які задачі вирішує Kubernetes (Порятунок)

Щоб автоматизувати весь описаний вище хаос, на допомогу приходить Kubernetes:

Деплоймент (Deployment)

Він автоматично піднімає нові екземпляри додатку без ручного втручання.

Самовідновлення (Self-healing)

Інструмент безперервно стежить за станом сервісів. Якщо додаток впав, Kubernetes миттєво піднімає його заново, гарантуючи, що працює рівно стільки екземплярів, скільки було задано.

Управління інфраструктурою

Дає зручний єдиний центр для управління всіма процесами.

Автоматичне масштабування

Якщо навантаження раптово виросло, Kubernetes сам додасть кілька нових екземплярів додатку, щоб впоратися з потоком користувачів.

Балансування трафіку

Він самостійно та рівномірно розподіляє вхідні запити між усіма доступними екземплярами додатків.

Чого Kubernetes НЕ робить (Обмеження)

Важливо пояснити межі відповідальності цього інструменту, щоб не виникало хибних очікувань:

Не зберігає дані

Управління базами даних та забезпечення збереження стану — це окрема задача, яку потрібно вирішувати іншими інструментами (хоча бази і можна запускати в ньому).

Не зберігає образи додатків

Він не замінює Docker Registry. Готові образи (наприклад, Docker-контейнери) потрібно десь зберігати і просто передавати Kubernetes на них посилання.

Не збирає (не білдить) образи

Він працює вже з готовими запакованими додатками.

Не веде журнали та метрики

Логування роботи додатку та налаштування метрик залишаються зоною відповідальності розробників та DevOps-інженерів.

Kubernetes vs Docker Compose: порівняння

Важливо розуміти: Kubernetes не замінює Docker Compose повністю. Вони вирішують різні завдання на різних рівнях.

ХарактеристикаDocker ComposeKubernetes
ПризначенняЛокальна розробка, малі деплоїProduction, кластери
Кількість хостівОдинБагато (кластер)
Self-healingЛише перезапуск контейнераПеренос між вузлами
МасштабуванняРучне (--scale)Автоматичне (HPA)
Rolling updateЗ downtimeБез downtime
Крива навчання1–2 дні2–4 тижні
КонфігураціяОдин YAML-файл (50–200 рядків)Кілька YAML-маніфестів
МоніторингВідсутнійВбудована інтеграція
Типовий use casedev-середовище, MVPProduction з вимогами до SLA
Практична порада: використовуйте Docker Compose для локальної розробки навіть тоді, коли production розгортається у Kubernetes. Compose простіший, швидший для старту і дозволяє розробникам не думати про кластер щодня. Перехід між ними — природній крок при зростанні проєкту.

Де запускається Kubernetes?

Kubernetes можна розгорнути у різних середовищах. Розрізняють два основних підходи.

Managed Kubernetes (хмарні провайдери)

Хмарний провайдер бере на себе управління control plane — серцем кластера. Ви платите за worker-вузли і не турбуєтесь про оновлення та відмовостійкість самого Kubernetes.

Google Kubernetes Engine (GKE)

Оригінальна реалізація від творців Kubernetes. Вважається еталонною за рівнем автоматизації та інтеграції з Google Cloud.

Amazon EKS

Kubernetes від Amazon Web Services. Тісна інтеграція з AWS-сервісами: IAM, ALB, EBS, ECR.

Azure AKS

Kubernetes від Microsoft. Природна інтеграція з Azure Active Directory та іншими Azure-сервісами.

Self-hosted Kubernetes

Ви розгортаєте та керуєте кластером самостійно. Більше контролю, але більше відповідальності.

  • kubeadm — офіційний інструмент для розгортання production-кластерів
  • k3s — легкий дистрибутив Kubernetes від Rancher, ідеальний для edge та IoT
  • microk8s — від Canonical (Ubuntu), простий у встановленні

Для навчання та локальної роботи існують спрощені інструменти, які розглядаються у наступній статті: minikube, kind, Docker Desktop Kubernetes.


Резюме

Docker Compose вирішує проблему управління контейнерами на одному хості. Kubernetes вирішує проблему управління контейнерами на кластері серверів. Це не конкуруючі технології — це різні інструменти для різних завдань і різних масштабів.

Kubernetes виникає як природна відповідь на обмеження single-host оркестрації: відсутність self-healing, ручне масштабування, downtime при оновленнях та єдина точка відмови.

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


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

Рівень 1 (Базовий)

Завдання 1. Маєте Docker Compose стек: Nginx + Node.js API + MongoDB. Визначте три конкретних сценарії, за яких цей стек зазнає повного відмови. Для кожного сценарію вкажіть, яка саме можливість Kubernetes могла б запобігти або зменшити час відмови.