Deployment — декларативне управління Pod
Deployment — декларативне управління Pod
Проблема: чому Pod не підходить для production
У попередніх статтях ми детально вивчили Pod — атомарну одиницю Kubernetes. Ми навчилися створювати Pod, налаштовувати контейнери, використовувати volumes, init-контейнери та sidecar-патерни. Але наприкінці статті про Pod ми зробили важливий висновок: Pod не слід створювати напряму у production.
Чому? Давайте розглянемо типовий сценарій розгортання веб-застосунку.
Сценарій: розгортання ASP.NET Core Web API
Уявіть, що ви розгортаєте ASP.NET Core Web API у Kubernetes. Ваші вимоги:
- Три репліки для балансування навантаження та відмовостійкості
- Автоматичне відновлення — якщо одна репліка падає, система має створити нову автоматично
- Оновлення без downtime — нова версія застосунку має розгортатись поступово, без зупинки сервісу
- Можливість rollback — якщо нова версія має критичний баг, потрібно швидко повернутись до попередньої версії
- Масштабування — можливість швидко збільшити або зменшити кількість реплік залежно від навантаження
Якби ви використовували Pod напряму, вам довелося б:
Проблеми цього підходу:
Немає self-healing
Немає масштабування
Downtime при оновленні
Немає історії версій
Ручна робота
Це неприйнятно для production. Саме тому існує Deployment.
Що потрібно замість ручного управління
Нам потрібен механізм, який:
- Автоматично підтримує задану кількість реплік — якщо Pod падає, система сама створює новий
- Дозволяє декларативно описати бажаний стан — "хочу 3 репліки версії 1.0.0", а система сама досягає цього стану
- Виконує оновлення без downtime — поступово замінює старі Pod новими, завжди залишаючи працюючі репліки
- Зберігає історію версій — можна швидко повернутись до попередньої версії однією командою
- Легко масштабується — змінити кількість реплік можна однією командою або автоматично
Саме це і робить Deployment.
Що таке Deployment: формальне визначення
Deployment — це ресурс Kubernetes, який забезпечує декларативне управління набором ідентичних Pod. Ви описуєте бажаний стан (скільки реплік, яка версія образу, які ресурси), а Kubernetes автоматично підтримує цей стан.
replicas: 3, але працює лише 2 Pod (один впав). Deployment Controller виявляє розбіжність та створює третій Pod. Ви нічого не робите вручну — система сама усуває проблему.Основні можливості Deployment
Self-healing
Декларативне масштабування
kubectl scale) або редагуванням YAML. Kubernetes сам створить або видалить Pod для досягнення бажаної кількості.Rolling Updates
Rollback
kubectl rollout undo.Версіонування
Архітектура: Deployment → ReplicaSet → Pod
Deployment не створює Pod напряму. Він використовує проміжний ресурс — ReplicaSet:
Чому потрібен ReplicaSet?
ReplicaSet відповідає за підтримку заданої кількості реплік. Його єдине завдання — гарантувати, що завжди працює рівно N Pod з певним шаблоном.
Deployment використовує ReplicaSet для реалізації rolling updates: при оновленні створюється новий ReplicaSet з новим шаблоном Pod, а старий поступово зменшується до нуля. Це дозволяє виконувати оновлення без downtime.
Анатомія Deployment: структура YAML
Тепер розглянемо, як описати Deployment у YAML. Почнемо з мінімального прикладу та поступово додаватимемо поля.
Мінімальний Deployment
Найпростіший Deployment містить лише обов'язкові поля:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
Розберемо кожне поле детально.
apps/v1 (не просто v1, як для Pod). Це означає, що Deployment належить до групи API apps. Ця група містить ресурси для управління застосунками: Deployment, StatefulSet, DaemonSet, ReplicaSet.Deployment. Це вказує Kubernetes, що ви створюєте саме Deployment, а не Pod або інший ресурс.kubectl та як префікс для імен ReplicaSet та Pod.selector.matchLabels мають точно збігатись з мітками у template.metadata.labels. Якщо мітки не збігаються — Deployment не зможе знайти свої Pod і буде постійно створювати нові.app: nginx означає "вибрати всі Pod з міткою app=nginx".apiVersion та kind. Всі Pod, створені цим Deployment, будуть ідентичними копіями цього шаблону.spec.selector.matchLabels. Це критично важливо для роботи Deployment.spec, яке ви використовували при створенні Pod напряму. Всі поля з статті про Pod доступні тут.Важливість selector та labels
Зв'язок між selector та template.metadata.labels — це найважливіша частина Deployment. Давайте розберемо детально:
spec:
selector:
matchLabels:
app: nginx # ← Deployment шукає Pod з цією міткою
template:
metadata:
labels:
app: nginx # ← Pod створюються з цією міткою
Що відбувається:
- Deployment створює Pod з міткою
app: nginx(зtemplate.metadata.labels) - Deployment Controller постійно шукає Pod з міткою
app: nginx(черезselector.matchLabels) - Якщо знайдено менше Pod, ніж
replicas— створює нові - Якщо знайдено більше Pod, ніж
replicas— видаляє зайві
Типова помилка новачків:
spec:
selector:
matchLabels:
app: nginx # ← Шукає Pod з app=nginx
template:
metadata:
labels:
app: web # ← Створює Pod з app=web (ПОМИЛКА!)
У цьому випадку Deployment створить Pod з міткою app: web, але шукатиме Pod з міткою app: nginx. Він не знайде жодного Pod і буде нескінченно створювати нові, бо вважатиме, що реплік недостатньо.
selector.matchLabels та template.metadata.labels мають точно збігатись. Це не рекомендація — це вимога. Kubernetes навіть не дозволить створити Deployment з неспівпадаючими мітками (отримаєте помилку валідації).Повна специфікація Deployment
Тепер розглянемо всі доступні поля Deployment. Почнемо з базових, потім перейдемо до розширених.
Базові поля (обов'язкові та найчастіше використовувані)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
namespace: default
labels:
app: myapp
version: '1.0'
annotations:
description: 'My application deployment'
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
version: '1.0'
spec:
containers:
- name: app
image: mcr.microsoft.com/dotnet/samples:aspnetapp
ports:
- containerPort: 8080
env:
- name: ENV
value: 'production'
resources:
requests:
memory: '128Mi'
cpu: '100m'
limits:
memory: '256Mi'
cpu: '500m'
default. Namespace — це спосіб ізоляції ресурсів у Kubernetes. Різні команди або проєкти можуть мати свої namespace.team: backend та потім знайти всі Deployment команди backend через kubectl get deployments -l team=backend.Розширені поля: стратегія оновлення
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
spec.template. Визначає, як Kubernetes замінює старі Pod новими. Є два типи: RollingUpdate (за замовчуванням) та Recreate.RollingUpdate— поступове оновлення: нові Pod створюються, старі видаляються. Завжди є працюючі репліки.Recreate— спочатку видаляються всі старі Pod, потім створюються нові. Є момент downtime. ::
1, 2) або відсотком від replicas (25%, 50%).Приклад: Якщо replicas: 10 та maxUnavailable: 2, то під час оновлення мінімум 8 Pod мають бути доступними (10 - 2 = 8).Приклад з відсотком: Якщо replicas: 10 та maxUnavailable: 25%, то під час оновлення мінімум 7-8 Pod мають бути доступними (25% від 10 = 2.5, округлюється вниз до 2, тому 10 - 2 = 8).replicas під час оновлення. Може бути абсолютним числом або відсотком.Приклад: Якщо replicas: 10 та maxSurge: 2, то під час оновлення максимум 12 Pod можуть існувати одночасно (10 + 2 = 12).Навіщо це потрібно: Додаткові Pod дозволяють швидше виконати оновлення. Нові Pod створюються паралельно зі старими, і лише після того, як нові стануть готовими, старі видаляються.::
- Швидке оновлення, більше ресурсів:
maxUnavailable: 0,maxSurge: 50%— створюються багато нових Pod одразу, старі видаляються лише після готовності нових. Потребує більше ресурсів (CPU, пам'ять). - Повільне оновлення, менше ресурсів:
maxUnavailable: 25%,maxSurge: 0— старі Pod видаляються, потім створюються нові. Економить ресурси, але оновлення триватиме довше. - Збалансований підхід (за замовчуванням):
maxUnavailable: 25%,maxSurge: 25%— компроміс між швидкістю та ресурсами. ::
Розширені поля: контроль життєвого циклу
spec:
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
minReadySeconds: 0
paused: false
spec.template створює нову ревізію (новий ReplicaSet). Старі ReplicaSet зберігаються з replicas: 0 для історії. Якщо встановити 0 — rollback буде неможливий.Progressing: False з причиною ProgressDeadlineExceeded.Навіщо це потрібно: Запобігає ситуації, коли оновлення "зависає" нескінченно (наприклад, новий образ не може завантажитись або Pod не проходить health checks).minReadySeconds: 30, то після того, як Pod стане Ready, Kubernetes чекатиме ще 30 секунд. Якщо за цей час Pod не впаде — він вважається доступним і оновлення продовжується. Якщо впаде — оновлення призупиняється.Навіщо це потрібно: Запобігає ситуації, коли Pod стартує успішно, але падає через кілька секунд (наприклад, через помилку підключення до бази даних).true — Deployment призупинено. Зміни у spec.template не застосовуються автоматично. Використовується для ручного контролю оновлень (canary deployments). Щоб продовжити оновлення, потрібно встановити paused: false.::
Повний приклад з усіма полями
Тепер об'єднаємо все, що ми вивчили, в один Deployment з усіма важливими полями:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
namespace: default
labels:
app: myapp
tier: backend
version: '1.0'
annotations:
description: 'Production deployment for MyApp API'
contact: 'backend-team@example.com'
spec:
# Кількість реплік
replicas: 3
# Селектор для вибору Pod
selector:
matchLabels:
app: myapp
# Стратегія оновлення
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
# Контроль життєвого циклу
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
minReadySeconds: 5
# Шаблон Pod
template:
metadata:
labels:
app: myapp
version: '1.0'
spec:
containers:
- name: app
image: mcr.microsoft.com/dotnet/samples:aspnetapp
ports:
- containerPort: 8080
name: http
env:
- name: ASPNETCORE_ENVIRONMENT
value: 'Production'
resources:
requests:
memory: '128Mi'
cpu: '100m'
limits:
memory: '256Mi'
cpu: '500m'
Цей Deployment:
- Створює 3 репліки Pod з образом
mcr.microsoft.com/dotnet/samples:aspnetapp - Використовує rolling update з обережними налаштуваннями (максимум 1 недоступний Pod)
- Зберігає історію 10 останніх версій для rollback
- Чекає 5 секунд після готовності Pod перед продовженням оновлення
- Має таймаут 600 секунд для оновлення
Створення першого Deployment
Тепер створимо реальний Deployment та подивимося, як він працює.
Крок 1: Створення YAML маніфесту
Створіть файл nginx-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
Крок 2: Застосування маніфесту
Застосуйте маніфест до кластера:
Крок 3: Перевірка створеного Deployment
Переглянемо список Deployment:
Що означають колонки:
- NAME: Ім'я Deployment (з
metadata.name) - READY:
3/3— 3 з 3 реплік готові до роботи (станReady) - UP-TO-DATE:
3— 3 репліки відповідають поточній версії шаблону (актуальний образ, змінні оточення тощо) - AVAILABLE:
3— 3 репліки доступні для обслуговування трафіку (пройшли readiness probe, якщо він налаштований) - AGE: Час з моменту створення Deployment
Крок 4: Перевірка створених Pod
Deployment автоматично створив Pod. Подивимося на них:
Структура імені Pod:
Ім'я Pod складається з трьох частин: nginx-deployment-7d6b8c9f4d-8xk2p
nginx-deployment— ім'я Deployment7d6b8c9f4d— хеш ReplicaSet (унікальний ідентифікатор версії шаблону Pod)8xk2p— унікальний суфікс Pod (генерується випадково)
Це дозволяє легко ідентифікувати, до якого Deployment належить Pod, та яку версію шаблону він використовує.
Крок 5: Детальна інформація про Deployment
Переглянемо детальну інформацію:
Тут ви бачите:
- Конфігурацію Deployment (replicas, selector, strategy)
- Шаблон Pod (образ, порти)
- Умови (Conditions) — стан Deployment
- Події (Events) — що відбувалося з Deployment (створення ReplicaSet, масштабування)
ReplicaSet: проміжний шар між Deployment та Pod
Ми згадували, що Deployment не створює Pod напряму. Він використовує ReplicaSet. Давайте розберемося детально, що це і навіщо.
Що таке ReplicaSet
ReplicaSet — це ресурс Kubernetes, який забезпечує підтримку заданої кількості ідентичних Pod. Його єдине завдання — гарантувати, що завжди працює рівно N Pod з певним шаблоном.
Коли ви створюєте Deployment, він автоматично створює ReplicaSet:
Навіщо потрібен ReplicaSet
ReplicaSet виконує дві ключові функції:
- Підтримка кількості реплік — якщо Pod падає, ReplicaSet створює новий
- Версіонування для rolling updates — при оновленні Deployment створює новий ReplicaSet, а старий залишається для rollback
Переглянемо ReplicaSet, створений нашим Deployment:
Що означають колонки:
- NAME: Ім'я ReplicaSet (ім'я Deployment + хеш шаблону Pod)
- DESIRED: Бажана кількість Pod (з
spec.replicasDeployment) - CURRENT: Поточна кількість Pod (скільки насправді існує)
- READY: Кількість готових Pod (стан
Ready) - AGE: Час з моменту створення ReplicaSet
- Rolling updates (поступового оновлення)
- Rollback (повернення до попередньої версії)
- Історії версій
Self-healing у дії
Тепер продемонструємо головну перевагу Deployment — автоматичне відновлення (self-healing).
Експеримент: видалення Pod
Видалимо один Pod вручну та подивимося, що станеться:
Одразу перевіримо список Pod:
Бачимо новий Pod з іменем p4k8t у стані ContainerCreating. Що сталося?
- Ми видалили Pod
8xk2p - ReplicaSet Controller виявив, що реплік менше, ніж задано (
2 < 3) - ReplicaSet автоматично створив новий Pod
p4k8t - Scheduler призначив Pod вузлу
- Kubelet завантажує образ та запускає контейнер
Через кілька секунд:
Знову три репліки! Це і є self-healing — система сама усуває розбіжності між бажаним станом (replicas: 3) та поточним станом (2 працюючі Pod).
- Читають бажаний стан з API Server (
spec.replicas: 3) - Читають поточний стан з API Server (скільки Pod насправді працює)
- Порівнюють бажаний та поточний стан
- Якщо є розбіжності — виконують дії для їх усунення (створюють або видаляють Pod)
- Повторюють цикл кожні кілька секунд
Візуалізація reconciliation loop
Давайте детально розглянемо, як працює цикл узгодження при видаленні Pod:
Ключові моменти:
- Постійний моніторинг — ReplicaSet Controller не чекає на події. Він постійно (кожні 5-10 секунд) перевіряє стан Pod.
- Декларативність — контролер не знає, чому Pod зникли (видалення, збій вузла, OOMKilled). Він просто бачить розбіжність між бажаним та поточним станом і усуває її.
- Ідемпотентність — якщо стан уже узгоджено, контролер нічого не робить. Це безпечно викликати reconciliation багато разів.
- Розподілена робота — ReplicaSet Controller створює Pod, Scheduler призначає вузол, Kubelet запускає контейнер. Кожен компонент відповідає за свою частину.
Масштабування Deployment
Одна з найважливіших можливостей Deployment — масштабування (scaling). Це зміна кількості реплік Pod для адаптації до навантаження.
Навіщо потрібне масштабування
Уявіть, що ваш веб-застосунок отримує різне навантаження протягом дня:
- Ніч (02:00-06:00): 100 запитів/хвилину — достатньо 2 реплік
- День (09:00-18:00): 1000 запитів/хвилину — потрібно 5 реплік
- Пікове навантаження (12:00-13:00): 5000 запитів/хвилину — потрібно 10 реплік
Без масштабування вам довелося б постійно тримати 10 реплік, витрачаючи ресурси навіть коли вони не потрібні. З масштабуванням ви можете динамічно змінювати кількість реплік.
Три способи масштабування
Kubernetes надає три способи змінити кількість реплік Deployment:
1. Команда kubectl scale
2. Редагування YAML та kubectl apply
3. Команда kubectl edit
Розглянемо кожен спосіб детально.
Спосіб 1: kubectl scale
Найпростіший спосіб — команда kubectl scale:
Перевіримо результат:
Kubernetes створив 2 нові Pod (x2n9k та q7m4p) для досягнення бажаної кількості 5 реплік.
Через кілька секунд:
Тепер усі 5 реплік працюють.
Зменшення кількості реплік:
Так само легко зменшити кількість реплік:
Kubernetes видаляє 3 зайві Pod. Статус Terminating означає, що Pod отримав сигнал завершення (SIGTERM) і має 30 секунд (за замовчуванням) для graceful shutdown.
- Unscheduled Pod (ще не призначені вузлу) — видаляються першими
- Pending Pod (чекають на ресурси) — наступні
- Running Pod на вузлах з більшою кількістю реплік — для балансування навантаження
- Новіші Pod (за часом створення) — старіші Pod зберігаються, бо вони довше працюють без проблем
Переваги kubectl scale:
- Швидкість — одна команда, миттєвий результат
- Простота — не потрібно редагувати файли
- Ідеально для експериментів — швидко збільшити/зменшити репліки для тестування
Недоліки kubectl scale:
- Не зберігається у Git — зміна не відображена у YAML файлах
- Можна забути — якщо пізніше застосуєте старий YAML з
replicas: 3, масштабування скасується - Не підходить для production — краще використовувати декларативний підхід (YAML)
Спосіб 2: Редагування YAML та kubectl apply
Декларативний підхід — змінюєте YAML файл та застосовуєте його:
Крок 1: Відкрийте nginx-deployment.yaml та змініть replicas:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 7 # ← Було 3, стало 7
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
Крок 2: Застосуйте зміни:
Зверніть увагу на слово configured (а не created). Це означає, що Deployment уже існував, і Kubernetes застосував зміни.
Крок 3: Перевірте результат:
Тепер працює 7 реплік.
Переваги kubectl apply:
- Декларативність — YAML файл — це єдине джерело правди (single source of truth)
- Версійний контроль — зміни зберігаються у Git, можна відстежити історію
- Відтворюваність — можна застосувати той самий YAML на іншому кластері
- Production-ready — рекомендований підхід для production
Недоліки kubectl apply:
- Повільніше — потрібно редагувати файл, зберегти, застосувати
- Потрібен локальний файл — якщо ви на іншій машині без файлів, доведеться спочатку отримати YAML
kubectl apply для production. Зберігайте всі YAML файли у Git. Це дозволяє:- Відстежувати, хто і коли змінив конфігурацію
- Повернутись до попередньої версії через
git revert - Автоматизувати розгортання через CI/CD (GitHub Actions, GitLab CI)
- Мати документацію інфраструктури у вигляді коду (Infrastructure as Code) ::
Спосіб 3: kubectl edit
Інтерактивне редагування — відкриває редактор з поточною конфігурацією:replicas та змініть значення:spec:
replicas: 4 # ← Змініть на потрібне значення
:wq, у nano: Ctrl+O, Ctrl+X).- Не потрібен локальний файл — редагуєте конфігурацію безпосередньо у кластері
- Швидше за kubectl apply — не потрібно шукати файл, редагувати, зберігати
- Бачите всі поля — включно з тими, що додав Kubernetes автоматично
- Не зберігається у Git — зміни не відображені у локальних файлах
- Складніше для новачків — потрібно вміти користуватись vim/nano
- Можна зробити помилку — якщо випадково змінити щось інше, можна зламати Deployment
kubectl edit зручний для швидких експериментів, але не рекомендується для production. Причини:- Зміни не зберігаються у Git — немає історії
- Легко зробити помилку — випадково змінити щось інше
- Немає code review — зміни застосовуються одразу без перевірки колегами
kubectl edit лише для debugging або швидких тестів. Для production завжди використовуйте kubectl apply з YAML файлами у Git.Порівняння способів масштабування
kubectl scale
Коли використовувати:
- Швидкі експерименти
- Тимчасове масштабування під час інциденту
- Локальна розробка (Minikube)
Приклад: Раптовий сплеск трафіку — швидко збільшити репліки, поки не з'ясуєте причину.
kubectl apply
Коли використовувати:
- Production розгортання
- CI/CD pipelines
- Будь-які зміни, які мають зберігатись
Приклад: Планове збільшення реплік перед маркетинговою кампанією — змінюєте YAML, робите commit, застосовуєте через CI/CD.
kubectl edit
Коли використовувати:
- Debugging
- Немає доступу до локальних файлів
- Швидкі тести
Приклад: Ви на сервері без Git репозиторію, потрібно швидко змінити конфігурацію для перевірки гіпотези.
::
Візуалізація процесу масштабування
Давайте подивимося, що відбувається всередині Kubernetes при масштабуванні з 3 до 5 реплік:
Ключові етапи масштабування:
- Оновлення Deployment — користувач змінює
replicasчерез kubectl - Deployment Controller — виявляє зміну та оновлює ReplicaSet
- ReplicaSet Controller — виявляє розбіжність (3 < 5) та створює 2 нові Pod
- Scheduler — призначає нові Pod вузлам з найменшим навантаженням
- Kubelet — завантажує образи та запускає контейнери
- Готовність — Pod переходять у стан
Runningта стають доступними
Весь процес повністю автоматичний — ви лише змінюєте одне число (replicas), а Kubernetes виконує всю роботу.
Практичний приклад: ASP.NET Core TodoApi з Deployment
Тепер створимо реальний застосунок ASP.NET Core та розгорнемо його у Kubernetes через Deployment. Це буде простий TodoApi з трьома репліками.
Архітектура застосунку
Наш TodoApi матиме наступну структуру:
Крок 1: Створення ASP.NET Core Minimal API
Створимо простий TodoApi з використанням ASP.NET Core Minimal API (без Entity Framework для простоти):
Структура проєкту:
TodoApi/
├── Program.cs
├── TodoApi.csproj
├── Dockerfile
└── k8s/
└── deployment.yaml
Program.cs:
using System.Collections.Concurrent;
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Додаємо підтримку OpenAPI та Scalar
builder.Services.AddOpenApi();
// In-memory сховище (для демонстрації)
var todos = new ConcurrentDictionary<int, Todo>();
var nextId = 1;
var app = builder.Build();
// Scalar UI (доступний лише у Development)
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
// Health check endpoint (для Kubernetes)
app.MapGet("/health", () => Results.Ok(new { status = "healthy", timestamp = DateTime.UtcNow }))
.WithName("HealthCheck")
.WithOpenApi();
// GET /todos - отримати всі завдання
app.MapGet("/todos", () =>
{
var hostname = Environment.GetEnvironmentVariable("HOSTNAME") ?? "unknown";
return Results.Ok(new
{
todos = todos.Values.ToList(),
servedBy = hostname,
count = todos.Count
});
})
.WithName("GetAllTodos")
.WithOpenApi();
// GET /todos/{id} - отримати завдання за ID
app.MapGet("/todos/{id:int}", (int id) =>
{
if (todos.TryGetValue(id, out var todo))
{
var hostname = Environment.GetEnvironmentVariable("HOSTNAME") ?? "unknown";
return Results.Ok(new { todo, servedBy = hostname });
}
return Results.NotFound(new { error = "Todo not found", id });
})
.WithName("GetTodoById")
.WithOpenApi();
// POST /todos - створити нове завдання
app.MapPost("/todos", (CreateTodoRequest request) =>
{
var id = Interlocked.Increment(ref nextId);
var todo = new Todo
{
Id = id,
Title = request.Title,
IsCompleted = false,
CreatedAt = DateTime.UtcNow
};
todos[id] = todo;
var hostname = Environment.GetEnvironmentVariable("HOSTNAME") ?? "unknown";
return Results.Created($"/todos/{id}", new { todo, servedBy = hostname });
})
.WithName("CreateTodo")
.WithOpenApi();
// PUT /todos/{id} - оновити завдання
app.MapPut("/todos/{id:int}", (int id, UpdateTodoRequest request) =>
{
if (!todos.TryGetValue(id, out var todo))
{
return Results.NotFound(new { error = "Todo not found", id });
}
todo.Title = request.Title ?? todo.Title;
todo.IsCompleted = request.IsCompleted ?? todo.IsCompleted;
var hostname = Environment.GetEnvironmentVariable("HOSTNAME") ?? "unknown";
return Results.Ok(new { todo, servedBy = hostname });
})
.WithName("UpdateTodo")
.WithOpenApi();
// DELETE /todos/{id} - видалити завдання
app.MapDelete("/todos/{id:int}", (int id) =>
{
if (todos.TryRemove(id, out var todo))
{
var hostname = Environment.GetEnvironmentVariable("HOSTNAME") ?? "unknown";
return Results.Ok(new { message = "Todo deleted", todo, servedBy = hostname });
}
return Results.NotFound(new { error = "Todo not found", id });
})
.WithName("DeleteTodo")
.WithOpenApi();
app.Run();
// Моделі
record Todo
{
public int Id { get; set; }
public required string Title { get; set; }
public bool IsCompleted { get; set; }
public DateTime CreatedAt { get; set; }
}
record CreateTodoRequest(string Title);
record UpdateTodoRequest(string? Title, bool? IsCompleted);
TodoApi.csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.*" />
<PackageReference Include="Scalar.AspNetCore" Version="2.*" />
</ItemGroup>
</Project>
servedBy у відповідях?Кожна відповідь API містить поле servedBy з hostname Pod. Це дозволяє побачити, який саме Pod обробив запит. У Kubernetes кожен Pod має унікальне ім'я (наприклад, todoapi-7d6b8c9f4d-x2n9k), яке зберігається у змінній оточення HOSTNAME.Це корисно для демонстрації балансування навантаження — ви побачите, що різні запити обробляються різними Pod.Крок 2: Створення Dockerfile
Створимо multi-stage Dockerfile для оптимізації розміру образу:
Dockerfile:
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Копіюємо .csproj та відновлюємо залежності (кешування шарів)
COPY TodoApi.csproj .
RUN dotnet restore
# Копіюємо решту файлів та збираємо
COPY . .
RUN dotnet publish -c Release -o /app/publish
# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
# Копіюємо зібраний застосунок
COPY --from=build /app/publish .
# Відкриваємо порт 8080 (non-privileged port)
EXPOSE 8080
# Налаштовуємо ASP.NET Core для прослуховування на порту 8080
ENV ASPNETCORE_URLS=http://+:8080
# Запускаємо застосунок
ENTRYPOINT ["dotnet", "TodoApi.dll"]
- Build stage — використовує
dotnet/sdk:10.0(розмір ~1.2 GB) для компіляції застосунку - Runtime stage — використовує
dotnet/aspnet:10.0(розмір ~200 MB) лише з runtime
Крок 3: Збірка та завантаження образу у Minikube
Тепер зберемо Docker образ та завантажимо його у Minikube:
eval $(minikube docker-env)?Ця команда налаштовує ваш локальний Docker CLI для роботи з Docker daemon всередині Minikube. Після цього всі образи, які ви збираєте, зберігаються безпосередньо у Minikube, і їх не потрібно завантажувати окремо.Без цієї команди вам довелося б:- Зібрати образ локально
- Зберегти його у tar-файл
- Завантажити у Minikube через
minikube image load
eval $(minikube docker-env) образ одразу доступний у Minikube.Перевіримо, що образ створено:
Крок 4: Створення Deployment YAML
Створимо маніфест Deployment для TodoApi:
k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: todoapi
labels:
app: todoapi
version: '1.0.0'
spec:
# Три репліки для балансування навантаження
replicas: 3
# Селектор для вибору Pod
selector:
matchLabels:
app: todoapi
# Шаблон Pod
template:
metadata:
labels:
app: todoapi
version: '1.0.0'
spec:
containers:
- name: todoapi
image: todoapi:1.0.0
imagePullPolicy: Never # Використовувати локальний образ (для Minikube)
ports:
- name: http
containerPort: 8080
protocol: TCP
# Змінні оточення
env:
- name: ASPNETCORE_ENVIRONMENT
value: 'Production'
- name: ASPNETCORE_URLS
value: 'http://+:8080'
# Ресурси (requests та limits)
resources:
requests:
memory: '128Mi'
cpu: '100m'
limits:
memory: '256Mi'
cpu: '500m'
# Liveness probe - перевірка, чи застосунок живий
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# Readiness probe - перевірка, чи застосунок готовий приймати трафік
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
Розберемо нові поля детально.
Always— завжди завантажувати образ з registry (навіть якщо він є локально)IfNotPresent— завантажувати лише якщо образу немає локальноNever— ніколи не завантажувати, використовувати лише локальний образ (для Minikube)
Never, бо образ зібрано локально. Для production використовуйте Always або IfNotPresent.requests (мінімум, який гарантується) та limits (максимум, який дозволено).memory: "128Mi" означає, що Pod потребує мінімум 128 MiB пам'яті. Якщо жоден вузол не має 128 MiB вільної пам'яті, Pod залишиться у стані Pending.- CPU: контейнер буде throttled (обмежено) — він працюватиме повільніше
- Memory: контейнер буде killed (OOMKilled — Out Of Memory Killed) та перезапущено
memory: "256Mi" означає, що якщо контейнер спробує використати більше 256 MiB, Kubernetes його вб'є.failureThreshold разів підряд — Kubernetes перезапускає контейнер.Навіщо це потрібно: Іноді застосунок може "зависнути" — процес працює, але не відповідає на запити (deadlock, infinite loop). Liveness probe виявляє такі ситуації та перезапускає контейнер.Різниця між Liveness та Readiness Probe
Це важлива концепція, яку часто плутають новачки. Давайте розберемо детально:
Приклад сценаріїв:
Сценарій 1: Старт застосунку
Що відбувається:
- Контейнер стартує
- Застосунок підключається до БД (5 секунд)
- Readiness probe fails → Pod не отримує трафік
- Підключення встановлено
- Readiness probe ✓ → Pod починає отримувати трафік
Результат: Трафік надходить лише після повної готовності.
Сценарій 2: Тимчасова проблема з БД
Що відбувається:
- Pod працює нормально
- БД тимчасово недоступна (мережева проблема)
- Readiness probe fails → Pod виключається з балансування
- Liveness probe ✓ → контейнер НЕ перезапускається
- БД знову доступна
- Readiness probe ✓ → Pod знову отримує трафік
Результат: Трафік не надходить на проблемний Pod, але контейнер не перезапускається (бо проблема тимчасова).
Сценарій 3: Deadlock у застосунку
Що відбувається:
- Pod працює нормально
- Deadlock у коді — застосунок завис
- Readiness probe fails → Pod виключається з балансування
- Liveness probe fails (3 рази підряд)
- Kubernetes kills контейнер
- Контейнер перезапускається
- Після старту readiness probe ✓ → Pod знову працює
Результат: Завислий контейнер автоматично перезапущено.
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
initialDelaySeconds, ніж час старту застосунку. Readiness probe може мати менший initialDelaySeconds.livenessProbe:
initialDelaySeconds: 60 # Достатньо часу для старту
periodSeconds: 10
readinessProbe:
initialDelaySeconds: 10 # Швидше виявляє готовність
periodSeconds: 5
Крок 5: Розгортання у Kubernetes
Перед розгортанням завантажимо наш щойно зібраний образ у кластер Minikube, щоб Kubernetes міг отримати до нього доступ:
Тепер застосуємо маніфест:
Перевіримо стан Deployment:
Бачимо 0/3 — жоден Pod ще не готовий. Це нормально — Pod стартують. Почекаємо кілька секунд:
Тепер усі 3 репліки готові! Переглянемо Pod:
-l app=todoapi:Це label selector — фільтрує Pod за міткою app=todoapi. Без нього ви побачите всі Pod у namespace, включно з Pod інших застосунків.Це той самий селектор, який використовує Deployment у spec.selector.matchLabels.Переглянемо детальну інформацію про один Pod:
Тут ви бачите всю інформацію про Pod: образ, порти, ресурси, probes, змінні оточення та події.
Крок 6: Тестування API через port-forward
Щоб протестувати API, використаємо kubectl port-forward для перенаправлення трафіку з локальної машини на Pod:
Тепер API доступний на http://localhost:8080. Відкрийте новий термінал та протестуйте:
Зверніть увагу на поле servedBy — кожен запит обробляється різним Pod! Це демонструє, що kubectl port-forward автоматично балансує навантаження між репліками.
ConcurrentDictionary для зберігання даних у пам'яті. Це означає, що кожен Pod має власне сховище. Якщо ви створите todo через Pod 1, а потім запитаєте список через Pod 2 — ви не побачите цей todo.Чому так?Кожен Pod — це окремий процес з власною пам'яттю. Вони не діляться даними між собою. У реальному застосунку ви б використовували зовнішню базу даних (PostgreSQL, MongoDB), до якої підключаються всі Pod.Демонстрація проблеми:# Створюємо todo (обробляє Pod 1)
curl -X POST http://localhost:8080/todos -d '{"title":"Test"}'
# Відповідь: servedBy: todoapi-xxx-pod1
# Запитуємо список (обробляє Pod 2)
curl http://localhost:8080/todos
# Відповідь: servedBy: todoapi-xxx-pod2, todos: [] (порожньо!)
Крок 7: Тестування self-healing
Тепер продемонструємо self-healing — видалимо один Pod та подивимося, як Deployment автоматично створить новий:
Одразу перевіримо список Pod:
Бачимо новий Pod n8p2r у стані ContainerCreating. Через кілька секунд:
Знову три репліки! Deployment автоматично відновив бажаний стан.
Крок 8: Тестування масштабування
Тепер збільшимо кількість реплік з 3 до 5:
Два нові Pod створюються. Через 10-15 секунд:
Тепер працює 5 реплік! Зменшимо назад до 3:
Два Pod видаляються (статус Terminating). Kubernetes вибрав найновіші Pod для видалення.
Крок 9: Моніторинг ресурсів
Переглянемо, скільки ресурсів використовують Pod:
kubectl top вимагає Metrics Server. У Minikube його можна увімкнути:minikube addons enable metrics-server
Бачимо, що кожен Pod використовує ~2m CPU (0.002 cores) та ~45 MiB пам'яті. Це значно менше за наші limits (500m CPU, 256Mi memory), тому Pod працюють без обмежень.
Крок 10: Перегляд логів
Переглянемо логи одного з Pod:
Якщо хочете стежити за логами в реальному часі (як tail -f):
Щоб переглянути логи всіх Pod одночасно:
Прапорець --tail=10 показує лише останні 10 рядків з кожного Pod.
Очищення ресурсів
Після експериментів видалимо Deployment:
Це автоматично видалить:
- Deployment
- ReplicaSet (створений Deployment)
- Всі Pod (створені ReplicaSet)
Перевіримо:
Все видалено!
kubectl apply -f deployment.yaml, можете видалити їх тим самим файлом:kubectl delete -f k8s/deployment.yaml
Практичні завдання
Тепер, коли ви розумієте основи Deployment, виконайте наступні завдання для закріплення знань:
Завдання 1: Deployment з різними образами
Мета: Навчитись створювати Deployment з різними образами та порівнювати їх поведінку.
Завдання:
- Створіть два Deployment:
nginx-deploymentз образомnginx:1.27(3 репліки)httpd-deploymentз образомhttpd:2.4(2 репліки)
- Перевірте, що всі Pod працюють
- Використайте
kubectl port-forwardдля доступу до кожного Deployment та порівняйте відповіді (nginx показує "Welcome to nginx!", httpd показує "It works!") - Видаліть один Pod з кожного Deployment та переконайтесь, що вони автоматично відновлюються
Очікуваний результат:
kubectl get deployments
# NAME READY UP-TO-DATE AVAILABLE AGE
# nginx-deployment 3/3 3 3 2m
# httpd-deployment 2/2 2 2 2m
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# nginx-deployment-xxx-yyy 1/1 Running 0 2m
# nginx-deployment-xxx-zzz 1/1 Running 0 2m
# nginx-deployment-xxx-www 1/1 Running 0 2m
# httpd-deployment-aaa-bbb 1/1 Running 0 2m
# httpd-deployment-aaa-ccc 1/1 Running 0 2m
nginx-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
httpd-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd-deployment
spec:
replicas: 2
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:2.4
ports:
- containerPort: 80
Команди:
# Створення
kubectl apply -f nginx-deployment.yaml
kubectl apply -f httpd-deployment.yaml
# Перевірка
kubectl get deployments
kubectl get pods
# Тестування nginx
kubectl port-forward deployment/nginx-deployment 8080:80
curl http://localhost:8080
# Відповідь: Welcome to nginx!
# Тестування httpd (у новому терміналі)
kubectl port-forward deployment/httpd-deployment 8081:80
curl http://localhost:8081
# Відповідь: <html><body><h1>It works!</h1></body></html>
# Тестування self-healing
kubectl delete pod <nginx-pod-name>
kubectl get pods -w # Спостерігайте за створенням нового Pod
# Очищення
kubectl delete deployment nginx-deployment httpd-deployment
Завдання 2: Експерименти з ресурсами
Мета: Зрозуміти, як працюють resource requests та limits.
Завдання:
- Створіть Deployment з дуже низькими limits:
resources: limits: memory: '10Mi' cpu: '10m' - Спостерігайте, що відбувається з Pod (можливо, OOMKilled)
- Поступово збільшуйте limits, поки Pod не стане стабільним
- Використайте
kubectl top podsдля моніторингу реального споживання ресурсів
Очікуваний результат:
Ви побачите, як Pod з недостатніми ресурсами постійно перезапускаються (CrashLoopBackOff або OOMKilled), а після збільшення limits стають стабільними.
low-resources-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: low-resources-test
spec:
replicas: 1
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: nginx
image: nginx:1.27
resources:
limits:
memory: '10Mi' # Занадто мало для nginx!
cpu: '10m'
requests:
memory: '5Mi'
cpu: '5m'
Команди:
# Створення
kubectl apply -f low-resources-deployment.yaml
# Спостереження (Pod буде постійно перезапускатись)
kubectl get pods -w
# NAME READY STATUS RESTARTS AGE
# low-resources-test-xxx-yyy 0/1 OOMKilled 1 10s
# low-resources-test-xxx-yyy 0/1 CrashLoopBackOff 1 20s
# Перегляд причини
kubectl describe pod <pod-name>
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
# Збільшення ресурсів
kubectl edit deployment low-resources-test
# Змініть memory limit на 64Mi
# Тепер Pod має стартувати успішно
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# low-resources-test-xxx-zzz 1/1 Running 0 15s
# Перевірка реального споживання
kubectl top pods
# NAME CPU(cores) MEMORY(bytes)
# low-resources-test-xxx-zzz 1m 8Mi
# Очищення
kubectl delete deployment low-resources-test
Завдання 3: Масштабування під навантаженням
Мета: Навчитись масштабувати застосунок залежно від навантаження.
Завдання:
- Розгорніть TodoApi з 2 репліками
- Використайте
kubectl port-forwardта інструмент навантаження (наприклад,abабоwrk) для генерації трафіку - Під час навантаження збільште кількість реплік до 5
- Спостерігайте за розподілом навантаження через логи (
kubectl logs) - Після завершення тесту зменшіть репліки назад до 2
Очікуваний результат:
Ви побачите, як нові Pod створюються під час навантаження та починають обробляти запити, розподіляючи навантаження між усіма репліками.
Команди:
# Розгортання TodoApi (використовуйте YAML з попереднього прикладу)
kubectl apply -f k8s/deployment.yaml
kubectl scale deployment todoapi --replicas=2
# Port-forward
kubectl port-forward deployment/todoapi 8080:8080 &
# Генерація навантаження (потрібен Apache Bench)
# macOS: brew install httpd
# Ubuntu: sudo apt install apache2-utils
ab -n 1000 -c 10 http://localhost:8080/health
# Під час виконання ab — у новому терміналі:
kubectl scale deployment todoapi --replicas=5
# Спостереження за Pod
kubectl get pods -l app=todoapi -w
# Перегляд логів (побачите, що запити розподіляються між Pod)
kubectl logs -l app=todoapi --tail=20
# Після завершення тесту
kubectl scale deployment todoapi --replicas=2
# Очищення
kubectl delete deployment todoapi
Альтернатива з wrk (більш потужний інструмент):
# Встановлення wrk
# macOS: brew install wrk
# Ubuntu: sudo apt install wrk
# Генерація навантаження (10 потоків, 100 з'єднань, 30 секунд)
wrk -t10 -c100 -d30s http://localhost:8080/health
Завдання 4: Налаштування Health Checks
Мета: Навчитись правильно налаштовувати liveness та readiness probes.
Завдання:
- Створіть Deployment з некоректними health checks (занадто короткий
initialDelaySeconds) - Спостерігайте за CrashLoopBackOff
- Виправте налаштування та досягніть стабільного стану
- Експериментуйте з різними значеннями
periodSeconds,timeoutSeconds,failureThreshold
Очікуваний результат:
Ви зрозумієте, як неправильні health checks можуть призвести до постійних перезапусків Pod, та навчитесь підбирати оптимальні значення.
bad-probes-deployment.yaml (некоректні налаштування):
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad-probes-test
spec:
replicas: 1
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 1 # Занадто мало! nginx не встигне стартувати
periodSeconds: 2
failureThreshold: 2
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 1
periodSeconds: 2
failureThreshold: 2
Команди:
# Створення з поганими налаштуваннями
kubectl apply -f bad-probes-deployment.yaml
# Спостереження (Pod буде постійно перезапускатись)
kubectl get pods -w
# NAME READY STATUS RESTARTS AGE
# bad-probes-test-xxx-yyy 0/1 Running 0 2s
# bad-probes-test-xxx-yyy 0/1 Running 1 5s
# bad-probes-test-xxx-yyy 0/1 CrashLoopBackOff 1 10s
# Перегляд подій
kubectl describe pod <pod-name>
# Events:
# Warning Unhealthy 10s (x3 over 14s) kubelet Liveness probe failed: Get "http://10.244.0.15:80/": dial tcp 10.244.0.15:80: connect: connection refused
# Warning BackOff 5s (x2 over 10s) kubelet Back-off restarting failed container
# Виправлення (збільшення initialDelaySeconds)
kubectl edit deployment bad-probes-test
# Змініть initialDelaySeconds на 10 для обох probes
# Тепер Pod має стартувати успішно
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# bad-probes-test-xxx-zzz 1/1 Running 0 15s
# Очищення
kubectl delete deployment bad-probes-test
Правильні налаштування:
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10 # Достатньо часу для старту
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5 # Може бути менше за liveness
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
Завдання 5: Декларативне управління через Git
Мета: Навчитись використовувати Git для версійного контролю Kubernetes маніфестів.
Завдання:
- Створіть Git репозиторій для Kubernetes маніфестів
- Додайте Deployment YAML з 3 репліками
- Зробіть commit та застосуйте через
kubectl apply - Змініть кількість реплік на 5 у YAML, зробіть commit
- Застосуйте зміни через
kubectl apply - Використайте
git logдля перегляду історії змін - Поверніться до попередньої версії через
git revertта застосуйте
Очікуваний результат:
Ви матимете повну історію змін у Git, зможете відстежувати, хто і коли змінював конфігурацію, та легко повертатись до попередніх версій.
Команди:
# Створення Git репозиторію
mkdir k8s-manifests
cd k8s-manifests
git init
# Створення першої версії Deployment
cat > nginx-deployment.yaml <<'YAML'
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
YAML
# Commit
git add nginx-deployment.yaml
git commit -m "Initial deployment: 3 replicas"
# Застосування
kubectl apply -f nginx-deployment.yaml
# Перевірка
kubectl get deployments
# NAME READY UP-TO-DATE AVAILABLE AGE
# nginx-deployment 3/3 3 3 10s
# Зміна кількості реплік
sed -i.bak 's/replicas: 3/replicas: 5/' nginx-deployment.yaml
# Commit
git add nginx-deployment.yaml
git commit -m "Scale up to 5 replicas"
# Застосування
kubectl apply -f nginx-deployment.yaml
# Перевірка
kubectl get deployments
# NAME READY UP-TO-DATE AVAILABLE AGE
# nginx-deployment 5/5 5 5 1m
# Перегляд історії
git log --oneline
# a1b2c3d Scale up to 5 replicas
# d4e5f6g Initial deployment: 3 replicas
# Повернення до попередньої версії
git revert HEAD --no-edit
# Застосування
kubectl apply -f nginx-deployment.yaml
# Перевірка (знову 3 репліки)
kubectl get deployments
# NAME READY UP-TO-DATE AVAILABLE AGE
# nginx-deployment 3/3 3 3 2m
# Очищення
kubectl delete -f nginx-deployment.yaml
cd ..
rm -rf k8s-manifests
Переваги цього підходу:
- Історія змін —
git logпоказує всі зміни з коментарями - Code review — зміни можна переглянути через Pull Request перед застосуванням
- Rollback — легко повернутись до будь-якої версії через
git revertабоgit checkout - Документація — Git commit messages документують причини змін
- Collaboration — кілька людей можуть працювати над маніфестами одночасно
Резюме
У цій статті ми детально вивчили Deployment — основний ресурс Kubernetes для управління застосунками у production. Ось що ми розглянули:
Проблема ручного управління Pod
Що таке Deployment
Архітектура: Deployment → ReplicaSet → Pod
Анатомія YAML
Self-healing
Масштабування (3 способи)
kubectl scale (швидко), kubectl apply (декларативно), kubectl edit (інтерактивно). Кожен спосіб має свої переваги та недоліки.Практичний приклад: TodoApi
Health Checks
Resource Management
Практичні завдання
Ключові висновки
- Завжди використовуйте Deployment у production — ніколи не створюйте Pod напряму. Deployment надає self-healing, масштабування та rolling updates.
- Декларативний підхід — описуйте бажаний стан у YAML, а Kubernetes автоматично досягає його. Зберігайте YAML у Git для версійного контролю.
- Reconciliation loop — фундаментальний принцип Kubernetes. Контролери постійно порівнюють бажаний стан з поточним та усувають розбіжності.
- ReplicaSet — проміжний шар — Deployment не створює Pod напряму. Він використовує ReplicaSet для підтримки кількості реплік та реалізації rolling updates.
- Health checks критично важливі — правильно налаштовані liveness та readiness probes гарантують стабільність застосунку. Неправильні налаштування призводять до CrashLoopBackOff.
- Resource requests та limits — завжди вказуйте ресурси для Pod. Requests використовуються для scheduling, limits запобігають перевитраті ресурсів.
- Масштабування — це просто — змінити кількість реплік можна однією командою або редагуванням YAML. Kubernetes автоматично створює або видаляє Pod.
Що далі?
У наступній статті "Rolling Updates та управління життєвим циклом Deployment" ми детально розглянемо:
- Як працює rolling update (покрокова візуалізація)
- Стратегії оновлення: RollingUpdate vs Recreate
- Параметри: maxSurge, maxUnavailable, progressDeadlineSeconds, minReadySeconds (з математичними розрахунками)
- Rollback та історія версій
- Оновлення TodoApi з v1.0 на v2.0 з реальними змінами в коді
- Troubleshooting типових проблем
Ви навчитесь виконувати оновлення застосунків без downtime та швидко повертатись до попередніх версій у разі проблем.
Корисні команди
Для швидкого доступу — всі команди, які ми використовували у цій статті:
# Створення Deployment
kubectl apply -f deployment.yaml
# Перегляд Deployment
kubectl get deployments
kubectl get deploy # скорочена форма
# Детальна інформація
kubectl describe deployment <name>
# Перегляд Pod
kubectl get pods
kubectl get pods -l app=myapp # з фільтром за міткою
# Детальна інформація про Pod
kubectl describe pod <pod-name>
# Масштабування через kubectl scale
kubectl scale deployment <name> --replicas=5
# Масштабування через kubectl edit
kubectl edit deployment <name>
# Масштабування через kubectl apply
# (змініть replicas у YAML та застосуйте)
kubectl apply -f deployment.yaml
# Логи одного Pod
kubectl logs <pod-name>
# Логи з follow (реальний час)
kubectl logs -f <pod-name>
# Логи всіх Pod з міткою
kubectl logs -l app=myapp --tail=20
# Моніторинг ресурсів
kubectl top pods
kubectl top pods -l app=myapp
# Port-forward на Deployment
kubectl port-forward deployment/<name> 8080:8080
# Port-forward на конкретний Pod
kubectl port-forward pod/<pod-name> 8080:8080
# Тестування через curl
curl http://localhost:8080/health
# Видалення Deployment (також видаляє ReplicaSet та Pod)
kubectl delete deployment <name>
# Видалення через файл
kubectl delete -f deployment.yaml
# Видалення всіх ресурсів з міткою
kubectl delete all -l app=myapp
# Перегляд подій
kubectl get events --sort-by=.metadata.creationTimestamp
# Виконання команди всередині Pod
kubectl exec -it <pod-name> -- /bin/bash
# Копіювання файлів з/до Pod
kubectl cp <pod-name>:/path/to/file ./local-file
kubectl cp ./local-file <pod-name>:/path/to/file
Додаткові ресурси
Наступна стаття: Rolling Updates та управління життєвим циклом Deployment
Патерни використання Pod
Init-контейнери та Sidecar — розв'язання реальних проблем у Kubernetes з практичними прикладами на .NET
Rolling Updates та управління життєвим циклом Deployment
Оновлення застосунків без downtime — від теорії до практики з детальною візуалізацією, математичними розрахунками та реальними прикладами