Docker та контейнеризація в AWS — ECR, ECS та Fargate
Docker та контейнеризація в AWS
Передумови
Для виконання практичних завдань у цьому розділі необхідне таке програмне забезпечення та конфігурація:
| Інструмент | Версія | Призначення |
|---|---|---|
| AWS CLI v2 | 2.x | Керування ресурсами AWS з терміналу |
| Docker Desktop | 4.x+ | Збірка та локальний запуск контейнерів |
| .NET SDK 8 | 8.0+ | Компіляція ASP.NET Core проєктів |
| AWS-акаунт | — | Доступ до ECR, ECS, IAM, CloudWatch |
Перевірте налаштування AWS CLI перед початком:
aws configure list # переглянути поточний профіль
aws sts get-caller-identity # перевірити підключення до акаунту
Користувач або роль, від імені якої ви працюєте, повинна мати права на: ecr:*, ecs:*, iam:CreateRole, iam:AttachRolePolicy, ec2:DescribeVpcs, ec2:DescribeSubnets, ec2:CreateSecurityGroup, elasticloadbalancing:*, logs:CreateLogGroup.
Проблема, яку вирішують контейнери

Уявіть класичну ситуацію: ваш .NET Web API ідеально працює на вашому MacBook. Ви передаєте код колезі — і він не запускається, бо у нього інша версія .NET Runtime. Ви деплоїте на сервер з Ubuntu — і знову проблеми, бо там встановлені інші версії бібліотек. DevOps-інженер каже: «У мене все працює» — але ж середовища різні!
Це фундаментальна проблема розробки програмного забезпечення, відома як «works on my machine» — «у мене на машині працює». Десятиліттями це вирішувалось через детальну документацію налаштування середовища, складні інструкції для DevOps, або просто через «ну, запустіть ще раз».
Docker вирішує цю проблему радикально: замість того, щоб передавати код і сподіватись, що середовище збігається — ви передаєте весь застосунок разом із середовищем. Код, .NET Runtime, бібліотеки, системні залежності — все упаковане в один незмінний артефакт, який однаково запускається на будь-якій машині.
Цей артефакт називається Docker-образом (image), а запущений екземпляр образу — контейнером (container).
Контейнер vs Віртуальна машина

Важливо відразу розуміти різницю між контейнером і віртуальною машиною (VM), яку ми вивчали у попередньому модулі (EC2).
VM — повна ізоляція через апаратну віртуалізацію. Кожна VM містить власну гостьову ОС (~1–20 GB). Запуск займає хвилини.
Контейнер — ізоляція на рівні ОС через namespaces та cgroups Linux. Контейнер ділить ядро з хост-ОС. Розмір образу — десятки–сотні MB. Запуск займає секунди або мілісекунди.
Для AWS це означає: на одному EC2 інстансі можна запустити десятки контейнерів, де могло б бути лише кілька VM.
Amazon ECR — Elastic Container Registry

Перш ніж запустити контейнер в AWS, образ потрібно десь зберегти. Amazon Elastic Container Registry (ECR) — це керований сервіс для зберігання, версіонування та поширення Docker-образів, аналогічний Docker Hub, але повністю інтегрований з AWS IAM та іншими сервісами AWS.
Чому ECR, а не Docker Hub?
Приватність та безпека: ECR-репозиторії за замовчуванням приватні і захищені IAM-правами. Жоден зовнішній користувач не зможе завантажити ваші образи без явного дозволу.
Інтеграція з AWS: ECS, EKS та Lambda автоматично аутентифікуються в ECR через IAM Role — без додаткових секретів і паролів. Порівняйте з Docker Hub, де потрібно зберігати credentials у Secrets Manager.
Швидкість: ECR розміщений в тому ж регіоні, що і ваші ECS/EKS кластери. Завантаження образу відбувається через внутрішню мережу AWS — без витрат на transfer та з максимальною швидкістю.
Lifecycle Policies: ECR автоматично видаляє старі версії образів за правилами, які ви визначаєте (наприклад, зберігати лише 10 останніх версій). Це суттєво знижує вартість зберігання.
Структура ECR
ECR організований через репозиторії (repositories). Один репозиторій — один Docker-образ у різних версіях (тегах).
123456789012.dkr.ecr.eu-central-1.amazonaws.com/
├── my-api/
│ ├── :latest
│ ├── :v1.2.3
│ └── :sha256-abc123...
├── my-worker/
│ ├── :latest
│ └── :v2.0.0
└── my-frontend/
└── :latest
Адреса ECR-репозиторію завжди має формат:
{account-id}.dkr.ecr.{region}.amazonaws.com/{repository-name}:{tag}
Практика: Створення ASP.NET Minimal API проєкту
Перш ніж писати Dockerfile, підготуємо сам застосунок. Пройдемо по кроках — від порожнього каталогу до готового проєкту, який потім контейнеризуємо та задеплоїмо в AWS.
Крок 1 — Створення проєкту:
Крок 2 — Структура проєкту:
MyApi/
├── MyApi.csproj
├── Program.cs
├── appsettings.json
└── appsettings.Development.json
Крок 3 — Замініть вміст Program.cs на мінімальний API з health check endpoint:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapGet("/", () => new { status = "ok", version = "1.0.0" });
app.MapGet("/weatherforecast", () =>
{
var summaries = new[] { "Freezing", "Cool", "Warm", "Hot" };
return Enumerable.Range(1, 5).Select(i => new
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(i)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
});
});
// Health check endpoint — потрібен для ECS health check
app.MapHealthChecks("/health");
app.Run();
Крок 4 — Локальний запуск і перевірка:
Проєкт готовий до контейнеризації. Далі — пишемо Dockerfile.
Dockerfile для .NET 8 — multi-stage build

Multi-stage build — це техніка написання Dockerfile, яка дозволяє значно зменшити розмір фінального образу. Ідея: використовуємо один образ для збірки (з усіма інструментами розробника) і окремий — мінімальний — для запуску в production.
Розглянемо детально кожен рядок оптимального Dockerfile для ASP.NET Core 8:
# ─── STAGE 1: BUILD ────────────────────────────────────────────────────────────
# Базовий образ із SDK для збірки (~800 MB)
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
# Встановлюємо робочу директорію всередині контейнера
WORKDIR /src
# Спочатку копіюємо ЛИШЕ .csproj файли і відновлюємо залежності.
# Це використовує Docker layer caching: якщо .csproj не змінились —
# команда restore береться з кешу, і збірка прискорюється суттєво.
COPY ["MyApi/MyApi.csproj", "MyApi/"]
RUN dotnet restore "MyApi/MyApi.csproj"
# Тепер копіюємо весь вихідний код
COPY . .
# Перейдіть у директорію проєкту
WORKDIR "/src/MyApi"
# Публікуємо Release-версію у директорію /app/publish
# -c Release — конфігурація Release
# -o /app/publish — директорія виводу
# --no-restore — restore вже виконали вище
RUN dotnet publish "MyApi.csproj" -c Release -o /app/publish --no-restore
# ─── STAGE 2: RUNTIME ──────────────────────────────────────────────────────────
# Мінімальний runtime-образ (~200 MB) — набагато менший за SDK
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
# Задаємо робочу директорію
WORKDIR /app
# Копіюємо ЛИШЕ опублікований артефакт зі stage build
# Весь SDK, вихідний код, проміжні файли — залишаються в stage build
# і НЕ потрапляють у фінальний образ
COPY --from=build /app/publish .
# Вказуємо точку входу
ENTRYPOINT ["dotnet", "MyApi.dll"]
Завдяки multi-stage build:
- Build image: ~800 MB (SDK + вихідний код)
- Final image: ~200 MB (лише Runtime + скомпільований застосунок)
- Вихідний код не потрапляє у production образ — важливо для безпеки
.dockerignore — що не копіювати в образ
Файл .dockerignore (аналог .gitignore) вказує Docker, які файли ігнорувати при копіюванні:
# Директорії збірки — вони будуть перезбудовані всередині контейнера
**/bin/
**/obj/
# Git-директорія та файли
.git
.gitignore
# Налаштування IDE
.vs/
.rider/
*.user
# Docker-файли самі по собі
Dockerfile
.dockerignore
# Локальні секрети та конфігурації
**/appsettings.Development.json
**/appsettings.Local.json
**/*.pfx
**/.env
Практика: Push образу в Amazon ECR

Після написання Dockerfile — виконуємо п'ять послідовних операцій для відправки образу в ECR.
Крок 1 — Аутентифікація у ECR. AWS ECR не зберігає постійних паролів Docker Hub. Натомість використовуються тимчасові токени, дійсні 12 годин. Команда get-login-password отримує токен і через pipe передає його стандартному docker login:
Крок 2 — Створення репозиторію у ECR (виконується один раз; якщо репозиторій вже існує — пропустіть):
Значення repositoryUri — адреса вашого репозиторію в ECR. Саме цей URI використовується у наступних двох кроках.
Крок 3 — Збірка та теґування образу:
# Теґуємо образ адресою ECR репозиторію
docker tag my-api:latest \
123456789012.dkr.ecr.eu-central-1.amazonaws.com/my-api:latest
Крок 4 — Завантаження образу в ECR:
--image-scanning-configuration scanOnPush=true. ECR автоматично сканує кожен завантажений образ на відомі CVE (Common Vulnerabilities and Exposures) за допомогою Amazon Inspector. Це безкоштовна перша лінія захисту від вразливостей у ваших залежностях.Amazon ECS — Elastic Container Service

Amazon Elastic Container Service (ECS) — це повністю керований оркестратор контейнерів від AWS. Якщо ECR — це «склад» для образів, то ECS — це «виробничий цех», де ці образи запускаються, масштабуються та оновлюються.
Оркестратор контейнерів — це система, яка відповідає за:
- Запуск контейнерів на доступних серверах
- Перезапуск контейнерів у разі збою
- Масштабування (запуск більше копій при зростанні навантаження)
- Rolling updates — оновлення без зупинки сервісу
- Балансування трафіку між копіями контейнера
Три ключові сутності ECS
ECS оперує трьома основними концепціями. Розберемо їх через аналогію з рестораном.
Task Definition — це рецепт страви. Він описує: який образ використовувати, скільки CPU і RAM виділити, які змінні середовища передати, де зберігати логи, які порти відкрити. Task Definition є незмінним шаблоном — кожна зміна створює нову версію (revision).
Task — це конкретна приготована страва. Один запущений екземпляр Task Definition. Контейнер, що реально виконується прямо зараз.
Service — це менеджер, який гарантує, що завжди є рівно N порцій страви готових до подачі. Якщо одна «страва» зіпсувалась (контейнер впав) — Service автоматично замовить нову. Він також керує rolling updates і підключає Tasks до Load Balancer.
Cluster — це сам ресторан: фізичне місце, де все це відбувається. Логічна група, яка об'єднує всі Tasks і Services.
Lifecycle Task: від запиту до зупинки
Розуміння станів Task є практично важливим: якщо Task «застряг» у певному стані, правильна діагностика можлива лише тоді, коли ви розумієте, що в ньому відбувається.
| Стан | Що відбувається |
|---|---|
PROVISIONING | Fargate виділяє ізольоване обчислювальне середовище, ENI |
PENDING | Завантажується Docker-образ з ECR у виділене середовище |
ACTIVATING | Запускаються контейнери, виконується ENTRYPOINT |
RUNNING | Контейнер працює; ECS виконує health check-и |
DEACTIVATING | Починається graceful shutdown: Task отримує сигнал SIGTERM |
STOPPING | Очікування завершення застосунку (до 30 сек за замовчуванням) |
STOPPED | Task завершено; причина зупинки зафіксована в stoppedReason |
Типові причини застрягання в PENDING: образ не знайдено в ECR (невірний URI або тег), Task Execution Role не має прав на ecr:GetDownloadUrlForLayer, Task у приватній підмережі без NAT Gateway.
Ревізії Task Definition. Кожна зміна Task Definition (навіть однієї змінної середовища) створює нову revision — наприклад, my-api:1, my-api:2, my-api:3. Попередні ревізії зберігаються назавжди і не видаляються автоматично. ECS Service завжди посилається на конкретну ревізію, що гарантує точну відтворюваність середовища при кожному запуску Task.
Task Definition: детальна конфігурація

Task Definition — це JSON-документ, але AWS Console надає зручний конструктор. Розберемо ключові поля:
{
"family": "my-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789012:role/my-api-task-role",
"containerDefinitions": [
{
"name": "my-api",
"image": "123456789012.dkr.ecr.eu-central-1.amazonaws.com/my-api:v1.2.3",
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{ "name": "ASPNETCORE_ENVIRONMENT", "value": "Production" },
{ "name": "ASPNETCORE_URLS", "value": "http://+:8080" }
],
"secrets": [
{
"name": "ConnectionStrings__DefaultConnection",
"valueFrom": "arn:aws:secretsmanager:eu-central-1:123456789012:secret:prod/db-connection"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-api",
"awslogs-region": "eu-central-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}
}
]
}
Розберемо важливі поля:
executionRoleArn — IAM Role для ECS агента (не вашого застосунку). Потрібна для завантаження образу з ECR та запису логів у CloudWatch. Стандартна роль: AmazonECSTaskExecutionRolePolicy.
taskRoleArn — IAM Role для вашого застосунку всередині контейнера. Якщо API потребує доступу до S3 або DynamoDB — саме сюди додаються ці права.
secrets — замість передачі паролів через environment (небезпечно!) — отримання секретів із AWS Secrets Manager або SSM Parameter Store. ECS агент завантажує секрет і підставляє як змінну середовища під час запуску контейнера.
healthCheck — ECS регулярно перевіряє стан контейнера. Якщо health check провалюється тричі — Task вважається нездоровим і Service замінює його новим.
Допустимі комбінації CPU та пам'яті для Fargate
Параметри cpu і memory у Task Definition — це рядки (не числа). Fargate не приймає довільні значення — допустимі лише певні комбінації, перелік яких наведено нижче. Спроба вказати неіснуючу комбінацію призведе до помилки валідації Task Definition.
| CPU (JSON рядок) | Реальне значення | Допустима пам'ять (memory, MB) |
|---|---|---|
"256" | 0.25 vCPU | 512, 1024, 2048 |
"512" | 0.5 vCPU | 1024, 2048, 3072, 4096 |
"1024" | 1 vCPU | 2048–8192 (крок 1024) |
"2048" | 2 vCPU | 4096–16384 (крок 1024) |
"4096" | 4 vCPU | 8192–30720 (крок 1024) |
"8192" | 8 vCPU | 16384–61440 (крок 4096) |
"16384" | 16 vCPU | 32768–122880 (крок 8192) |
Для більшості .NET Web API-сервісів достатньо "256" (0.25 vCPU) і "512" MB RAM — це найменш вартісна конфігурація Fargate. При високому RPS або інтенсивних обчисленнях рекомендовано стартувати з "512" / 1024 MB і коригувати на основі метрик CloudWatch.
app.MapHealthChecks("/health");
AWS Fargate vs EC2 Launch Types

Один із ключових вибір при налаштуванні ECS: як запускати Tasks? ECS підтримує два «launch types» — режими запуску, які принципово відрізняються підходом до управління інфраструктурою.
EC2 Launch Type
При використанні EC2 launch type ви самостійно управляєте EC2 інстансами, на яких запускаються контейнери. ECS розміщує Tasks на ваших інстансах, але відповідальність за самі інстанси — на вас: встановлення ECS Agent, оновлення ОС, вибір типу і розміру інстансу, масштабування парку машин.
Переваги EC2 launch type:
- Нижча вартість при стабільному, передбачуваному навантаженні (Reserved Instances, Savings Plans)
- Більш детальний контроль: GPU інстанси для ML, специфічні типи інстансів, Spot Instances для batch-задач
- Можливість запускати privileged контейнери та монтувати локальні томи
Недоліки: Ви платите за інстанс, навіть якщо він простоює. Потрібно управляти capacity, оновленнями ОС, патчами безпеки.
AWS Fargate — Serverless Containers
AWS Fargate — це serverless платформа для запуску контейнерів. При використанні Fargate launch type AWS повністю бере на себе управління інфраструктурою: ви не вибираєте, не запускаєте і не обслуговуєте жодного EC2 інстансу. Ви просто описуєте Task Definition (образ, CPU, RAM) — і Fargate запускає контейнер.
Принцип роботи Fargate: для кожного Task AWS виділяє ізольоване мікро-середовище з запитаними ресурсами. Task не знає про інші Tasks. Після зупинки Task — ресурси звільняються. Ви платите лише за час виконання Task (vCPU-годину та GB-годину RAM).
Переваги Fargate:
- Нуль операційного overhead: немає кластеру EC2 для управління
- Ізоляція на рівні завдання — кожен Task у власному мікро-VM
- Автоматичне масштабування від 0 до тисяч Tasks без попереднього планування capacity
- Ідеально для нерівномірного навантаження, мікросервісів та стартапів
Недоліки: Дорожче за EC2 для стабільного, постійного навантаження. Немає GPU-підтримки. Холодний старт повільніший (кілька секунд для підняття ізольованого середовища).
Fargate — коли обирати
- Мікросервісна архітектура
- Нерівномірне навантаження (пікові навантаження вдень, майже нуль вночі)
- Команда без DevOps-досвіду або маленька команда
- Batch-задачі, фонові воркери
- Стартапи та MVP
EC2 Launch Type — коли обирати
- Стабільне, передбачуване навантаження 24/7
- Потреба у GPU (ML моделі)
- Специфічні вимоги до типу інстансу
- Spot Instances для batch-обробки даних
- Велика компанія з виділеним DevOps-відділом
ECS Task Networking: awsvpc режим

Перш ніж розглядати мережевий режим awsvpc, необхідно сформувати чітке розуміння фундаментальної концепції AWS-інфраструктури — Amazon VPC, — без якої неможливо пояснити, яким чином ECS Tasks отримують мережеву ідентичність і як вони стають доступними ззовні.
Що таке Amazon VPC
Amazon Virtual Private Cloud (VPC) — це логічно ізольована віртуальна мережа всередині AWS, яку користувач визначає і якою повністю керує. Концептуально VPC відтворює традиційну корпоративну мережу, розміщену у власному дата-центрі: ви самостійно обираєте діапазон IP-адрес, розбиваєте мережу на сегменти, встановлюєте правила фільтрації трафіку — проте замість фізичного обладнання весь цей контроль здійснюється програмно.
Кожен AWS-акаунт при створенні отримує Default VPC у кожному регіоні — попередньо налаштовану мережу з адресним простором 172.31.0.0/16, готову до використання без жодних додаткових налаштувань. Саме цей Default VPC застосовуватиметься у практичному прикладі далі в розділі.
VPC охоплює весь регіон AWS (наприклад, eu-central-1), однак ресурси всередині VPC фізично розміщуються у конкретних Availability Zones (зонах доступності) — незалежних дата-центрах у межах регіону. Для розподілу ресурсів між зонами VPC ділиться на підмережі (Subnets).
AWS Region: eu-central-1 (Frankfurt)
└── VPC: 10.0.0.0/16 ← адресний простір: 65 536 IP-адрес
├── Subnet: 10.0.1.0/24 → Availability Zone eu-central-1a (256 адрес)
├── Subnet: 10.0.2.0/24 → Availability Zone eu-central-1b (256 адрес)
└── Subnet: 10.0.3.0/24 → Availability Zone eu-central-1c (256 адрес)
Публічні та приватні підмережі
Підмережі поділяються на два принципово різних типи залежно від наявності маршруту до Інтернету.
Публічна підмережа (Public Subnet) — підмережа, маршрутна таблиця якої містить запис, що направляє зовнішній трафік (0.0.0.0/0) до Internet Gateway (IGW). Internet Gateway — керований AWS компонент, що забезпечує двосторонній зв'язок між ресурсами VPC та публічним Інтернетом. Ресурс, розміщений у публічній підмережі і наділений публічною IP-адресою, є досяжним з Інтернету і сам здатний ініціювати вихідні з'єднання назовні.
Приватна підмережа (Private Subnet) — підмережа, маршрутна таблиця якої не містить маршруту до Internet Gateway. Ресурси у ній недоступні з Інтернету за будь-яких умов. Для вихідного трафіку (наприклад, завантаження Docker-образу з Amazon ECR або звернення до зовнішнього API) використовується NAT Gateway — керований AWS-сервіс, розміщений у публічній підмережі. NAT Gateway транслює приватну IP-адресу вихідного пакета у власну публічну Elastic IP, пропускаючи трафік назовні, проте залишаючи ресурси закритими для будь-яких вхідних з'єднань ззовні.
ENI — Elastic Network Interface
Elastic Network Interface (ENI) — це віртуальний мережевий адаптер у VPC. Фізичною аналогією слугує мережева карта (NIC) комп'ютера: ENI є тим інтерфейсом, через який ресурс AWS підключається до мережі, отримує приватну IP-адресу, і через який проходить увесь його мережевий трафік.
Кожен ENI характеризується такими атрибутами:
- Приватна IP-адреса — постійна адреса всередині VPC, що призначається з діапазону підмережі
- Публічна IP-адреса — необов'язкова; призначається лише для ресурсів у публічних підмережах при явному запиті
- Security Groups — набори правил фільтрації трафіку, прикріплені безпосередньо до ENI
Режим awsvpc та його значення для ECS
awsvpc — це мережевий режим Task Definition, при якому кожен запущений Task отримує власний виділений ENI і, відповідно, власну приватну IP-адресу всередині VPC. Цей режим є обов'язковим для Fargate і рекомендованим для EC2 launch type.
Дана модель принципово відрізняється від традиційного підходу, де всі контейнери на одному хості розділяють мережевий простір хостової машини і відрізняються лише номерами портів. У режимі awsvpc Task є повноправним мережевим ресурсом VPC — таким самим, як EC2-інстанс або RDS-база даних.
Практичні наслідки цього рішення:
- Власна IP-адреса кожного Task. ALB реєструє IP кожного Task як окремий target і направляє трафік безпосередньо, без додаткових рівнів трансляції.
- Security Groups на рівні Task. Правила фільтрації трафіку застосовуються окремо до кожного Task, що забезпечує точний контроль: наприклад, дозволити трафік на порт 8080 лише з конкретного ALB.
- Підтримка VPC Flow Logs. Весь мережевий трафік Tasks можна логувати та аналізувати на рівні мережі.
Публічний доступ до Fargate Task: assignPublicIp
Найпростіший спосіб надати Task доступ з Інтернету — розмістити його у публічній підмережі та встановити параметр assignPublicIp: ENABLED при запуску Service або окремого Task. У такому випадку AWS автоматично призначає ENI Task тимчасову публічну IP-адресу.
Internet
↓
Public IP: 3.64.x.x (тимчасова, змінюється при рестарті Task)
↓
ENI Task (awsvpc)
↓
.NET контейнер: порт 8080
Приватне розміщення Tasks і роль NAT Gateway
Якщо ECS Task розташований у приватній підмережі (рекомендована production-конфігурація), він не має публічного IP і залишається ізольованим від зовнішнього трафіку. Проте для коректного запуску Task вихідне з'єднання усе одно необхідне — щонайменше для завантаження Docker-образу з Amazon ECR в момент старту контейнера.
Для забезпечення вихідного трафіку з приватної підмережі в цій же VPC розміщується NAT Gateway — у публічній підмережі, де він має доступ до Internet Gateway. NAT Gateway транслює адресу Task у власну Elastic IP при проходженні трафіку назовні, не відкриваючи Task для вхідних з'єднань ззовні.
assignPublicIp: ENABLED — в такому разі NAT Gateway не потрібен, а трафік іде напряму через Internet Gateway. Врахуйте цей факт при проектуванні production-архітектури.Підсумкова схема адресації Tasks
VPC: 10.0.0.0/16
│
├── Public Subnet: 10.0.1.0/24 (AZ: eu-central-1a)
│ ├── Task 1 ENI → Private: 10.0.1.15 │ Public: 3.64.12.5 (my-api)
│ ├── Task 2 ENI → Private: 10.0.1.23 │ Public: 3.64.18.7 (my-api)
│ └── NAT Gateway (Elastic IP: 52.29.x.x)
│
└── Private Subnet: 10.0.2.0/24 (AZ: eu-central-1b)
├── Task 3 ENI → Private: 10.0.2.8 │ Public: — (my-api)
└── RDS ENI → Private: 10.0.2.50 │ Public: — (postgres)
Tasks у публічній підмережі доступні з Інтернету безпосередньо за публічним IP (прийнятно для тестування). Tasks у приватній підмережі досяжні лише через ALB або інші ресурси всередині VPC — що є стандартною production-моделлю.
ECS Service Auto Scaling

Auto Scaling — це автоматична зміна кількості запущених Tasks у відповідь на зміну навантаження. ECS Service Auto Scaling інтегрований з Amazon CloudWatch та Application Auto Scaling і підтримує три стратегії масштабування:
Target Tracking Scaling
Найпростіший і найрекомендованіший підхід для початку. Ви вказуєте цільове значення метрики — і AWS автоматично додає або видаляє Tasks для підтримання цього значення.
{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
},
"ScaleInCooldown": 300,
"ScaleOutCooldown": 60
}
Ця конфігурація каже: «Підтримуй середнє CPU-завантаження на рівні 70%. Якщо CPU зростає вище — додай Tasks (cooldown 60 сек). Якщо CPU падає нижче — видали Tasks (cooldown 300 сек)».
ScaleOutCooldown (60 сек) — пауза після масштабування вгору. Невеликий, бо при зростанні навантаження важливо швидко реагувати.
ScaleInCooldown (300 сек) — пауза після маштабування вниз. Більший, бо передчасне видалення Tasks при тимчасовому зниженні навантаження є контрпродуктивним.
Налаштування Target Tracking через AWS CLI:
# Крок 1: Зареєструйте ECS Service як scalable target.
# resource-id має формат: service/<cluster-name>/<service-name>
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--scalable-dimension ecs:service:DesiredCount \
--resource-id service/production/my-api \
--min-capacity 2 \
--max-capacity 20 \
--region eu-central-1
# Крок 2: Додайте Target Tracking Policy
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--scalable-dimension ecs:service:DesiredCount \
--resource-id service/production/my-api \
--policy-name cpu-target-70 \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
},
"ScaleInCooldown": 300,
"ScaleOutCooldown": 60
}' \
--region eu-central-1
--min-capacity 2 — для production-сервісу рекомендовано завжди підтримувати щонайменше 2 Tasks: це захищає від повного простою при падінні одного Task або під час rolling update. --max-capacity 20 — верхня межа захищає від неочікуваних витрат (наприклад, хиба у логіці політики, що змушує CPU зростати безмежно).Step Scaling та Scheduled Scaling
Step Scaling дозволяє задати різні кроки масштабування залежно від величини відхилення. Наприклад: CPU 70–80% → +1 Task, CPU 80–90% → +2 Tasks, CPU >90% → +4 Tasks.
Scheduled Scaling дозволяє масштабувати за розкладом — корисно, якщо навантаження передбачуване (наприклад, кожен день о 8:00 збільшувати мінімум до 5 Tasks).
Application Load Balancer для ECS

Application Load Balancer (ALB) — це Layer 7 (HTTP/HTTPS) балансувальник навантаження, який розподіляє вхідні запити між Tasks у ECS Service. Без ALB ваш застосунок буде доступний лише за приватною IP-адресою Task — що змінюється при кожному рестарті.
Як ALB інтегрується з ECS
Коли ECS Service запускає новий Task — він автоматично реєструє IP Task у Target Group ALB. Коли Task зупиняється — видаляє його. ALB завжди знає актуальний список здорових Tasks і направляє на них трафік.
Ланцюжок запиту:
Internet → Route 53 (DNS) → ALB (HTTPS:443) → Target Group → Tasks (HTTP:8080)
ALB виконує SSL termination: приймає HTTPS від клієнта, розшифровує і передає у Tasks по звичайному HTTP — Tasks не потрібно конфігурувати SSL.
Налаштування ALB для ECS через AWS CLI
# 0. Створіть сам ALB (публічний, розміщується в публічних підмережах)
ALB_ARN=$(aws elbv2 create-load-balancer \
--name my-api-alb \
--subnets subnet-aaa subnet-bbb \
--security-groups sg-alb-id \
--scheme internet-facing \
--type application \
--query "LoadBalancers[0].LoadBalancerArn" --output text \
--region eu-central-1)
echo "ALB ARN: $ALB_ARN"
# 1. Створіть Target Group (куди ALB направлятиме запити)
TG_ARN=$(aws elbv2 create-target-group \
--name my-api-tg \
--protocol HTTP \
--port 8080 \
--vpc-id vpc-0a1b2c3d4e5f \
--target-type ip \
--health-check-path /health \
--health-check-interval-seconds 30 \
--healthy-threshold-count 2 \
--unhealthy-threshold-count 3 \
--query "TargetGroups[0].TargetGroupArn" --output text \
--region eu-central-1)
echo "Target Group ARN: $TG_ARN"
# 1b. Додайте HTTP Listener до ALB (порт 80 → Target Group)
aws elbv2 create-listener \
--load-balancer-arn $ALB_ARN \
--protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=$TG_ARN \
--region eu-central-1
# 2. Під час створення ECS Service — прив'яжіть Target Group
aws ecs create-service \
--cluster production \
--service-name my-api \
--task-definition my-api:3 \
--desired-count 3 \
--launch-type FARGATE \
--load-balancers "targetGroupArn=$TG_ARN,containerName=my-api,containerPort=8080" \
--network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-zzz],assignPublicIp=DISABLED}"
Зверніть увагу: --target-type ip — це специфічно для Fargate та awsvpc режиму, де кожен Task має власний IP. ALB звертається до Task безпосередньо по IP, а не через порт на хості. Security Group ALB повинна дозволяти вхід на порт 80/443 з 0.0.0.0/0, а Security Group Tasks — вхід на порт 8080 лише з Security Group ALB.
Rolling Updates — оновлення без зупинки сервісу

Rolling update — це стратегія оновлення, яка дозволяє замінити стару версію застосунку новою без жодного часу простою (downtime). ECS Service виконує rolling update автоматично щоразу, коли ви змінюєте Task Definition.
Як це працює покроково:
Припустимо, у вас 4 Tasks з версією v1.2.3, і ви хочете оновитись до v1.3.0. Конфігурація: minimumHealthyPercent=50, maximumPercent=200.
- ALB продовжує направляти трафік на всі 4 Tasks (v1.2.3)
- ECS запускає 4 нових Tasks (v1.3.0) — тепер запущено 8 Tasks (200% від бажаних 4)
- ECS чекає, поки нові Tasks пройдуть health check і зареєструються в ALB
- ECS зупиняє 4 старих Tasks (v1.2.3) — залишається 4 Tasks (v1.3.0), 100%
- Оновлення завершено, ALB направляє трафік на v1.3.0
У будь-який момент процесу minimumHealthyPercent=50 гарантує, що щонайменше 2 Tasks (50% від 4) залишаються здоровими.
:latest.
Amazon EKS — огляд Kubernetes на AWS
Amazon Elastic Kubernetes Service (EKS) — це керований сервіс Kubernetes від AWS.
Він надає повноцінний Kubernetes кластер, де AWS бере на себе управління control plane (API Server, etcd, Controller Manager, Scheduler) — найскладнішою і найкритичнішою частиною Kubernetes.
Kubernetes і ECS — в чому різниця?
Якщо ECS — це «AWS-рідний» оркестратор, розроблений Amazon, то Kubernetes — це відкритий галузевий стандарт оркестрації контейнерів, розроблений Google і переданий у CNCF (Cloud Native Computing Foundation).
Amazon ECS
- Розроблений Amazon, нативно інтегрований з AWS
- Значно простіший в налаштуванні та управлінні
- Менше концепцій для вивчення
- Менша гнучкість і екосистема
- Ідеальний вибір, якщо ви не плануєте мігрувати між провайдерами
Amazon EKS (Kubernetes)
- Відкритий стандарт, мультихмарний (AWS, Azure, GCP, on-premises)
- Значно потужніша екосистема (Helm, Istio, ArgoCD, Prometheus)
- Складніший: потрібно розуміти Pods, Deployments, Services, Ingress, RBAC тощо
- Вища гнучкість та контроль
- Краще для великих команд із K8s-досвідом
Коли обирати EKS?
- Команда вже має Kubernetes-досвід
- Потреба у мультихмарній або гібридній архітектурі
- Складні networking вимоги (service mesh, mutual TLS)
- Розширена екосистема: Helm charts, GitOps (ArgoCD/Flux), observability стек (Prometheus + Grafana)
- Потреба у більшому контролі над scheduling (node affinity, tolerations, pod disruption budgets)
ECS vs EKS vs Fargate: підсумкова таблиця
| Характеристика | ECS + EC2 | ECS + Fargate | EKS + EC2 | EKS + Fargate |
|---|---|---|---|---|
| Управління серверами | Ви | AWS | Ви | AWS |
| Управління K8s control plane | — | — | AWS | AWS |
| Складність налаштування | Середня | Низька | Висока | Висока |
| Гнучкість | Середня | Низька | Висока | Середня |
| Вартість | Нижча | Вища | Нижча | Вища |
| Мультихмарність | ❌ | ❌ | ✅ | ✅ |
| Рекомендовано для | Досвідчені DevOps | Малі команди, MVP | Великі команди з K8s | Складні K8s, без серверів |

Environment Variables у ECS Task Definitions — специфіка .NET
Проблема: конфігурація у контейнерному середовищі
.NET традиційно зберігає конфігурацію в appsettings.json. У Docker-контейнері це проблема: конфіг «запечений» у образ і не може відрізнятись між dev та production без перезбірки образу.
Правильний підхід: дотримання принципу 12-Factor App — конфігурація зберігається у змінних середовища (environment variables), які передаються контейнеру при запуску.
ASP.NET Core автоматично читає змінні середовища через IConfiguration — жодних додаткових налаштувань не потрібно. Ієрархічні ключі з appsettings.json відповідають змінним з подвійним підкресленням __:
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "будь-яке значення"
},
"Feature": {
"MaxItems": 100
}
}
Відповідні змінні середовища:
ConnectionStrings__DefaultConnection=Host=prod-db;Database=mydb;...
Feature__MaxItems=50
Де зберігати секрети в ECS
У Task Definition є два способи передачі конфігурації:
environment — для незасекреченої конфігурації:
"environment": [
{"name": "ASPNETCORE_ENVIRONMENT", "value": "Production"},
{"name": "Feature__MaxItems", "value": "100"},
{"name": "Logging__LogLevel__Default", "value": "Warning"}
]
secrets — для секретів (паролі, API ключі, connection strings). Значення не зберігається у Task Definition у відкритому вигляді — ECS агент завантажує секрет з AWS Secrets Manager або SSM Parameter Store під час запуску:
"secrets": [
{
"name": "ConnectionStrings__DefaultConnection",
"valueFrom": "arn:aws:secretsmanager:eu-central-1:123456789012:secret:prod/db-conn"
},
{
"name": "ExternalApi__ApiKey",
"valueFrom": "/prod/external-api/key"
}
]
У .NET-коді все виглядає однаково через IConfiguration:
// Program.cs — нічого особливого, стандартна конфігурація
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var apiKey = builder.Configuration["ExternalApi:ApiKey"];
Довідник: Amazon ECR — повна документація функцій

Lifecycle Policies — автоматичне управління версіями образів
Lifecycle Policy — це правило, яке ECR виконує автоматично для видалення старих або непотрібних образів. Без lifecycle policy репозиторій безмежно росте, і ви платите за зберігання тисяч застарілих образів.
Навіщо потрібні: кожен CI/CD pipeline при кожному commit'і завантажує новий образ. За місяць це 30+ образів на репозиторій. Lifecycle policy забезпечує автоматичне очищення.
Lifecycle policy складається з правил (rules) з пріоритетами. Кожне правило описує: які образи шукати (за тегом або без тегу) та що з ними робити (видалити після N днів або зберегти лише N останніх).
Типовий сценарій: зберігати лише 10 tagged версій і видаляти untagged після 1 дня:
{
"rules": [
{
"rulePriority": 1,
"description": "Видаляти untagged образи через 1 день",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 1
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "Зберігати лише 10 останніх tagged образів",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["v"],
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": { "type": "expire" }
}
]
}
Застосування через AWS CLI:
aws ecr put-lifecycle-policy \
--repository-name my-api \
--lifecycle-policy-text file://lifecycle-policy.json \
--region eu-central-1
Через AWS Console: ECR → Repositories → my-api → вкладка «Lifecycle policy» → Create rule → задайте умови у формі → Save.
my-api → Lifecycle policy → Test rules → ECR покаже список образів, які підпадають під правила, без реального видалення.
Image Scanning — автоматичний аудит безпеки
ECR інтегрований з Amazon Inspector і може автоматично сканувати кожен завантажений образ на відомі CVE (Common Vulnerabilities and Exposures) — бази відомих вразливостей у ПЗ.
Два режими сканування:
Basic Scanning — безкоштовне сканування на основі відкритої бази даних CVE. Виконується один раз при push (якщо scanOnPush=true) або вручну.
Enhanced Scanning (Amazon Inspector) — платне безперервне сканування. Повторно аналізує образи при появі нових CVE у базі — навіть якщо образ давно завантажений.
Перегляд результатів через CLI:
# Переглянути результати останнього сканування
aws ecr describe-image-scan-findings \
--repository-name my-api \
--image-id imageTag=v1.2.3 \
--region eu-central-1
# Приклад відповіді:
# {
# "imageScanFindings": {
# "findings": [
# {
# "name": "CVE-2023-12345",
# "severity": "HIGH",
# "description": "Buffer overflow in libssl...",
# "uri": "https://nvd.nist.gov/vuln/detail/CVE-2023-12345"
# }
# ],
# "findingSeverityCounts": {"HIGH": 1, "MEDIUM": 3, "LOW": 12}
# }
# }
Запустити сканування вручну:
aws ecr start-image-scan \
--repository-name my-api \
--image-id imageTag=v1.2.3 \
--region eu-central-1

Image Tag Mutability — захист від перезапису тегів
Tag Mutability визначає, чи можна перезаписати тег образу, який вже існує.
- MUTABLE (за замовчуванням) — тег
:latestабо:v1.0.0можна перезаписати новим образом. Зручно, але небезпечно: CI/CD міг завантажити інший образ під тим самим тегом. - IMMUTABLE — після першого завантаження тег не можна перезаписати. Забезпечує повну відтворюваність: образ
:v1.0.0завжди залишається тим самим.
Рекомендація: використовуйте IMMUTABLE для production репозиторіїв. Для тегування використовуйте git commit SHA або семантичне версіонування, а не :latest.
# Змінити mutability для існуючого репозиторію
aws ecr put-image-tag-mutability \
--repository-name my-api \
--image-tag-mutability IMMUTABLE \
--region eu-central-1

Cross-Account Access — доступ між AWS акаунтами
У multi-account архітектурі (окремі акаунти для dev/staging/production) ECR може бути в одному акаунті, а ECS кластери — в інших. Для цього налаштовується Repository Policy — IAM Policy на рівні репозиторію, яка дозволяє зовнішнім акаунтам завантажувати образи.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPullFromProductionAccount",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::PRODUCTION_ACCOUNT_ID:root"
},
"Action": ["ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:BatchCheckLayerAvailability"]
}
]
}
# Застосувати Repository Policy
aws ecr set-repository-policy \
--repository-name my-api \
--policy-text file://repo-policy.json \
--region eu-central-1

ECR Replication — реплікація між регіонами
Якщо ваші ECS кластери розташовані у кількох регіонах (наприклад, EU та US), образи варто реплікувати в кожен регіон — для швидкого завантаження без cross-region трафіку.
# Налаштувати реплікацію образів у інший регіон
aws ecr put-replication-configuration \
--replication-configuration '{
"rules": [{
"destinations": [{"region": "us-east-1", "registryId": "ACCOUNT_ID"}],
"repositoryFilters": [{"filter": "my-api", "filterType": "PREFIX_MATCH"}]
}]
}' \
--region eu-central-1
Довідник: Amazon ECS — повна документація функцій

ECS Exec — підключення до контейнера для дебагінгу
ECS Exec — це функція, яка дозволяє запустити інтерактивну сесію (bash або будь-яку команду) всередині запущеного Task. Аналог kubectl exec у Kubernetes або docker exec для локальних контейнерів.
Навіщо потрібно: коли контейнер поводиться дивно у production — перевірити змінні середовища, пінгувати БД зсередини контейнера, переглянути файли.
Підготовка — IAM права для ECS Exec:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel"
],
"Resource": "*"
}
]
}
Додайте цю Policy до Task Role (не Execution Role).
Увімкнення ECS Exec для Service:
# Увімкнути execute-command при оновленні сервісу
aws ecs update-service \
--cluster my-cluster \
--service my-api-service \
--enable-execute-command \
--region eu-central-1
# Підключитися до контейнера у запущеному Task
TASK_ARN="arn:aws:ecs:eu-central-1:123456789012:task/my-cluster/abc123"
aws ecs execute-command \
--cluster my-cluster \
--task $TASK_ARN \
--container my-api \
--interactive \
--command "/bin/bash" \
--region eu-central-1
- ECS → Clusters →
my-cluster→ Tasks - Оберіть потрібний Task → натисніть «Execute command»
- Оберіть контейнер → введіть команду (наприклад
/bin/bash) → Execute - Відкриється термінальна сесія прямо в браузері через AWS CloudShell
session-manager-plugin), Task Role з SSM правами (вище), enableExecuteCommand: true у Service.
Deployment Circuit Breaker — автоматичний відкат при збоях
Circuit Breaker — це механізм, який автоматично зупиняє rolling deployment і відкочується до попередньої версії, якщо нові Tasks не проходять health check.
Без Circuit Breaker: якщо новий образ має баг і Tasks не запускаються — ECS нескінченно намагається їх запустити. Deployment «зависає», стара версія поступово видаляється — сервіс деградує.
З Circuit Breaker: ECS підраховує, скільки Tasks підряд не вдалося запустити. Якщо досягнуто порогу (за замовчуванням 10 провальних Tasks) — deployment зупиняється і автоматично відкочується до попередньої Task Definition revision.
# Увімкнути Circuit Breaker при оновленні Service
aws ecs update-service \
--cluster my-cluster \
--service my-api-service \
--deployment-configuration '{
"deploymentCircuitBreaker": {
"enable": true,
"rollback": true
},
"maximumPercent": 200,
"minimumHealthyPercent": 50
}' \
--region eu-central-1
Через Console: ECS → Services → Update → розділ «Deployment options» → ✅ Circuit breaker → ✅ Rollback on failure.
rollback: true у production. Це забезпечує автоматичний захист від деплою зламаного коду.
Service Connect — внутрішній service discovery
ECS Service Connect — це вбудований механізм service discovery та load balancing для комунікації між мікросервісами всередині одного кластера. Замість того, щоб налаштовувати AWS Cloud Map або ALB для internal трафіку, Service Connect робить це автоматично.
Сценарій: OrderService потребує звертатись до UserService. З Service Connect — UserService доступний просто за назвою user-service:8080 без жодного DNS-налаштування.
Налаштування Service Connect у Task Definition:
"serviceConnectConfiguration": {
"enabled": true,
"namespace": "my-app",
"services": [{
"portName": "http",
"discoveryName": "user-service",
"clientAliases": [{
"port": 8080,
"dnsName": "user-service"
}]
}]
}
Після цього OrderService може звертатись до http://user-service:8080/api/users — ECS автоматично проксіює запит на відповідний Task через Envoy proxy, вбудований у кожен Task.

Scheduled Tasks — запуск контейнерів за розкладом
ECS підтримує запуск одноразових Tasks за розкладом через інтеграцію з Amazon EventBridge Scheduler. Це ідеально для batch-задач, щоденних звітів, очищення даних.
Приклад: запускати контейнер щодня о 02:00 UTC:
# Крок 1: Створіть Trust Policy файл для IAM Role
cat > /tmp/eb-ecs-trust.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "scheduler.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF
# Крок 2: Створіть IAM Role
aws iam create-role \
--role-name EventBridgeECSRole \
--assume-role-policy-document file:///tmp/eb-ecs-trust.json
# Крок 3: Додайте інлайн-політику з правами ecs:RunTask та iam:PassRole
aws iam put-role-policy \
--role-name EventBridgeECSRole \
--policy-name ECSRunTaskPolicy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ecs:RunTask",
"Resource": "arn:aws:ecs:eu-central-1:123456789012:task-definition/cleanup-task:*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole"
}
]
}'
# Крок 4: Створіть scheduled task
aws scheduler create-schedule \
--name "daily-cleanup" \
--schedule-expression "cron(0 2 * * ? *)" \
--flexible-time-window '{"Mode": "OFF"}' \
--target '{
"Arn": "arn:aws:ecs:eu-central-1:123456789012:cluster/my-cluster",
"RoleArn": "arn:aws:iam::123456789012:role/EventBridgeECSRole",
"EcsParameters": {
"TaskDefinitionArn": "arn:aws:ecs:eu-central-1:123456789012:task-definition/cleanup-task:1",
"LaunchType": "FARGATE",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": ["subnet-xxx"],
"SecurityGroups": ["sg-xxx"],
"AssignPublicIp": "DISABLED"
}
}
}
}' \
--region eu-central-1
- Відкрийте Amazon EventBridge → Schedules → Create schedule
- Name:
daily-cleanup - Schedule pattern: Recurring → Cron:
0 2 * * ? * - Target: AWS API → ECS → RunTask
- Оберіть кластер, Task Definition, підмережі, Security Group
- Натисніть «Create schedule»

Capacity Providers — управління ресурсами кластера
Capacity Provider визначає, де і як ECS виділяє ресурси для запуску Tasks. Для Fargate доступні два built-in провайдери:
- FARGATE — стандартний Fargate. Підходить для більшості задач.
- FARGATE_SPOT — Tasks запускаються на AWS Spot Capacity. Знижка до 70%, але Tasks можуть бути примусово зупинені з 2-хвилинним попередженням. Ідеально для batch-задач і stateless воркерів.
Налаштування Capacity Provider Strategy:
# Налаштувати кластер на використання Fargate Spot
aws ecs put-cluster-capacity-providers \
--cluster my-cluster \
--capacity-providers FARGATE FARGATE_SPOT \
--default-capacity-provider-strategy \
"capacityProvider=FARGATE_SPOT,weight=3,base=0" \
"capacityProvider=FARGATE,weight=1,base=1" \
--region eu-central-1
Ця конфігурація: 1 Task завжди на FARGATE (base=1), потім 75% нових Tasks — FARGATE_SPOT, 25% — FARGATE. Економія 50–60% при прийнятному рівні надійності.

Container Insights — розширений моніторинг
CloudWatch Container Insights — це dashboard з деталізованими метриками для ECS: CPU та Memory по кожному Task, Service та Cluster; кількість Task failures; network I/O; storage I/O.
Увімкнення для кластера:
aws ecs update-cluster-settings \
--cluster my-cluster \
--settings name=containerInsights,value=enabled \
--region eu-central-1
Через Console: ECS → Clusters → my-cluster → Update cluster → ✅ Use Container Insights.
Після увімкнення: CloudWatch → Container Insights → оберіть ECS Clusters — ви побачите автоматично згенеровані дашборди з графіками CPU, Memory, Task count по кожному сервісу.

Практичний приклад: .NET Web API на ECS Fargate від А до Я
Тепер застосуємо всю теорію та пройдемо повний шлях: від порожньої папки до працюючого API у хмарі.
Крок 1: Створення .NET проєкту
Відкрийте термінал і створіть новий проєкт:
Відредагуйте Program.cs — повний вміст файлу:
var builder = WebApplication.CreateBuilder(args);
// Реєструємо Health Check сервіс
builder.Services.AddHealthChecks();
var app = builder.Build();
// Головний ендпоінт — версія відповіді береться зі змінної середовища
var version = Environment.GetEnvironmentVariable("APP_VERSION") ?? "1.0.0";
app.MapGet("/", () => new
{
message = "Hello from ECS Fargate!",
version = version,
environment = app.Environment.EnvironmentName,
timestamp = DateTime.UtcNow
});
// Health check ендпоінт — ECS використовує його для перевірки стану
app.MapHealthChecks("/health");
app.Run();
Перевірте, що проєкт збирається локально:
Крок 2: Dockerfile та .dockerignore
Створіть файл Dockerfile у корені проєкту EcsLabApi/:
# ─── STAGE 1: BUILD ───────────────────────────────────────────
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Копіюємо .csproj і відновлюємо залежності (layer cache!)
COPY ["EcsLabApi.csproj", "."]
RUN dotnet restore "EcsLabApi.csproj"
# Копіюємо решту коду і публікуємо Release-версію
COPY . .
RUN dotnet publish "EcsLabApi.csproj" -c Release -o /app/publish --no-restore
# ─── STAGE 2: RUNTIME ─────────────────────────────────────────
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
# ASP.NET Core слухає на порту 8080 (не 5000!)
ENV ASPNETCORE_URLS=http://+:8080
# Встановлюємо curl — він потрібен для health check команди в ECS Task Definition.
# Базовий aspnet-образ мінімальний і curl не містить за замовчуванням.
# Без цього рядка ECS health check завжди повертає "unhealthy".
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
# Копіюємо лише опублікований артефакт — SDK і вихідний код не потрапляють у образ
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "EcsLabApi.dll"]
curl відсутній у базовому aspnet образі. Якщо не встановити його явно — ECS health check (curl -f http://localhost:8080/health || exit 1) завжди завершуватиметься з кодом 127 («command not found»), і Task буде позначений як UNHEALTHY та циклічно перезапускатиметься.Створіть файл .dockerignore:
**/bin/
**/obj/
.git
.gitignore
.vs/
.rider/
Dockerfile
.dockerignore
**/appsettings.Development.json
Зберіть образ і перевірте локально:
Крок 3: Створення ECR репозиторію
# Спочатку дізнайтесь ваш Account ID — він знадобиться в наступних кроках
aws sts get-caller-identity --query Account --output text
# Виведе: 123456789012 ← запам'ятайте це число, це ВАШ реальний AWS Account ID
# Створіть репозиторій у ECR
# eu-central-1 — змініть на ваш регіон, якщо потрібно
aws ecr create-repository \
--repository-name ecs-lab-api \
--region eu-central-1 \
--image-scanning-configuration scanOnPush=true
Успішна відповідь матиме вигляд:
{
"repository": {
"repositoryArn": "arn:aws:ecr:eu-central-1:123456789012:repository/ecs-lab-api",
"registryId": "123456789012",
"repositoryName": "ecs-lab-api",
"repositoryUri": "123456789012.dkr.ecr.eu-central-1.amazonaws.com/ecs-lab-api"
}
}
Значення repositoryUri — це адреса вашого репозиторію. Скопіюйте її, вона знадобиться у наступному кроці.
- Відкрийте AWS Console → у рядку пошуку введіть ECR → оберіть Elastic Container Registry
- Переконайтесь, що у верхньому правому куті обрано регіон eu-central-1 (Frankfurt)
- Натисніть «Create repository»
- Заповніть форму:
- Visibility: Private (за замовчуванням)
- Repository name:
ecs-lab-api - Image tag mutability: Mutable
- Image scan settings: ✅ Scan on push
- Натисніть «Create repository»
- У списку репозиторіїв знайдіть
ecs-lab-api— скопіюйте значення в колонці URI (виглядає як123456789012.dkr.ecr.eu-central-1.amazonaws.com/ecs-lab-api)
Крок 4: Push образу в ECR
Виконайте команди послідовно. Замініть 123456789012 на ваш реальний Account ID (отриманий на попередньому кроці):
# Змінні — підставте ваші реальні значення
ACCOUNT_ID="123456789012" # ← ЗАМІНІТЬ на ваш Account ID
REGION="eu-central-1" # ← змініть на ваш регіон
REPO_URI="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/ecs-lab-api"
echo "Буде використано репозиторій: $REPO_URI"
# Аутентифікуємось у ECR — отримуємо тимчасовий токен і передаємо docker login
aws ecr get-login-password --region $REGION | \
docker login --username AWS --password-stdin "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com"
Для push через консоль скористайтесь вбудованими інструкціями ECR:
- ECR → Repositories →
ecs-lab-api - Натисніть кнопку «View push commands» у правому верхньому куті
- У вікні, що відкрилось, ви побачите чотири готові команди з вашим реальним Account ID та регіоном — просто скопіюйте і виконайте їх послідовно у терміналі
# Тегуємо локальний образ адресою ECR репозиторію
docker tag ecs-lab-api:v1.0.0 $REPO_URI:v1.0.0
# Завантажуємо образ у ECR
docker push $REPO_URI:v1.0.0
Перевірте результат — образ має з'явитись у консолі:
- ECR → Repositories →
ecs-lab-api→ вкладка «Images» - Ви побачите образ з тегом
v1.0.0та статус сканування
Крок 5: Створення ECS Cluster
- Відкрийте ECS (Elastic Container Service) у AWS Console
- У лівому меню → Clusters → Create cluster
- Заповніть:
- Cluster name:
ecs-lab-cluster - Infrastructure: ✅ AWS Fargate (зніміть галочку з Amazon EC2 instances)
- Моніторинг: ✅ Use Container Insights (рекомендовано — дасть метрики CPU/Memory)
- Cluster name:
- Натисніть «Create» — кластер створюється за ~30 секунд
# Створіть кластер із підтримкою Fargate та вмікненими Container Insights
aws ecs create-cluster \
--cluster-name ecs-lab-cluster \
--capacity-providers FARGATE \
--settings name=containerInsights,value=enabled \
--region eu-central-1
Крок 6: Створення IAM Role для ECS Task Execution
ECS потребує IAM Role, щоб завантажити образ з ECR та записувати логи. Це стандартна роль — її потрібно створити один раз.
- Відкрийте IAM → Roles → Create role
- Trusted entity type: AWS service
- Use case: знайдіть і оберіть Elastic Container Service Task
- Натисніть Next
- У пошуку политик введіть
AmazonECSTaskExecutionRolePolicy→ поставте ✅ - Натисніть Next → Role name:
ecsTaskExecutionRole→ Create role
# Крок 6a: Створіть Trust Policy файл
cat > /tmp/ecs-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ecs-tasks.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
EOF
# Крок 6b: Створіть роль
aws iam create-role \
--role-name ecsTaskExecutionRole \
--assume-role-policy-document file:///tmp/ecs-trust-policy.json
# Крок 6c: Прикріпіть стандартну policy
aws iam attach-role-policy \
--role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Крок 7: Створення Task Definition
Важливо: у полі image та executionRoleArn підставте ваші реальні значення.
- ECS → Task definitions → Create new task definition
- Task definition family:
ecs-lab-api - Infrastructure requirements:
- Launch type: ✅ AWS Fargate
- OS/Architecture: Linux/X86_64
- CPU: 0.25 vCPU (.25)
- Memory: 0.5 GB (512 MB)
- Task roles:
- Task role: залиште пустим (наш API не звертається до інших AWS сервісів)
- Task execution role:
ecsTaskExecutionRole
- Container — 1:
- Name:
ecs-lab-api - Image URI:
ВАШ_ACCOUNT_ID.dkr.ecr.eu-central-1.amazonaws.com/ecs-lab-api:v1.0.0(скопіюйте URI з ECR → Repositories → ecs-lab-api → Images → тег v1.0.0) - Container port:
8080, Protocol: TCP - Environment variables → Add:
- Key:
ASPNETCORE_ENVIRONMENT, Value:Production - Key:
APP_VERSION, Value:1.0.0
- Key:
- Health check command:
CMD-SHELL, curl -f http://localhost:8080/health || exit 1 - Health check intervals: Interval: 30s, Timeout: 5s, Start period: 10s, Retries: 3
- Name:
- Logging: ✅ Use log collection → awslogs, log group
/ecs/ecs-lab-api - Натисніть «Create»
# Підставте ВАШ РЕАЛЬНИЙ ACCOUNT_ID
ACCOUNT_ID="123456789012" # ← ЗАМІНІТЬ!
REGION="eu-central-1"
# Збережіть Task Definition у файл
cat > /tmp/task-def.json << EOF
{
"family": "ecs-lab-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskExecutionRole",
"containerDefinitions": [{
"name": "ecs-lab-api",
"image": "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/ecs-lab-api:v1.0.0",
"portMappings": [{"containerPort": 8080, "protocol": "tcp"}],
"environment": [
{"name": "ASPNETCORE_ENVIRONMENT", "value": "Production"},
{"name": "APP_VERSION", "value": "1.0.0"}
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30, "timeout": 5, "retries": 3, "startPeriod": 10
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/ecs-lab-api",
"awslogs-region": "${REGION}",
"awslogs-stream-prefix": "ecs",
"awslogs-create-group": "true"
}
}
}]
}
EOF
aws ecs register-task-definition \
--cli-input-json file:///tmp/task-def.json \
--region $REGION
Крок 8: Визначення Security Group та підмережі
Перед запуском Service потрібно знати ID вашої VPC та підмереж. Fargate Tasks будуть запускатись у них.
# Знайдіть ID вашого default VPC
aws ec2 describe-vpcs \
--filters "Name=isDefault,Values=true" \
--query "Vpcs[0].VpcId" \
--output text --region eu-central-1
# Виведе щось на кшталт: vpc-0a1b2c3d4e5f67890
# ← ЗАПИШІТЬ це значення, знадобиться далі
# Знайдіть підмережі у цьому VPC (потрібні мінімум 2 у різних AZ)
aws ec2 describe-subnets \
--filters "Name=defaultForAz,Values=true" \
--query "Subnets[*].{ID:SubnetId,AZ:AvailabilityZone}" \
--output table --region eu-central-1
# Виведе таблицю підмереж — запишіть 2 ID, наприклад:
# subnet-0a1b2c3d eu-central-1a
# subnet-1b2c3d4e eu-central-1b
Створіть Security Group для Tasks:
VPC_ID="vpc-0a1b2c3d4e5f67890" # ← ЗАМІНІТЬ на ваш реальний VPC ID
# Створіть Security Group
SG_ID=$(aws ec2 create-security-group \
--group-name "ecs-lab-sg" \
--description "Security group for ECS lab tasks" \
--vpc-id $VPC_ID \
--region eu-central-1 \
--query GroupId --output text)
echo "Security Group ID: $SG_ID" # ← ЗАПИШІТЬ цей ID
# Дозвольте вхідний трафік на порт 8080
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 8080 \
--cidr 0.0.0.0/0 \
--region eu-central-1
Знайдіть VPC та підмережі:
- Відкрийте VPC (через пошук у консолі)
- Your VPCs → знайдіть рядок з колонкою "Default VPC: Yes" → скопіюйте VPC ID
- Subnets → відфільтруйте за вашим VPC ID → запишіть ID двох підмереж у різних AZ
Створіть Security Group:
- VPC → Security groups → Create security group
- Name:
ecs-lab-sg, VPC: оберіть ваш default VPC - Inbound rules → Add rule:
- Type: Custom TCP, Port: 8080, Source: Anywhere-IPv4 (0.0.0.0/0)
- Натисніть Create → скопіюйте Security group ID
0.0.0.0/0 (доступ з усього Інтернету) прийнятне лише для цьої навчальної лабораторної роботи, де Tasks мають публічний IP і немає ALB. У production-середовищі Security Group Tasks повинна дозволяти вхідний трафік на порт 8080 виключно з Security Group вашого ALB, а не з усього Інтернету:# Правильне production-правило для Security Group Tasks:
# Source: sg-alb-id (а не 0.0.0.0/0)
aws ec2 authorize-security-group-ingress \
--group-id $TASKS_SG_ID \
--protocol tcp \
--port 8080 \
--source-group $ALB_SG_ID \
--region eu-central-1
Крок 9: Запуск ECS Service
- ECS → Clusters →
ecs-lab-cluster→ вкладка Services → Create - Environment:
- Compute options: Launch type → FARGATE
- Platform version: LATEST
- Deployment configuration:
- Application type: Service
- Task definition Family:
ecs-lab-api, Revision: LATEST - Service name:
ecs-lab-service - Desired tasks: 2
- Networking:
- VPC: ваш default VPC
- Subnets: оберіть 2 підмережі у різних AZ
- Security group:
ecs-lab-sg(створений вище) - Public IP: Turn on (для цієї лабораторної роботи)
- Натисніть «Create»
# ЗАМІНІТЬ усі значення на реальні з попередніх кроків!
ACCOUNT_ID="123456789012" # ← ваш Account ID
REGION="eu-central-1"
SUBNET_1="subnet-0a1b2c3d" # ← ваш Subnet ID 1
SUBNET_2="subnet-1b2c3d4e" # ← ваш Subnet ID 2
SG_ID="sg-0a1b2c3d4e5f67890" # ← ваш Security Group ID
aws ecs create-service \
--cluster ecs-lab-cluster \
--service-name ecs-lab-service \
--task-definition ecs-lab-api \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={
subnets=[$SUBNET_1,$SUBNET_2],
securityGroups=[$SG_ID],
assignPublicIp=ENABLED
}" \
--region $REGION
Після запуску зачекайте 1–2 хвилини поки Tasks перейдуть у стан RUNNING.
Крок 10: Перевірка роботи застосунку
Знайдіть публічну IP-адресу одного з Tasks:
- ECS → Clusters →
ecs-lab-cluster→ вкладка Tasks - Натисніть на будь-який Task у стані
RUNNING - У розділі Network → скопіюйте Public IP
- Відкрийте у браузері або через curl
# Отримайте список Tasks
TASK_ARN=$(aws ecs list-tasks \
--cluster ecs-lab-cluster \
--region eu-central-1 \
--query "taskArns[0]" --output text)
# Знайдіть ENI прикріплений до Task
ENI_ID=$(aws ecs describe-tasks \
--cluster ecs-lab-cluster \
--tasks $TASK_ARN \
--region eu-central-1 \
--query "tasks[0].attachments[0].details[?name==\`networkInterfaceId\`].value" \
--output text)
# Отримайте публічний IP
PUBLIC_IP=$(aws ec2 describe-network-interfaces \
--network-interface-ids $ENI_ID \
--region eu-central-1 \
--query "NetworkInterfaces[0].Association.PublicIp" \
--output text)
echo "Public IP: $PUBLIC_IP"
Перевірте, що API відповідає:
Крок 11: Rolling Update — деплой нової версії
Змінимо версію API і задеплоїмо оновлення без зупинки сервісу.
Оновіть Program.cs — змініть значення за замовчуванням:
var version = Environment.GetEnvironmentVariable("APP_VERSION") ?? "1.1.0";
Зберіть і завантажте новий образ:
ACCOUNT_ID="123456789012" # ← ваш Account ID
REGION="eu-central-1"
REPO_URI="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/ecs-lab-api"
docker build -t ecs-lab-api:v1.1.0 .
docker tag ecs-lab-api:v1.1.0 $REPO_URI:v1.1.0
docker push $REPO_URI:v1.1.0
Оновіть Task Definition та запустіть Rolling Update:
- ECS → Task definitions →
ecs-lab-api→ Create new revision - Знайдіть поле Image URI у розділі Container → змініть тег
:v1.0.0на:v1.1.0 - Також змініть Environment variable
APP_VERSIONна1.1.0 - Натисніть «Create» → з'явиться revision 2
- ECS → Clusters →
ecs-lab-cluster→ Services →ecs-lab-service→ Update - Task definition: оберіть
ecs-lab-api:2(нова revision) - Натисніть «Update»
- Перейдіть на вкладку Deployments — спостерігайте як нові Tasks запускаються (Running: 2→4→2)
ACCOUNT_ID="123456789012" # ← ваш Account ID
REGION="eu-central-1"
# Зареєструйте нову Task Definition з оновленим образом
aws ecs register-task-definition \
--family ecs-lab-api \
--network-mode awsvpc \
--requires-compatibilities FARGATE \
--cpu 256 --memory 512 \
--execution-role-arn "arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskExecutionRole" \
--container-definitions '[{
"name": "ecs-lab-api",
"image": "'"${ACCOUNT_ID}"'.dkr.ecr.'"${REGION}"'.amazonaws.com/ecs-lab-api:v1.1.0",
"portMappings": [{"containerPort": 8080}],
"environment": [
{"name": "ASPNETCORE_ENVIRONMENT", "value": "Production"},
{"name": "APP_VERSION", "value": "1.1.0"}
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30, "timeout": 5, "retries": 3, "startPeriod": 10
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/ecs-lab-api",
"awslogs-region": "'"${REGION}"'",
"awslogs-stream-prefix": "ecs",
"awslogs-create-group": "true"
}
}
}]' \
--region $REGION
# Оновіть Service — він автоматично визначить latest revision
aws ecs update-service \
--cluster ecs-lab-cluster \
--service ecs-lab-service \
--task-definition ecs-lab-api \
--region $REGION
Поки йде оновлення, виконуйте запити до API — жодного збою не буде:
Ви побачите, як відповіді поступово переходять з 1.0.0 на 1.1.0 — без жодної помилки.

Крок 12: Перегляд логів у CloudWatch
- Відкрийте CloudWatch → Log groups →
/ecs/ecs-lab-api - Оберіть один із Log Streams — ви побачите логи запитів у реальному часі
- Натисніть «Start tailing» для live-режиму
# Переглянути останні логи у режимі постійного оновлення
aws logs tail /ecs/ecs-lab-api \
--follow \
--format short \
--region eu-central-1
10.0.1.47 у логах — це IP ALB, який виконує health check. Кожен Task пише логи у окремий Log Stream (ім'я стріму — це ecs/<родина>/<task-id>). При рестарті Task створюється новий стрім, тому історія логів попередньої інстанції завжди доступна в CloudWatch.Крок 13: ОБОВ'ЯЗКОВО — Очищення ресурсів
Порядок видалення важливий — ресурси мають залежності між собою:
- ECS → Clusters →
ecs-lab-cluster→ Services →ecs-lab-service→ Delete service- ✅ Force delete service → Delete
- ECS → Clusters →
ecs-lab-cluster→ Delete cluster → підтвердіть - ECR → Repositories →
ecs-lab-api→ Delete repository- ✅ Delete all images in this repository → підтвердіть назву → Delete
- CloudWatch → Log groups →
/ecs/ecs-lab-api→ Actions → Delete log group(s) - VPC → Security groups →
ecs-lab-sg→ Actions → Delete security group - IAM → Roles →
ecsTaskExecutionRole→ Delete (лише якщо ви більше не будете використовувати ECS)
REGION="eu-central-1"
SG_ID="sg-0a1b2c3d4e5f67890" # ← ваш Security Group ID
# 1. Зупиніть Service (desired count → 0, потім видаліть)
aws ecs update-service --cluster ecs-lab-cluster --service ecs-lab-service --desired-count 0 --region $REGION
aws ecs delete-service --cluster ecs-lab-cluster --service ecs-lab-service --force --region $REGION
# 2. Видаліть кластер
aws ecs delete-cluster --cluster ecs-lab-cluster --region $REGION
# 3. Видаліть образи в ECR та репозиторій
aws ecr batch-delete-image --repository-name ecs-lab-api --image-ids imageTag=v1.0.0 imageTag=v1.1.0 --region $REGION
aws ecr delete-repository --repository-name ecs-lab-api --region $REGION
# 4. Видаліть log group
aws logs delete-log-group --log-group-name /ecs/ecs-lab-api --region $REGION
# 5. Видаліть Security Group
aws ec2 delete-security-group --group-id $SG_ID --region $REGION

Крок 14 (бонус): Підключення безкоштовного домену pp.ua до ALB
Ваш ECS Service доступний через ALB DNS name — щось схоже на ecs-lab-alb-123456789.eu-central-1.elb.amazonaws.com. Підключимо читабельний домен pp.ua.
Загальна схема:
myecs.pp.ua
↓ CNAME record (DNS)
ecs-lab-alb-123456789.eu-central-1.elb.amazonaws.com
↓ ALB (порт 80/443)
ECS Fargate Task → .NET API (порт 8080)
Крок 14a: Отримання DNS name вашого ALB
# Знайдіть DNS name ALB (якщо не записали раніше)
aws elbv2 describe-load-balancers \
--names ecs-lab-alb \
--query "LoadBalancers[0].DNSName" \
--output text --region eu-central-1
# Виведе: ecs-lab-alb-123456789.eu-central-1.elb.amazonaws.com
Крок 14b: Реєстрація субдомену на pp.ua
- Перейдіть на https://pp.ua та зареєструйте субдомен (наприклад
myecs.pp.ua) - Підтвердіть email і увійдіть у панель управління
Крок 14c: Додавання CNAME record у pp.ua
Панель pp.ua → DNS Management → Add Record:
| Поле | Значення |
|---|---|
| Type | CNAME |
| Name/Host | myecs |
| Value/Target | ecs-lab-alb-123456789.eu-central-1.elb.amazonaws.com (ваш ALB DNS) |
| TTL | 300 |
Різниця A vs CNAME: A record вказує на IP-адресу (3.64.185.42). CNAME record вказує на інший DNS name (ecs-lab-alb-xxx.amazonaws.com) — браузер потім сам вирішує IP цього name. CNAME не можна використовувати для кореневого домену (pp.ua), лише для субдоменів.
Зачекайте 1–10 хвилин і перевірте:
Крок 14d: HTTPS через ACM + ALB (опціонально)
ALB підтримує ACM сертифікати — на відміну від EC2, де потрібен Certbot. Сертифікат ACM безкоштовний.
eu-central-1. Не us-east-1!# 1. Запросити сертифікат (eu-central-1, той самий регіон що ALB)
CERT_ARN=$(aws acm request-certificate \
--domain-name "myecs.pp.ua" \
--validation-method DNS \
--region eu-central-1 \
--query CertificateArn --output text)
echo "Certificate ARN: $CERT_ARN"
# 2. Отримати CNAME для DNS валідації
aws acm describe-certificate \
--certificate-arn $CERT_ARN \
--region eu-central-1 \
--query "Certificate.DomainValidationOptions[0].ResourceRecord"
# Виведе Name і Value для CNAME — додайте у pp.ua (Type: CNAME)
Додайте у pp.ua другий CNAME для валідації ACM (Name і Value з попередньої команди). Дочекайтесь статусу Issued.
# 3. Знайдіть ARN вашого ALB
ALB_ARN=$(aws elbv2 describe-load-balancers \
--names ecs-lab-alb \
--query "LoadBalancers[0].LoadBalancerArn" \
--output text --region eu-central-1)
# 3b. Знайдіть ARN Target Group (створеної при налаштуванні Service)
TG_ARN=$(aws elbv2 describe-target-groups \
--names ecs-lab-tg \
--query "TargetGroups[0].TargetGroupArn" \
--output text --region eu-central-1)
# 4. Додати HTTPS Listener на ALB
aws elbv2 create-listener \
--load-balancer-arn $ALB_ARN \
--protocol HTTPS --port 443 \
--certificates CertificateArn=$CERT_ARN \
--default-actions Type=forward,TargetGroupArn=$TG_ARN \
--region eu-central-1
# 5. HTTP → HTTPS redirect (змінити HTTP listener)
HTTP_LISTENER=$(aws elbv2 describe-listeners \
--load-balancer-arn $ALB_ARN \
--query "Listeners[?Port==\`80\`].ListenerArn" \
--output text --region eu-central-1)
aws elbv2 modify-listener \
--listener-arn $HTTP_LISTENER \
--default-actions 'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}' \
--region eu-central-1
Тепер https://myecs.pp.ua — ваш ECS Fargate .NET API з офіційним SSL сертифікатом!

Резюме
- Docker вирішує проблему «works on my machine»: застосунок пакується разом із середовищем у незмінний образ.
- Multi-stage Dockerfile: build stage (~800 MB SDK) → runtime stage (~200 MB). Вихідний код не потрапляє у фінальний образ.
- Amazon ECR — приватний реєстр образів, інтегрований з IAM. Автоматичне сканування вразливостей при push.
- ECS: Task Definition (шаблон) → Task (запущений контейнер) → Service (підтримує N Tasks, rolling updates).
- Fargate — serverless запуск контейнерів без управління EC2. Оплата за vCPU/RAM × час роботи Task.
- awsvpc — кожен Task отримує власний ENI та IP. Security Groups на рівні Task.
- Rolling updates — нові Tasks запускаються паралельно зі старими, трафік переключається поступово.
- Environment variables у Task Definition:
environmentдля конфігурації,secretsдля паролів зі Secrets Manager.

Практичні завдання
Рівень 1 (Базовий)
Завдання 1. Поясніть різницю між Docker image та Docker container. Що таке multi-stage Dockerfile і яку проблему він вирішує для .NET?
Завдання 2. У чому принципова відмінність між Fargate та EC2 launch type? Для якого сценарію ви б обрали кожен?
Завдання 3. Поясніть, що відбувається під час кожного з станів Task: PROVISIONING, PENDING, ACTIVATING. Чому Task може застрягти у стані PENDING і як це діагностувати?
Рівень 2 (Практичний)
Завдання 4. Напишіть Dockerfile для ASP.NET Core 8, який використовує multi-stage build, запускається як non-root user (USER app перед ENTRYPOINT) і налаштований для production (Release конфігурація).
Завдання 5. Напишіть повний Task Definition JSON для ECS Fargate де: .NET API слухає порт 8080, ASPNETCORE_ENVIRONMENT=Production, connection string PostgreSQL отримується з Secrets Manager, логи пишуться у CloudWatch /ecs/my-api, health check кожні 30 секунд за /health.
Завдання 6. Сервіс отримує всплески після ролінгу оновлення до нової версії. Що змінити в Service, щоб деплой завжди зупинявся при 10+ послідовних помилкових health check? Які два параметри деплою відповідають за мінімальну кількість здорових Tasks під час оновлення?
Рівень 3 (Архітектура)
Завдання 7. Спроектуйте ECS-архітектуру для трьох мікросервісів: UserService (.NET), OrderService (.NET), NotificationService (background worker). Вимоги: усі у приватних підмережах, зовнішній трафік лише через ALB, UserService/OrderService масштабуються за CPU (60%), NotificationService — за кількістю повідомлень у SQS. Паролі в Secrets Manager. Намалюйте PlantUML схему.
Завдання 8. Ваш .NET API пише цитабельні дані безпосередньо у файл (appsettings.Production.json), який потрапляє в Docker image. Опишіть послідовність дій для перенесення секретів у Secrets Manager (які AWS пермішення потрібні, які зміни в Task Definition, які зміни в коді .NET для зчитування з перемінних середовища).
AWS IAM CLI — Довідник команд
Повний довідник AWS CLI команд для IAM — Users, Groups, Roles, Policies, MFA, STS. Усі важливі команди з прикладами та очікуваним виводом.
AWS ECR / ECS CLI — Довідник команд
Повний довідник AWS CLI команд для ECR та ECS — аутентифікація, репозиторії, кластери, сервіси, мережа, балансувальник, автомасштабування, логи. Усі важливі команди з прикладами та очікуваним виводом.