У попередніх статтях ми детально вивчили Pod — атомарну одиницю Kubernetes. Ми навчилися створювати Pod, налаштовувати контейнери, використовувати volumes, init-контейнери та sidecar-патерни. Але наприкінці статті про Pod ми зробили важливий висновок: Pod не слід створювати напряму у production.
Чому? Давайте розглянемо типовий сценарій розгортання веб-застосунку.
Уявіть, що ви розгортаєте ASP.NET Core Web API у Kubernetes. Ваші вимоги:
Якби ви використовували Pod напряму, вам довелося б:
Проблеми цього підходу:
Немає self-healing
Немає масштабування
Downtime при оновленні
Немає історії версій
Ручна робота
Це неприйнятно для production. Саме тому існує Deployment.
Нам потрібен механізм, який:
Саме це і робить Deployment.
Deployment — це ресурс Kubernetes, який забезпечує декларативне управління набором ідентичних Pod. Ви описуєте бажаний стан (скільки реплік, яка версія образу, які ресурси), а Kubernetes автоматично підтримує цей стан.
replicas: 3, але працює лише 2 Pod (один впав). Deployment Controller виявляє розбіжність та створює третій Pod. Ви нічого не робите вручну — система сама усуває проблему.Self-healing
Декларативне масштабування
kubectl scale) або редагуванням YAML. Kubernetes сам створить або видалить Pod для досягнення бажаної кількості.Rolling Updates
Rollback
kubectl rollout undo.Версіонування
Deployment не створює Pod напряму. Він використовує проміжний ресурс — ReplicaSet:
Чому потрібен ReplicaSet?
ReplicaSet відповідає за підтримку заданої кількості реплік. Його єдине завдання — гарантувати, що завжди працює рівно N Pod з певним шаблоном.
Deployment використовує ReplicaSet для реалізації rolling updates: при оновленні створюється новий ReplicaSet з новим шаблоном Pod, а старий поступово зменшується до нуля. Це дозволяє виконувати оновлення без downtime.
Тепер розглянемо, як описати Deployment у YAML. Почнемо з мінімального прикладу та поступово додаватимемо поля.
Найпростіший 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 та template.metadata.labels — це найважливіша частина Deployment. Давайте розберемо детально:
spec:
selector:
matchLabels:
app: nginx # ← Deployment шукає Pod з цією міткою
template:
metadata:
labels:
app: nginx # ← Pod створюються з цією міткою
Що відбувається:
app: nginx (з template.metadata.labels)app: nginx (через selector.matchLabels)replicas — створює нові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. Почнемо з базових, потім перейдемо до розширених.
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:
mcr.microsoft.com/dotnet/samples:aspnetappТепер створимо реальний Deployment та подивимося, як він працює.
Створіть файл 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
Застосуйте маніфест до кластера:
Переглянемо список Deployment:
Що означають колонки:
metadata.name)3/3 — 3 з 3 реплік готові до роботи (стан Ready)3 — 3 репліки відповідають поточній версії шаблону (актуальний образ, змінні оточення тощо)3 — 3 репліки доступні для обслуговування трафіку (пройшли readiness probe, якщо він налаштований)Deployment автоматично створив Pod. Подивимося на них:
Структура імені Pod:
Ім'я Pod складається з трьох частин: nginx-deployment-7d6b8c9f4d-8xk2p
nginx-deployment — ім'я Deployment7d6b8c9f4d — хеш ReplicaSet (унікальний ідентифікатор версії шаблону Pod)8xk2p — унікальний суфікс Pod (генерується випадково)Це дозволяє легко ідентифікувати, до якого Deployment належить Pod, та яку версію шаблону він використовує.
Переглянемо детальну інформацію:
Тут ви бачите:
Ми згадували, що Deployment не створює Pod напряму. Він використовує ReplicaSet. Давайте розберемося детально, що це і навіщо.
ReplicaSet — це ресурс Kubernetes, який забезпечує підтримку заданої кількості ідентичних Pod. Його єдине завдання — гарантувати, що завжди працює рівно N Pod з певним шаблоном.
Коли ви створюєте Deployment, він автоматично створює ReplicaSet:
ReplicaSet виконує дві ключові функції:
Переглянемо ReplicaSet, створений нашим Deployment:
Що означають колонки:
spec.replicas Deployment)Ready)Тепер продемонструємо головну перевагу Deployment — автоматичне відновлення (self-healing).
Видалимо один Pod вручну та подивимося, що станеться:
Одразу перевіримо список Pod:
Бачимо новий Pod з іменем p4k8t у стані ContainerCreating. Що сталося?
8xk2p2 < 3)p4k8tЧерез кілька секунд:
Знову три репліки! Це і є self-healing — система сама усуває розбіжності між бажаним станом (replicas: 3) та поточним станом (2 працюючі Pod).
spec.replicas: 3)Давайте детально розглянемо, як працює цикл узгодження при видаленні Pod:
Ключові моменти:
Одна з найважливіших можливостей Deployment — масштабування (scaling). Це зміна кількості реплік Pod для адаптації до навантаження.
Уявіть, що ваш веб-застосунок отримує різне навантаження протягом дня:
Без масштабування вам довелося б постійно тримати 10 реплік, витрачаючи ресурси навіть коли вони не потрібні. З масштабуванням ви можете динамічно змінювати кількість реплік.
Kubernetes надає три способи змінити кількість реплік Deployment:
1. Команда kubectl scale
2. Редагування YAML та kubectl apply
3. Команда kubectl edit
Розглянемо кожен спосіб детально.
Найпростіший спосіб — команда kubectl scale:
Перевіримо результат:
Kubernetes створив 2 нові Pod (x2n9k та q7m4p) для досягнення бажаної кількості 5 реплік.
Через кілька секунд:
Тепер усі 5 реплік працюють.
Зменшення кількості реплік:
Так само легко зменшити кількість реплік:
Kubernetes видаляє 3 зайві Pod. Статус Terminating означає, що Pod отримав сигнал завершення (SIGTERM) і має 30 секунд (за замовчуванням) для graceful shutdown.
Переваги kubectl scale:
Недоліки kubectl scale:
replicas: 3, масштабування скасуєтьсяДекларативний підхід — змінюєте 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:
Недоліки kubectl apply:
kubectl apply для production. Зберігайте всі YAML файли у Git. Це дозволяє:git revertІнтерактивне редагування — відкриває редактор з поточною конфігурацією:
У редакторі ви побачите повну конфігурацію Deployment (включно з полями, які додав Kubernetes автоматично). Знайдіть рядок replicas та змініть значення:
spec:
replicas: 4 # ← Змініть на потрібне значення
Збережіть та закрийте редактор (у vim: :wq, у nano: Ctrl+O, Ctrl+X).
Kubernetes одразу застосує зміни.
Переваги kubectl edit:
Недоліки kubectl edit:
kubectl edit зручний для швидких експериментів, але не рекомендується для production. Причини:kubectl edit лише для debugging або швидких тестів. Для production завжди використовуйте kubectl apply з YAML файлами у Git.kubectl scale
Коли використовувати:
Приклад: Раптовий сплеск трафіку — швидко збільшити репліки, поки не з'ясуєте причину.
kubectl apply
Коли використовувати:
Приклад: Планове збільшення реплік перед маркетинговою кампанією — змінюєте YAML, робите commit, застосовуєте через CI/CD.
kubectl edit
Коли використовувати:
Приклад: Ви на сервері без Git репозиторію, потрібно швидко змінити конфігурацію для перевірки гіпотези.
Давайте подивимося, що відбувається всередині Kubernetes при масштабуванні з 3 до 5 реплік:
Ключові етапи масштабування:
replicas через kubectlRunning та стають доступнимиВесь процес повністю автоматичний — ви лише змінюєте одне число (replicas), а Kubernetes виконує всю роботу.
Тепер створимо реальний застосунок ASP.NET Core та розгорнемо його у Kubernetes через Deployment. Це буде простий TodoApi з трьома репліками.
Наш TodoApi матиме наступну структуру:
Створимо простий TodoApi з використанням ASP.NET Core Minimal API (без Entity Framework для простоти):
Структура проєкту:
TodoApi/
├── Program.cs
├── TodoApi.csproj
├── Dockerfile
└── k8s/
└── deployment.yaml
Program.cs:
using System.Collections.Concurrent;
var builder = WebApplication.CreateBuilder(args);
// Додаємо Swagger для документації API
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// In-memory сховище (для демонстрації)
var todos = new ConcurrentDictionary<int, Todo>();
var nextId = 1;
var app = builder.Build();
// Swagger UI (доступний лише у Development)
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// 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>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project>
servedBy у відповідях?Кожна відповідь API містить поле servedBy з hostname Pod. Це дозволяє побачити, який саме Pod обробив запит. У Kubernetes кожен Pod має унікальне ім'я (наприклад, todoapi-7d6b8c9f4d-x2n9k), яке зберігається у змінній оточення HOSTNAME.Це корисно для демонстрації балансування навантаження — ви побачите, що різні запити обробляються різними Pod.Створимо multi-stage Dockerfile для оптимізації розміру образу:
Dockerfile:
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.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:8.0 AS runtime
WORKDIR /app
# Створюємо non-root користувача для безпеки
RUN addgroup --system --gid 1000 appgroup && \
adduser --system --uid 1000 --ingroup appgroup appuser
# Копіюємо зібраний застосунок
COPY --from=build /app/publish .
# Змінюємо власника файлів
RUN chown -R appuser:appgroup /app
# Перемикаємось на non-root користувача
USER appuser
# Відкриваємо порт 8080 (non-privileged port)
EXPOSE 8080
# Налаштовуємо ASP.NET Core для прослуховування на порту 8080
ENV ASPNETCORE_URLS=http://+:8080
# Запускаємо застосунок
ENTRYPOINT ["dotnet", "TodoApi.dll"]
dotnet/sdk:8.0 (розмір ~1.2 GB) для компіляції застосункуdotnet/aspnet:8.0 (розмір ~200 MB) лише з runtimeТепер зберемо Docker образ та завантажимо його у Minikube:
eval $(minikube docker-env)?Ця команда налаштовує ваш локальний Docker CLI для роботи з Docker daemon всередині Minikube. Після цього всі образи, які ви збираєте, зберігаються безпосередньо у Minikube, і їх не потрібно завантажувати окремо.Без цієї команди вам довелося б:minikube image loadeval $(minikube docker-env) образ одразу доступний у Minikube.Перевіримо, що образ створено:
Створимо маніфест 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.memory: "256Mi" означає, що якщо контейнер спробує використати більше 256 MiB, Kubernetes його вб'є.failureThreshold разів підряд — Kubernetes перезапускає контейнер.Навіщо це потрібно: Іноді застосунок може "зависнути" — процес працює, але не відповідає на запити (deadlock, infinite loop). Liveness probe виявляє такі ситуації та перезапускає контейнер.Це важлива концепція, яку часто плутають новачки. Давайте розберемо детально:
Приклад сценаріїв:
Сценарій 1: Старт застосунку
Що відбувається:
Результат: Трафік надходить лише після повної готовності.
Сценарій 2: Тимчасова проблема з БД
Що відбувається:
Результат: Трафік не надходить на проблемний Pod, але контейнер не перезапускається (бо проблема тимчасова).
Сценарій 3: Deadlock у застосунку
Що відбувається:
Результат: Завислий контейнер автоматично перезапущено.
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
Тепер застосуємо маніфест:
Перевіримо стан 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, змінні оточення та події.
Щоб протестувати 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: [] (порожньо!)
Тепер продемонструємо self-healing — видалимо один Pod та подивимося, як Deployment автоматично створить новий:
Одразу перевіримо список Pod:
Бачимо новий Pod n8p2r у стані ContainerCreating. Через кілька секунд:
Знову три репліки! Deployment автоматично відновив бажаний стан.
Тепер збільшимо кількість реплік з 3 до 5:
Два нові Pod створюються. Через 10-15 секунд:
Тепер працює 5 реплік! Зменшимо назад до 3:
Два Pod видаляються (статус Terminating). Kubernetes вибрав найновіші Pod для видалення.
Переглянемо, скільки ресурсів використовують 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 працюють без обмежень.
Переглянемо логи одного з Pod:
Якщо хочете стежити за логами в реальному часі (як tail -f):
Щоб переглянути логи всіх Pod одночасно:
Прапорець --tail=10 показує лише останні 10 рядків з кожного Pod.
Після експериментів видалимо Deployment:
Це автоматично видалить:
Перевіримо:
Все видалено!
kubectl apply -f deployment.yaml, можете видалити їх тим самим файлом:kubectl delete -f k8s/deployment.yaml
Тепер, коли ви розумієте основи Deployment, виконайте наступні завдання для закріплення знань:
Мета: Навчитись створювати Deployment з різними образами та порівнювати їх поведінку.
Завдання:
nginx-deployment з образом nginx:1.27 (3 репліки)httpd-deployment з образом httpd:2.4 (2 репліки)kubectl port-forward для доступу до кожного Deployment та порівняйте відповіді (nginx показує "Welcome to nginx!", httpd показує "It works!")Очікуваний результат:
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
Мета: Зрозуміти, як працюють resource requests та limits.
Завдання:
resources:
limits:
memory: "10Mi"
cpu: "10m"
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
Мета: Навчитись масштабувати застосунок залежно від навантаження.
Завдання:
kubectl port-forward та інструмент навантаження (наприклад, ab або wrk) для генерації трафікуkubectl logs)Очікуваний результат:
Ви побачите, як нові 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
Мета: Навчитись правильно налаштовувати liveness та readiness probes.
Завдання:
initialDelaySeconds)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
Мета: Навчитись використовувати Git для версійного контролю Kubernetes маніфестів.
Завдання:
kubectl applykubectl applygit 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 показує всі зміни з коментарямиgit revert або git checkoutУ цій статті ми детально вивчили Deployment — основний ресурс Kubernetes для управління застосунками у production. Ось що ми розглянули:
Проблема ручного управління Pod
Що таке Deployment
Архітектура: Deployment → ReplicaSet → Pod
Анатомія YAML
Self-healing
Масштабування (3 способи)
kubectl scale (швидко), kubectl apply (декларативно), kubectl edit (інтерактивно). Кожен спосіб має свої переваги та недоліки.Практичний приклад: TodoApi
Health Checks
Resource Management
Практичні завдання
У наступній статті "Rolling Updates та управління життєвим циклом Deployment" ми детально розглянемо:
Ви навчитесь виконувати оновлення застосунків без 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 — від теорії до практики з детальною візуалізацією, математичними розрахунками та реальними прикладами