AWS

Elastic Load Balancing та Auto Scaling

Глибоке дослідження Application Load Balancer, Network Load Balancer, Target Groups та Auto Scaling Groups для .NET застосунків — від фундаментальних принципів балансування навантаження до повного налаштування HTTPS, health checks та стратегій автоматичного масштабування.

Elastic Load Balancing та Auto Scaling

Проблема одного сервера: мотивація до масштабування

Розглянемо фундаментальну проблему, з якою стикається кожна команда розробників при переході від прототипу до production-системи. Уявіть, що ваш .NET API розміщений на єдиному EC2 instance — це типова відправна точка для більшості проектів. Архітектура проста, і на початковому етапі вона цілком задовольняє вимоги. Однак із зростанням продукту виникають три класи критичних проблем, кожна з яких потенційно призводить до повної недоступності сервісу.

Перший клас: пікові навантаження. Система функціонує стабільно при нормальному трафіку, але в момент публічного анонсу, маркетингової акції або вірусного поширення кількість одночасних користувачів зростає на порядок. Єдиний сервер вичерпує ресурси процесора та оперативної пам'яті, час відповіді зростає з мілісекунд до десятків секунд, черга з'єднань переповнюється, і сервер переходить у стан відмови.

Другий клас: апаратні та програмні збої. Жоден фізичний або віртуальний сервер не має стовідсоткової гарантії безперебійної роботи. Відмова дискового масиву, пошкодження операційної системи, мережевий збій або баг у коді — кожна з цих подій при архітектурі єдиного сервера означає повну недоступність сервісу до ручного втручання інженера, що може тривати від хвилин до годин.

Третій клас: деплой без простою. Оновлення застосунку на єдиному сервері неминуче передбачає зупинку процесу, встановлення нової версії та повторний запуск. Протягом цього часу сервіс недоступний — це неприйнятно для систем із вимогами до uptime 99.9% і вище.

Всі три проблеми вирішуються архітектурним підходом, що базується на двох взаємодоповнюючих механізмах: Load Balancer рівномірно розподіляє вхідний трафік між кількома серверами, а Auto Scaling автоматично регулює кількість серверів відповідно до поточного навантаження.

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

actor "Користувач A" as UA
actor "Користувач B" as UB
actor "Користувач C" as UC

rectangle "Application Load Balancer\n(Layer 7 — HTTP/HTTPS)" as ALB #fef3c7 {
    note right
      Приймає ВЕСЬ вхідний трафік
      Перевіряє health кожного сервера
      Рівномірно розподіляє запити
      SSL termination
    end note
}

package "Auto Scaling Group (min:2, max:10)" #d1fae5 {
    node "EC2 #1\n.NET API\n(Running)" as EC1 #bbf7d0
    node "EC2 #2\n.NET API\n(Running)" as EC2 #bbf7d0
    node "EC2 #3\n.NET API\n(Auto-launched)" as EC3 #fef9c3 {
        note right : Запускається автоматично\nколи CPU > 70%
    }
}

UA --> ALB
UB --> ALB
UC --> ALB
ALB --> EC1 : Round Robin
ALB --> EC2 : Round Robin
ALB --> EC3 : Round Robin

note bottom of ALB
  Якщо EC2 #2 "падає" — ALB
  автоматично перестає
  направляти трафік на нього
end note
@enduml

AWS Elastic Load Balancing: огляд сімейства сервісів

AWS Elastic Load Balancing (ELB) — це керований сервіс AWS, що автоматично розподіляє вхідний трафік між кількома цільовими ресурсами: EC2 instances, контейнерами, IP-адресами або Lambda-функціями. AWS надає три типи балансувальників навантаження, кожен з яких оптимізований для конкретних сценаріїв використання.

Application Load Balancer

Рівень OSI: 7 (Application)

  • HTTP/HTTPS, HTTP/2, gRPC, WebSocket
  • Маршрутизація за URL, заголовками, cookies
  • SSL/TLS termination
  • Ідеальний для .NET Web API та мікросервісів

Network Load Balancer

Рівень OSI: 4 (Transport)

  • TCP, UDP, TLS
  • Мільйони запитів/сек, latency < 1ms
  • Статична IP-адреса балансувальника
  • Gaming, IoT, фінансові системи

Gateway Load Balancer

Рівень OSI: 3 (Network)

  • Прозора вставка в мережевий шлях
  • Firewall, IDS/IPS, deep packet inspection
  • Для мережевих virtual appliances
  • Рідко потрібен .NET розробникам
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "OSI Model" {
    rectangle "L7 Application\nHTTP/HTTPS/gRPC" as L7 #fef3c7
    rectangle "L4 Transport\nTCP/UDP/TLS" as L4 #dbeafe
    rectangle "L3 Network\nIP Packets" as L3 #fce7f3
}

rectangle "Application Load Balancer" as ALB #fef3c7
rectangle "Network Load Balancer" as NLB #dbeafe
rectangle "Gateway Load Balancer" as GWLB #fce7f3

L7 --> ALB : "«розуміє» HTTP-запити\nURL, заголовки, cookies"
L4 --> NLB : "бачить лише TCP/UDP пакети\nнадзвичайно швидкий"
L3 --> GWLB : "прозора вставка\nу мережевий шлях"

@enduml

Application Load Balancer (ALB) — детальний розгляд

Application Load Balancer (ALB) є найбільш функціонально насиченим балансувальником навантаження у сімействі AWS ELB та є стандартним вибором для .NET Web API та мікросервісних архітектур. Його ключова відмінність від NLB полягає у тому, що ALB функціонує на сьомому рівні моделі OSI — рівні застосунків. Це означає, що ALB не просто передає мережеві пакети, а повністю розуміє семантику протоколу HTTP: він аналізує URI запиту, HTTP-заголовки, query parameters, тіло запиту та cookie-файли. На основі цього аналізу ALB приймає інтелектуальні рішення про маршрутизацію.

Архітектура ALB: Listener → Rule → Target Group

Внутрішня архітектура ALB будується на трирівневій ієрархії компонентів:

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

rectangle "Internet" as I #e2e8f0

rectangle "ALB\n(DNS: myapp.elb.amazonaws.com)" as ALB #fef3c7

rectangle "Listener :80\n(HTTP)" as L80 #e0e7ff
rectangle "Listener :443\n(HTTPS)" as L443 #dbeafe

together {
    rectangle "Rule 1\nIF path = /api/*\nTHEN forward → api-tg" as R1 #f0fdf4
    rectangle "Rule 2\nIF path = /health\nTHEN return 200 OK" as R2 #f0fdf4
    rectangle "Rule 3 (default)\nIF true\nTHEN forward → web-tg" as R3 #f0fdf4
}

package "Target Group: api-tg\nprotocol=HTTP port=5000" as APITG #d1fae5 {
    node "EC2 #1\nHealthy ✓" as A1 #bbf7d0
    node "EC2 #2\nHealthy ✓" as A2 #bbf7d0
}

package "Target Group: web-tg\nprotocol=HTTP port=3000" as WEBTG #fce7f3 {
    node "EC2 #3\nHealthy ✓" as W1 #fbcfe8
}

I --> ALB
ALB -down-> L80 : "HTTP:80"
ALB -down-> L443 : "HTTPS:443"
L80 -down-> R1
L80 -down-> R2
L80 -down-> R3
L443 -down-> R1 : "(Same rules)"
R1 -right-> APITG
R3 -right-> WEBTG

@enduml

Listener — точка входу трафіку

Listener — це процес, що виконується на ALB і очікує вхідні з'єднання на визначеному порті та протоколі. Кожен ALB може мати кілька Listener'ів одночасно. Типова production-конфігурація включає два Listener'и: HTTP на порту 80 (для редиректу на HTTPS) та HTTPS на порту 443 (для обробки реального трафіку).

Protocol
enum required
Протокол Listener'а. Допустимі значення: HTTP, HTTPS. HTTPS Listener вимагає прив'язки SSL/TLS сертифіката від AWS Certificate Manager або власного сертифіката.
Port
integer required
Порт, на якому Listener очікує вхідні з'єднання. Стандартні значення: 80 для HTTP та 443 для HTTPS. Допустимий діапазон: 1–65535.
DefaultActions
array required
Дія, що виконується за замовчуванням, якщо жодне правило (Rule) не збіглось. Типово: Forward до Target Group або Redirect на HTTPS.

Listener Rules — логіка маршрутизації

Listener Rules — це набір умовних правил, що визначають, як саме ALB обробляє кожен вхідний запит. Кожне правило має пріоритет (priority), одну або кілька умов (conditions) та одну дію (action). ALB перевіряє правила у порядку зростання пріоритету і виконує першу дію, умова якої співпала.

Типи умов (conditions):

  • path-pattern — URI path (/api/*, /static/*)
  • host-header — доменне ім'я (api.example.com)
  • http-header — значення HTTP-заголовка
  • http-request-method — HTTP-метод (GET, POST)
  • query-string — параметр query string
  • source-ip — IP-адреса або CIDR клієнта

Типи дій (actions):

  • Forward — передати запит до Target Group
  • Redirect — HTTP redirect (301/302) на інший URL
  • Fixed Response — повернути статичну відповідь (код + body) без залучення серверів

Target Group — пул серверів

Target Group — це логічна група серверів (targets), між якими ALB розподіляє запити, що відповідають умовам правила. Target Group є центральною концепцією архітектури ALB: вона відокремлює логіку маршрутизації від конфігурації конкретних серверів.

Типи targets у Target Group:

instance
тип target
EC2 instance за його Instance ID. ALB автоматично визначає приватну IP-адресу instance та надсилає трафік напряму. Найпоширеніший тип для ASG.
ip
тип target
Довільна IP-адреса (у межах VPC або через VPN/Direct Connect). Використовується для ECS Fargate задач, on-premises серверів або будь-якого іншого ресурсу з IP.
lambda
тип target
AWS Lambda function. ALB перетворює HTTP-запит у подію Lambda та очікує HTTP-відповідь. Дозволяє реалізувати serverless API за звичайним URL.

Network Load Balancer (NLB) — Layer 4

Network Load Balancer (NLB) функціонує на четвертому рівні моделі OSI — транспортному рівні. На відміну від ALB, NLB не розуміє семантики HTTP: він оперує лише TCP-з'єднаннями та UDP-дейтаграмами. Ця принципова відмінність визначає його сценарії використання: NLB здатен обробляти мільйони запитів на секунду із затримкою (latency) порядку мілісекунд або навіть мікросекунд.

Ключова технічна особливість NLB — збереження IP-адреси клієнта (source IP preservation) без додаткового налаштування. ALB виступає проксі і замінює IP клієнта своєю адресою, передаючи оригінальний IP лише через HTTP-заголовок X-Forwarded-For. NLB же прозоро передає TCP-з'єднання, і сервер бачить справжній IP клієнта.

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

actor "Client\n(IP: 203.0.113.5)" as C

rectangle "ALB\n(Layer 7 Proxy)" as ALB #fef3c7 {
    note right
      Перериває TCP-з'єднання.
      Встановлює НОВЕ з'єднання
      до сервера від свого IP.
      Додає X-Forwarded-For: 203.0.113.5
    end note
}

rectangle "NLB\n(Layer 4 Pass-through)" as NLB #dbeafe {
    note right
      Прозоро передає TCP-з'єднання.
      Сервер бачить справжній
      IP клієнта: 203.0.113.5
    end note
}

node ".NET API\nServer" as SRV1 #bbf7d0
node ".NET API\nServer" as SRV2 #bbf7d0

C --> ALB : "TCP з'єднання"
ALB --> SRV1 : "Нове TCP з'єднання\nвід IP ALB"

C --> NLB : "TCP з'єднання"
NLB --> SRV2 : "Те саме TCP з'єднання\n від IP клієнта"

@enduml

Для більшості .NET Web API — ALB є правильним вибором. NLB варто розглядати лише при специфічних вимогах до продуктивності або протоколів.


Target Groups та Health Checks

Алгоритми балансування навантаження

Target Group підтримує три алгоритми розподілу запитів між targets. Вибір алгоритму суттєво впливає на рівномірність навантаження залежно від характеристик вхідного трафіку: однорідності запитів, часу їх обробки та відносної потужності серверів.

Weighted Round Robin — зважений циклічний розподіл

Weighted Round Robin (WRR) є алгоритмом за замовчуванням для ALB Target Groups. Класичний Round Robin розподіляє запити по черзі між targets у фіксованому циклі, не беручи до уваги жодних метрик поточного стану. Зважений варіант (Weighted) розширює базовий алгоритм: кожному target може бути призначена числова вага, і target з більшою вагою отримує пропорційно більше запитів у межах одного циклу.

Принцип роботи. ALB підтримує внутрішній покажчик (pointer), що вказує на наступний target у послідовності. При надходженні кожного нового з'єднання ALB спрямовує його на поточний target і переміщує покажчик на наступний. По досягненні кінця списку покажчик повертається на початок. Цей підхід є детермінованим: за умови стабільного переліку targets порядок розподілу завжди передбачуваний.

Коли WRR є оптимальним вибором:

  • Всі запити до вашого API приблизно однакові за часом обробки та споживанням ресурсів (наприклад, простий CRUD API)
  • Всі targets мають однаковий тип EC2 instance (наприклад, всі t3.medium)
  • Немає довготривалих з'єднань або стрімінгу
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

title "Weighted Round Robin: розподіл 9 послідовних запитів"

participant "ALB\n(pointer → Target 1)" as ALB #fef3c7
participant "Target 1\n(.NET API)" as T1 #bbf7d0
participant "Target 2\n(.NET API)" as T2 #bbf7d0
participant "Target 3\n(.NET API)" as T3 #bbf7d0

note over ALB : Вага всіх targets = 1\n(рівний розподіл)

ALB -> T1 : Запит #1\n(pointer -> T2)
ALB -> T2 : Запит #2\n(pointer -> T3)
ALB -> T3 : Запит #3\n(pointer -> T1)
ALB -> T1 : Запит #4\n(pointer -> T2)
ALB -> T2 : Запит #5\n(pointer -> T3)
ALB -> T3 : Запит #6\n(pointer -> T1)
ALB -> T1 : Запит #7
ALB -> T2 : Запит #8
ALB -> T3 : Запит #9

note over T1 : Отримав: #1, #4, #7\n(3 запити / 33%)
note over T2 : Отримав: #2, #5, #8\n(3 запити / 33%)
note over T3 : Отримав: #3, #6, #9\n(3 запити / 33%)

@enduml

Проблема WRR при неоднорідних запитах. Уявіть API, де GET /users обробляється за 5ms, а POST /reports/generate — за 30 секунд. Round Robin розподіляє запити по черзі без урахування їхньої складності:

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

title "Проблема WRR при неоднорідних запитах"

participant "ALB" as ALB #fef3c7
participant "Target 1" as T1 #fca5a5
participant "Target 2" as T2 #bbf7d0
participant "Target 3" as T3 #bbf7d0

ALB -> T1 : POST /reports/generate\n(30 секунд!)
ALB -> T2 : GET /users\n(5ms)
ALB -> T3 : GET /users\n(5ms)
ALB -> T1 : GET /products <- WRR каже: черга T1!\n(Target 1 досi зайнятий)
T2 --> ALB : done (5ms)
T3 --> ALB : done (5ms)

note over T1 #fee2e2
  Target 1 оброблює важкий
  запит 30 секунд. Усi наступнi
  запити на нього вишиковуються
  в чергу. CPU: 100%!
end note

note over T2, T3 #f0fdf4
  Target 2 та 3 вiльнi:
  вони вже завершили своi
  запити, але WRR продовжує
  направляти на T1.
end note

@enduml

Налаштування ваги у Weighted Round Robin. За замовчуванням всі targets мають однакову вагу. Вагу можна змінити для нерівномірного розподілу:

# t3.large має вдвічі більше CPU — встановити вагу 2:1
aws elbv2 register-targets \
    --target-group-arn "$TG_ARN" \
    --targets \
        Id=i-0small123,Weight=1 \
        Id=i-0large456,Weight=2 \
    --region eu-central-1

Least Outstanding Requests — мінімум незавершених з'єднань

Least Outstanding Requests (LOR) — алгоритм, що вирішує фундаментальний недолік Round Robin при неоднорідному навантаженні. Замість механічної черги ALB аналізує поточний стан кожного target і направляє новий запит туди, де кількість активних (незавершених) з'єднань є найменшою у даний момент.

Принцип роботи. ALB підтримує лічильник активних запитів для кожного target. Коли надходить новий запит, ALB перебирає всі healthy targets, знаходить той з мінімальним лічильником і направляє запит туди, одночасно збільшуючи лічильник на 1. Коли target завершує обробку та повертає відповідь — лічильник зменшується на 1.

Детальний приклад. Система з трьома targets і змішаним трафіком (швидкі та повільні запити):

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

title "Least Outstanding Requests: адаптивний розподiл"

participant "ALB\n(лiчильники: T1=0 T2=0 T3=0)" as ALB #fef3c7
participant "Target 1" as T1 #bbf7d0
participant "Target 2" as T2 #bbf7d0
participant "Target 3" as T3 #bbf7d0

ALB -> T1 : [1] POST /reports (30s)\nT1=1, T2=0, T3=0
ALB -> T2 : [2] GET /users (5ms)\nMIN=0 -> T2\nT1=1, T2=1, T3=0
ALB -> T3 : [3] GET /orders (5ms)\nMIN=0 -> T3\nT1=1, T2=1, T3=1
T2 --> ALB : done -> T2=0
T3 --> ALB : done -> T3=0

note over ALB : Стан: T1=1, T2=0, T3=0

ALB -> T2 : [4] GET /products\nMIN=0 -> T2 (уникаємо T1!)
ALB -> T3 : [5] GET /catalog\nMIN=0 -> T3 (уникаємо T1!)
ALB -> T2 : [6] GET /search
ALB -> T3 : [7] GET /users

note over T1 #fee2e2
  Target 1 зайнятий 30s.
  LOR автоматично
  обходить його!
end note

note over T2, T3 #f0fdf4
  T2 та T3 рiвномiрно
  отримують легкi запити.
  Система збалансована!
end note

@enduml

Кількісне порівняння WRR vs LOR при 100 запитах: 1 важкий (30s) + 99 легких (5ms):

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

title "WRR vs LOR: порiвняння розподiлу при неоднорiдному трафiку (100 запитiв)"

rectangle "Weighted Round Robin" as WRR {
    rectangle "Target 1\n34 запити\n(1 важкий x 30s + 33 легких)\nCPU: ПЕРЕВАНТАЖЕНИЙ" as W1 #fca5a5
    rectangle "Target 2\n33 запити\n(33 легких x 5ms)\nCPU: ~30%" as W2 #bbf7d0
    rectangle "Target 3\n33 запити\n(33 легких x 5ms)\nCPU: ~30%" as W3 #bbf7d0
}

rectangle "Least Outstanding Requests" as LOR {
    rectangle "Target 1\n1 запит\n(важкий x 30s)\nCPU: 100% протягом 30s" as L1 #fde68a
    rectangle "Target 2\n~50 запитiв\n(легкi x 5ms)\nCPU: рiвномiрний" as L2 #bbf7d0
    rectangle "Target 3\n~49 запитiв\n(легкi x 5ms)\nCPU: рiвномiрний" as L3 #bbf7d0
}

@enduml

Коли LOR є оптимальним вибором:

  • API з різнорідними ендпоінтами: прості GET запити поруч із важкими операціями генерації звітів, обробки файлів або ML-inference
  • Наявність стрімінгових з'єднань або WebSocket (одне з'єднання тривалістю хвилини)
  • Targets мають різну потужність (різні типи EC2 instances)
  • Будь-який сценарій, де час обробки запитів суттєво варіюється

Налаштування LOR через AWS CLI:

# Змінити алгоритм Target Group на Least Outstanding Requests
aws elbv2 modify-target-group-attributes \
    --target-group-arn "$TG_ARN" \
    --attributes \
        Key=load_balancing.algorithm.type,Value=least_outstanding_requests \
    --region eu-central-1
Зміна алгоритму на LOR
$ aws elbv2 modify-target-group-attributes \
--target-group-arn "$TG_ARN" \
--attributes Key=load_balancing.algorithm.type,Value=least_outstanding_requests
{
"Attributes": [
{
"Key": "load_balancing.algorithm.type",
"Value": "least_outstanding_requests"
}
]
}

Перевірка поточного алгоритму Target Group:

aws elbv2 describe-target-group-attributes \
    --target-group-arn "$TG_ARN" \
    --region eu-central-1 \
    --query "Attributes[?Key=='load_balancing.algorithm.type']"
Перевірка активного алгоритму
$ aws elbv2 describe-target-group-attributes --target-group-arn "$TG_ARN" ...
[
{
"Key": "load_balancing.algorithm.type",
"Value": "least_outstanding_requests"
}
]

Weighted Random — зважений випадковий розподіл

Weighted Random — алгоритм, що поєднує випадковість із можливістю задати пропорції розподілу трафіку між targets. На відміну від Round Robin, послідовність розподілу не є фіксованою: ALB обирає target псевдовипадково, але з урахуванням ваги — target з вищою вагою має пропорційно вищу ймовірність бути обраним.

Математичний принцип. Якщо Target 1 має вагу 70, Target 2 — вагу 20, Target 3 — вагу 10, то ймовірність обрання: P(T1) = 70/100 = 70%, P(T2) = 20/100 = 20%, P(T3) = 10/100 = 10%.

Ключова сфера застосування: Canary Deployment. Weighted Random є стандартним інструментом для поступового розгортання нових версій застосунку. Замість одночасного переключення всього трафіку на нову версію, частка трафіку збільшується поступово, поки команда спостерігає за метриками помилок та латентністю:

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

title "Canary Deployment через Weighted Random: три фази"

rectangle "ALB Listener Rule" as ALB #fef3c7

rectangle "Фаза 1: Canary 5%" as P1 #fff3e0 {
    rectangle "api-v1-tg\nВага: 95\n(поточна версiя)" as V1_1 #bbf7d0
    rectangle "api-v2-tg\nВага: 5\n(нова версiя)" as V2_1 #fde68a
}

rectangle "Фаза 2: 50/50" as P2 #fff3e0 {
    rectangle "api-v1-tg\nВага: 50" as V1_2 #fde68a
    rectangle "api-v2-tg\nВага: 50" as V2_2 #fde68a
}

rectangle "Фаза 3: Повна мiграцiя" as P3 #f0fdf4 {
    rectangle "api-v2-tg\nВага: 100\n(v1 виведено)" as V2_3 #bbf7d0
}

ALB --> P1
P1 -[hidden]down-> P2
P2 -[hidden]down-> P3

note right of P1
  5% реальних користувачiв
  тестують нову версiю.
  Монiторимо: error rate,
  latency, business metrics.
end note

note right of P2
  Метрики гарнi -> 50%.
  Вiдкочування: вага v1=100,
  вага v2=0 (миттєво!).
end note

note right of P3
  Нова версiя стабiльна.
  Виводимо v1 з обiгу.
end note

@enduml

Реалізація Canary Deployment через ALB Weighted Target Groups:

AWS реалізує Weighted Random через ваги на рівні Listener Rule — правило може розподіляти трафік між кількома Target Groups з визначеними пропорціями:

LISTENER_ARN="arn:aws:elasticloadbalancing:eu-central-1:123456789012:listener/app/..."
TG_V1_ARN="arn:aws:elasticloadbalancing:...:targetgroup/api-v1-tg/..."
TG_V2_ARN="arn:aws:elasticloadbalancing:...:targetgroup/api-v2-tg/..."

# Фаза 1: 5% трафіку на нову версію
aws elbv2 modify-listener \
    --listener-arn "$LISTENER_ARN" \
    --default-actions "[
        {
            \"Type\": \"forward\",
            \"ForwardConfig\": {
                \"TargetGroups\": [
                    {\"TargetGroupArn\": \"$TG_V1_ARN\", \"Weight\": 95},
                    {\"TargetGroupArn\": \"$TG_V2_ARN\", \"Weight\": 5}
                ]
            }
        }
    ]" \
    --region eu-central-1

# Фаза 2: 50/50 (перевіряємо рівну продуктивність)
aws elbv2 modify-listener \
    --listener-arn "$LISTENER_ARN" \
    --default-actions "[
        {
            \"Type\": \"forward\",
            \"ForwardConfig\": {
                \"TargetGroups\": [
                    {\"TargetGroupArn\": \"$TG_V1_ARN\", \"Weight\": 50},
                    {\"TargetGroupArn\": \"$TG_V2_ARN\", \"Weight\": 50}
                ]
            }
        }
    ]" \
    --region eu-central-1

# Фаза 3: повна міграція на v2
aws elbv2 modify-listener \
    --listener-arn "$LISTENER_ARN" \
    --default-actions "Type=forward,TargetGroupArn=$TG_V2_ARN" \
    --region eu-central-1

echo "Migration complete: 100% traffic -> v2"
Canary Deployment: Фаза 1 → 5%
$ aws elbv2 modify-listener --listener-arn "$LISTENER_ARN" --default-actions '[...]'
{
"Listeners": [{
"DefaultActions": [{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{ "Weight": 95, "TargetGroupArn": "...api-v1-tg..." },
{ "Weight": 5, "TargetGroupArn": "...api-v2-tg..." }
]
}
}]
}]
}
← 5% реального трафіку тепер іде на нову версію
Weighted Random vs Round Robin при рівних вагах. Якщо всі targets мають однакову вагу, Weighted Random та Round Robin дають ідентичний результат у довгостроковій перспективі. Різниця: Round Robin гарантує точний розподіл (N/3 запитів кожному з трьох targets), тоді як Weighted Random дає статистично очікуваний розподіл — рівний у середньому, але з можливими коливаннями в короткій перспективі.

Порівняльна таблиця алгоритмів

ХарактеристикаWeighted Round RobinLeast Outstanding RequestsWeighted Random
За замовчуваннямТакНіНі
Однорідні запитиОптимальноДобреДобре
Неоднорідні запитиПоганоОптимальноЗадовільно
WebSocket / streamingПоганоОптимальноЗадовільно
Canary deploymentНе підходитьНе підходитьОптимально
Різна потужність targetsЧерез вагиАвтоматичноЧерез ваги
ПередбачуваністьДетермінованаАдаптивнаСтохастична
Рекомендовано для .NET APIПростий CRUD APIAPI з важкими операціямиA/B тест, canary

Дерево рішень для вибору алгоритму:

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

start

:Потрiбен canary deployment\nабо A/B тестування?;

if (Так) then
    :Weighted Random\n(ваги мiж Target Groups\nна рiвнi Listener Rule);
    stop
endif

:Запити однорiднi за\nчасом обробки?;

if (Так — всi запити\nблизько N ms) then
    :Weighted Round Robin\n(алгоритм за замовчуванням,\nнiчого не мiняти);
    stop
else (Нi — є важкi запити,\nWebSocket, streaming)
    :Least Outstanding Requests\n(автоматично уникає\nперевантажених targets);
    stop
endif

@enduml

Health Check: механізм виявлення відмов

Health Check — це механізм, за допомогою якого ALB постійно моніторить стан кожного target у Target Group. ALB регулярно надсилає тестові HTTP-запити на спеціальний ендпоінт кожного сервера і аналізує відповідь. На основі результатів цих перевірок ALB ухвалює рішення: включати чи виключати target із ротації.

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

participant "ALB\nHealth Checker" as HC
participant "Target (EC2)\n/health endpoint" as TG

loop "Кожні HealthCheckIntervalSeconds (30s)"
    HC -> TG : "GET /health HTTP/1.1\nHost: internal-ip:5000"
    alt "Відповідь 200 OK за < 5s"
        TG --> HC : "200 OK\n{\"status\":\"Healthy\"}"
        HC -> HC : "successCount++"
        note right of HC
            Якщо successCount >=
            HealthyThresholdCount (2)
            → target стає Healthy
        end note
    else "Таймаут або помилка"
        TG --> HC : "(немає відповіді / 500)"
        HC -> HC : "failureCount++"
        note right of HC
            Якщо failureCount >=
            UnhealthyThresholdCount (3)
            → target стає Unhealthy
            → ALB виключає з ротації
        end note
    end
end

@enduml

Конфігурація Health Check у консолі AWS або через CLI:

{
    "Protocol": "HTTP",
    "Path": "/health",
    "Port": "traffic-port",
    "HealthyThresholdCount": 2,
    "UnhealthyThresholdCount": 3,
    "HealthCheckIntervalSeconds": 15,
    "HealthCheckTimeoutSeconds": 5,
    "Matcher": {
        "HttpCode": "200"
    }
}
Protocol
string required
Протокол для health check запитів. Допустимі значення: HTTP, HTTPS. Має відповідати протоколу, на якому працює застосунок.
Path
string required
URI-шлях, на який ALB надсилає GET-запити. За угодою: /health. Цей ендпоінт повинен повертати HTTP 200 лише тоді, коли застосунок та всі його залежності справно функціонують.
HealthyThresholdCount
integer
Кількість послідовних успішних перевірок, після яких target переходить зі стану unhealthy у стан healthy. Значення 2 означає, що після двох поспіль успішних відповідей ALB відновить маршрутизацію трафіку на цей target.
UnhealthyThresholdCount
integer
Кількість послідовних невдалих перевірок, після яких target переходить у стан unhealthy і виключається з ротації. При значенні 3 та інтервалі 15 секунд — від першої невдалої відповіді до виключення мине 45 секунд.
HealthCheckIntervalSeconds
integer
Інтервал між перевірками в секундах. Допустимий діапазон: 5–300 секунд. Менший інтервал забезпечує швидше виявлення відмов, але генерує більше трафіку.
HealthCheckTimeoutSeconds
integer
Максимальний час очікування відповіді на health check запит. Якщо відповідь не надійшла протягом цього часу — перевірка вважається невдалою. Має бути меншим за HealthCheckIntervalSeconds.
Найпоширеніша помилка: реалізація /health endpoint, що завжди повертає HTTP 200 незалежно від реального стану застосунку. Це призводить до ситуації, коли ALB вважає сервер здоровим, хоча він втратив з'єднання з базою даних або іншими критичними залежностями. ALB продовжує направляти реальний трафік на цей сервер, і користувачі отримують помилки 500.

Health Check у .NET — правильна реалізація

ASP.NET Core надає вбудовану систему Health Checks через NuGet пакет Microsoft.Extensions.Diagnostics.HealthChecks. Ця система дозволяє декларативно описати перелік перевірок, що мають виконуватись, та агрегувати їхні результати в єдину відповідь.

Правильний підхід полягає у тому, що /health endpoint має перевіряти реальну функціональну готовність застосунку до обробки запитів — не лише факт того, що процес запущений.

Program.cs
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using HealthChecks.UI.Client;

var builder = WebApplication.CreateBuilder(args);

// Реєстрація Health Check сервісів
builder.Services.AddHealthChecks()
    // Перевірка підключення до SQL Server
    .AddSqlServer(
        connectionString: builder.Configuration.GetConnectionString("DefaultConnection")!,
        name: "sqlserver",
        failureStatus: HealthStatus.Unhealthy,
        tags: ["db", "sql", "infrastructure"])

    // Перевірка Redis (якщо використовується для кешування або сесій)
    .AddRedis(
        redisConnectionString: builder.Configuration["Redis:ConnectionString"]!,
        name: "redis",
        failureStatus: HealthStatus.Degraded,
        tags: ["cache", "infrastructure"])

    // Кастомна перевірка: чи доступний зовнішній API
    .AddCheck("external-api", async () =>
    {
        // Перевіряємо доступність залежного сервісу
        using var http = new HttpClient();
        try
        {
            var response = await http.GetAsync("https://api.payment-provider.com/ping");
            return response.IsSuccessStatusCode
                ? HealthCheckResult.Healthy("Payment API is reachable")
                : HealthCheckResult.Degraded("Payment API returned non-2xx status");
        }
        catch
        {
            return HealthCheckResult.Unhealthy("Payment API is unreachable");
        }
    }, tags: ["external"])

    // Базова самоперевірка: чи процес взагалі живий
    .AddCheck("self", () => HealthCheckResult.Healthy("Application process is running"));

var app = builder.Build();

// Ендпоінт для ALB Health Check — мінімальна відповідь (200 або 503)
// ALB не потребує деталей, лише HTTP статус код
app.MapHealthChecks("/health", new HealthCheckOptions
{
    // Включити ВСІ перевірки (без фільтрації за тегами)
    Predicate = _ => true,

    // Кастомний writer: повертаємо мінімальний JSON
    ResponseWriter = async (context, report) =>
    {
        context.Response.ContentType = "application/json";
        var result = System.Text.Json.JsonSerializer.Serialize(new
        {
            status = report.Status.ToString(),
            timestamp = DateTime.UtcNow,
            // Короткий перелік перевірок без чутливих деталей
            checks = report.Entries.Select(e => new
            {
                name = e.Key,
                status = e.Value.Status.ToString()
            })
        });
        await context.Response.WriteAsync(result);
    }
});

// Детальний ендпоінт для внутрішнього моніторингу
// НІКОЛИ не відкривайте цей ендпоінт публічно через ALB!
// Додайте окреме правило ALB: /health/detail → Fixed Response 403
app.MapHealthChecks("/health/detail", new HealthCheckOptions
{
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
}).RequireAuthorization(); // додаткова авторизація

app.Run();
Розподіл відповідальності між ендпоінтами:/health є спрощеним ендпоінтом виключно для ALB — він має бути максимально швидким та легким. /health/detail надає повну діагностичну інформацію для команди DevOps та систем моніторингу (Grafana, Datadog), але має бути захищений авторизацією або доступний лише у внутрішній мережі.

Auto Scaling Groups (ASG): концепція та архітектура

Auto Scaling Group (ASG) — це сервіс AWS, що управляє lifecycle-ом колекції EC2 instances як єдиного цілого. ASG безперервно порівнює поточний стан кластера instances з бажаним станом і виконує необхідні коригуючі дії: запускає нові instances або завершує існуючі.

Три фундаментальні параметри ASG

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

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

skinparam rectangle {
    BackgroundColor #f0fdf4
    BorderColor #16a34a
}

rectangle "Auto Scaling Group: dotnet-api-asg" as ASG {

    rectangle "Maximum Capacity = 10\n(жорстка верхня межа — захист від надмірних витрат)" as MAX #fef3c7

    rectangle "Desired Capacity = 4\n(поточна цільова кількість — змінюється Scaling Policies)" as DES #dbeafe

    rectangle "Minimum Capacity = 2\n(жорстка нижня межа — гарантія High Availability)" as MIN #dcfce7

    MAX -[hidden]down-> DES
    DES -[hidden]down-> MIN
}

note right of MAX
    ASG ніколи не запустить
    більше ніж max instances,
    навіть якщо навантаження
    продовжує зростати
end note

note right of DES
    ASG постійно прагне
    підтримувати саме
    цю кількість healthy instances
end note

note right of MIN
    ASG ніколи не зменшить
    кількість instances нижче min,
    навіть якщо навантаження = 0
end note

@enduml
MinSize
integer required
Мінімальна кількість instances, що завжди залишаються запущеними, незалежно від рівня навантаження. Значення 2 є стандартом для High Availability: якщо один instance відмовить, другий продовжує обробляти запити. Значення 0 допустиме для batch-обробки або розробки, але неприйнятне для production API.
MaxSize
integer required
Верхня межа кількості instances. Захищає від неконтрольованого зростання витрат у разі аномальної поведінки (DDoS-атака, нескінченний цикл, помилка в Scaling Policy). Завжди встановлюйте це значення усвідомлено, розуміючи максимально допустимий рахунок AWS.
DesiredCapacity
integer required
Поточна цільова кількість instances. ASG безперервно контролює, щоб кількість healthy instances дорівнювала цьому значенню. Scaling Policies змінюють DesiredCapacity, а ASG відповідно запускає або завершує instances.
HealthCheckGracePeriod
integer
Час у секундах після запуску нового instance, протягом якого ASG ігнорує результати health check. Необхідний для того, щоб .NET застосунок встиг запуститися, підключитися до бази даних та бути готовим до обробки запитів до першої перевірки. Рекомендоване значення: 120–300 секунд.

Launch Templates — специфікація нового instance

Launch Template — це версіонований документ-специфікація, що точно описує конфігурацію EC2 instance, який ASG створить при горизонтальному масштабуванні. Коли ASG вирішує додати новий instance (Scale Out), він звертається до Launch Template і створює EC2 instance відповідно до зазначених параметрів.

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

package "Launch Template: dotnet-api-lt" as LT #fef9c3 {
    artifact "AMI ID\n(Custom AMI з .NET 10\nта вашим API)" as AMI
    artifact "Instance Type\n(t3.micro)" as IT
    artifact "Key Pair\n(ec2-lab-key.pem)" as KP
    artifact "Security Group\n(ec2-asg-sg)" as SG
    artifact "User Data Script\n(запуск systemd сервісу)" as UD
    artifact "IAM Instance Profile\n(дозволи для EC2)" as IAM
}

rectangle "Auto Scaling Group" as ASG #d1fae5

ASG --> LT : "Потрібен новий instance!\n(DesiredCapacity зросла)"
LT --> AMI : читає
LT --> IT : читає
LT --> UD : читає

node "Новий EC2 Instance\n(точна копія специфікації)" as EC #bbf7d0

LT --> EC : "Створює instance\nза цим шаблоном"

note bottom of EC
    Кожен новий instance
    запускається з ідентичною
    конфігурацією автоматично,
    без ручного втручання
end note

@enduml
Launch Templates vs Launch Configurations. До 2017 року для налаштування ASG використовувались Launch Configurations. Вони застаріли (deprecated) і не підтримують ряд сучасних функцій. Launch Templates є їх повноцінною заміною: вони підтримують версіонування (v1, v2, v3), змішані стратегії використання Spot та On-Demand instances в одному ASG, та всі нові можливості EC2. Завжди використовуйте Launch Templates.

Scaling Policies — стратегії автоматичного масштабування

AWS Auto Scaling підтримує три принципово різні стратегії масштабування, кожна з яких оптимальна для певного класу навантажень.

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

package "Scaling Policies" {

    rectangle "Target Tracking\nПідтримуй метрику на цільовому рівні\n(найпростіший, рекомендований)" as TT #d1fae5

    rectangle "Step Scaling\nДодавай різну кількість instances\nзалежно від відхилення" as SS #dbeafe

    rectangle "Scheduled Scaling\nМасштабуй за розкладом\n(передбачуване навантаження)" as SCHED #fef3c7

}

note right of TT
    "Підтримуй CPU = 70%"
    ASG сам розраховує
    скільки instances потрібно
end note

note right of SS
    CPU 70-80%: +1 instance
    CPU 80-90%: +2 instances
    CPU > 90%: +4 instances
end note

note right of SCHED
    Пн-Пт 08:00 UTC: desired=5
    Пн-Пт 20:00 UTC: desired=2
end note

@enduml

Target Tracking Scaling Policy

Найпростіший та найрекомендованіший підхід. Ви декларуєте одне просте правило: «підтримуй метрику X на рівні Y». ASG автоматично розраховує необхідну кількість instances для досягнення цього показника та додає або видаляє instances відповідно.

  1. EC2 → Auto Scaling Groups → оберіть dotnet-api-asg → вкладка Automatic scaling
  2. Create dynamic scaling policy
  3. Policy type: Target tracking scaling
  4. Scaling policy name: cpu-target-tracking
  5. Metric type: Average CPU utilization
  6. Target value: 70
  7. Instance warmup: 60 seconds
  8. (Необов'язково) Розгорніть Additional settings → Scale-in cooldown: 300
  9. Create
put-scaling-policy → Target Tracking
$ aws autoscaling put-scaling-policy --policy-name cpu-target-tracking ...
{
"PolicyARN": "arn:aws:autoscaling:eu-central-1:123456789012:scalingPolicy:...",
"Alarms": [
{ "AlarmName": "TargetTracking-dotnet-api-asg-AlarmHigh-..." },
{ "AlarmName": "TargetTracking-dotnet-api-asg-AlarmLow-..." }
]
}
← ASG автоматично створив два CloudWatch Alarms: для Scale Out та Scale In

Конфігурація параметрів:

Повна конфігурація Target Tracking Policy
{
    "PolicyName": "cpu-target-tracking",
    "PolicyType": "TargetTrackingScaling",
    "TargetTrackingConfiguration": {
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "ASGAverageCPUUtilization"
        },
        "TargetValue": 70.0,
        "ScaleOutCooldown": 60,
        "ScaleInCooldown": 300,
        "DisableScaleIn": false
    }
}
PredefinedMetricType
enum required
Метрика, яку ASG буде підтримувати на цільовому рівні. Доступні значення:
  • ASGAverageCPUUtilization — середнє завантаження CPU по всіх instances ASG
  • ASGAverageNetworkIn / ASGAverageNetworkOut — середній мережевий трафік (байт/сек)
  • ALBRequestCountPerTarget — середня кількість запитів ALB на один instance
TargetValue
number required
Цільове значення метрики. ASG прагне підтримувати метрику саме на цьому рівні. Значення 70.0 для CPU означає: «тримай середнє CPU по ASG на рівні 70%». Рекомендований діапазон для CPU: 60–75%.
ScaleOutCooldown
integer
Час очікування (секунди) між додаванням instances. Під час cooldown ASG не запускатиме нові instances навіть якщо метрика перевищує ціль. Мале значення (60с) дозволяє швидко реагувати на зростання навантаження.
ScaleInCooldown
integer
Час очікування (секунди) між видаленням instances. Рекомендується встановлювати значно більшим за ScaleOutCooldown (300–600с), щоб уникнути передчасного видалення instances під час тимчасового спаду навантаження між піками.

Step Scaling Policy

Step Scaling дозволяє визначити нелінійну залежність між відхиленням метрики від порогу та кількістю instances, що додаються або видаляються. Це корисно, коли потрібна більш агресивна реакція при екстремальних відхиленнях.

Крок 1: Спочатку створіть CloudWatch Alarm для CPU:

  1. CloudWatch → AlarmsCreate alarmSelect metric
  2. EC2 → By Auto Scaling Group → dotnet-api-asgCPUUtilizationSelect metric
  3. Conditions: Threshold type: Static → Greater than: 70Next
  4. Actions: Remove default action → Next → Name: cpu-above-70Create alarm

Крок 2: Додайте Step Scaling Policy до ASG:

  1. EC2 → Auto Scaling Groupsdotnet-api-asg → вкладка Automatic scaling
  2. Create dynamic scaling policy
  3. Policy type: Step scaling
  4. Scaling policy name: cpu-step-scaling
  5. CloudWatch alarm: оберіть cpu-above-70
  6. Take the action:
    • Add 1 capacity unit when 0 ≤ alarm value < 10
    • Add 2 capacity units when 10 ≤ alarm value < 20
    • Add 4 capacity units when 20 ≤ alarm value
  7. Instance warmup: 60 seconds → Create
Step Scaling Policy → створення
$ aws autoscaling put-scaling-policy --policy-name cpu-step-scaling ...
Step Scaling Policy ARN:
arn:aws:autoscaling:eu-central-1:123456789012:scalingPolicy:abc123:autoScalingGroupName/dotnet-api-asg:policyName/cpu-step-scaling
$ aws cloudwatch put-metric-alarm --alarm-name cpu-above-70 --alarm-actions ...
(no output = success — Alarm прив'язаний до Policy)

Конфігурація кроків масштабування:

Конфігурація Step Scaling Policy
{
    "PolicyName": "cpu-step-scaling",
    "PolicyType": "StepScaling",
    "AdjustmentType": "ChangeInCapacity",
    "StepAdjustments": [
        {
            "MetricIntervalLowerBound": 0,
            "MetricIntervalUpperBound": 10,
            "ScalingAdjustment": 1
        },
        {
            "MetricIntervalLowerBound": 10,
            "MetricIntervalUpperBound": 20,
            "ScalingAdjustment": 2
        },
        {
            "MetricIntervalLowerBound": 20,
            "ScalingAdjustment": 4
        }
    ]
}
AdjustmentType
enum required
Спосіб інтерпретації значення ScalingAdjustment:
  • ChangeInCapacity — змінити DesiredCapacity на вказане число (додати або видалити N instances)
  • ExactCapacity — встановити DesiredCapacity рівно до вказаного числа
  • PercentChangeInCapacity — змінити DesiredCapacity на вказаний відсоток від поточного значення
MetricIntervalLowerBound / UpperBound
number
Визначають діапазон відхилення метрики від порогового значення (threshold). Наприклад, якщо threshold = 70% CPU, то LowerBound: 0, UpperBound: 10 означає діапазон 70–80% CPU.
ScalingAdjustment
integer required
Кількість instances для додавання (позитивне значення) або видалення (від'ємне значення) при спрацюванні цього кроку.
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

title "Step Scaling: поведінка при CPU threshold = 70%"

rectangle "CPU 70-80%\n(перевищення на 0-10%)" as S1 #d1fae5
rectangle "CPU 80-90%\n(перевищення на 10-20%)" as S2 #fef3c7
rectangle "CPU > 90%\n(перевищення > 20%)" as S3 #fee2e2

rectangle "+1 instance\n(помірна реакція)" as R1 #bbf7d0
rectangle "+2 instances\n(активна реакція)" as R2 #fde68a
rectangle "+4 instances\n(агресивна реакція)" as R3 #fca5a5

S1 --> R1
S2 --> R2
S3 --> R3

note bottom
    При поверненні CPU нижче 70%
    ASG починає видаляти instances
    (Scale In з cooldown 300с)
end note
@enduml

Scheduled Scaling

Масштабування за розкладом підходить для систем з передбачуваними та циклічними піками навантаження: робочі дні vs вихідні, ранок vs ніч, початок місяця vs середина місяця.

  1. EC2 → Auto Scaling Groupsdotnet-api-asg → вкладка Automatic scaling
  2. Розгорніть Scheduled actionsCreate scheduled action
  3. Name: scale-up-morning
  4. Desired capacity: 5, Min: 5, Max: (залишити порожнім)
  5. Recurrence: Cron: 0 8 * * MON-FRI (UTC)
  6. Create → повторіть для scale-down-evening з desired=2, min=2, cron 0 20 * * MON-FRI

Всі часи в Scheduled Scaling задаються у UTC. Для України (UTC+2/UTC+3) враховуйте різницю: 08:00 UTC = 10:00/11:00 за київським часом.

Scheduled Scaling → перевірка
$ aws autoscaling describe-scheduled-actions --auto-scaling-group-name dotnet-api-asg ...
-----------------------------------------------------
| DescribeScheduledActions |
+--------------------+----------------+-----------+
| Name | Recurrence | Desired |
+--------------------+----------------+-----------+
| scale-up-morning | 0 8 * * MON-FRI | 5 |
| scale-down-evening | 0 20 * * MON-FRI| 2 |
+--------------------+----------------+-----------+

ALB Listener Rules: маршрутизація запитів

Path-based Routing

Path-based routing дозволяє направляти запити на різні Target Groups залежно від URI-шляху. Це фундаментальна можливість для реалізації мікросервісної архітектури за єдиним ALB.

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

actor "Client" as C

rectangle "ALB\napi.example.com:443" as ALB #fef3c7

package "UserService Target Group\n(EC2 з UserService .NET API)" as UTG #d1fae5 {
    node "EC2 #1" as U1 #bbf7d0
    node "EC2 #2" as U2 #bbf7d0
}

package "OrderService Target Group\n(EC2 з OrderService .NET API)" as OTG #dbeafe {
    node "EC2 #3" as O1 #bfdbfe
    node "EC2 #4" as O2 #bfdbfe
}

rectangle "Fixed Response\n200 OK (без сервера)" as FR #f0fdf4

C --> ALB

ALB --> UTG : "IF path = /api/users/*"
ALB --> OTG : "IF path = /api/orders/*"
ALB --> FR : "IF path = /health"

note right of FR
    Для health check ALB
    не потрібен сервер —
    Fixed Response повертає
    200 миттєво
end note

@enduml

Налаштування Path-based routing:

  1. EC2 → Load Balancersdotnet-api-alb → вкладка Listeners
  2. Оберіть HTTPS:443Manage rulesAdd rule
  3. Name: users-service-rule
  4. Add conditionPath/api/users/*Confirm
  5. Add actionForward to target groups → оберіть user-service-tgConfirm
  6. Priority: 10Create
  7. Повторіть для OrderService: path /api/orders/*, priority 20
  8. Переконайтесь, що default rule (пріоритет найнижчий) залишається як fallback
create-rule → Path-based routing
$ aws elbv2 create-rule --priority 10 --conditions '[{"Field":"path-pattern"...}]' ...
{
"Rules": [{
"RuleArn": "arn:aws:elasticloadbalancing:eu-central-1:...:rule/app/...",
"Priority": "10",
"Conditions": [{"Field": "path-pattern", "Values": ["/api/users/*"]}],
"IsDefault": false
}]
}

Host-based Routing

Host-based routing маршрутизує запити на основі значення HTTP-заголовка Host, що дозволяє обслуговувати кілька доменних імен за єдиним ALB:

ALB (один балансувальник, одна IP-адреса)
├── api.example.com    → API Target Group      (EC2 з .NET Web API)
├── admin.example.com  → Admin Target Group    (EC2 з Admin Dashboard)
└── static.example.com → Fixed Response 301   → redirect to CloudFront

Це суттєво знижує витрати: замість окремого ALB для кожного сервісу — один ALB для всіх.

Налаштування Host-based routing:

  1. EC2 → Load Balancersdotnet-api-alb → вкладка ListenersHTTPS:443Manage rules
  2. Add rule → Name: api-host-rule
  3. Add conditionHost headerapi.example.comConfirm
  4. Add actionForwardapi-tgConfirm → Priority: 10Create
  5. Повторіть для admin.example.comadmin-tg, priority 20

Sticky Sessions для ASP.NET сесій

Проблема стану у розподіленій системі

При використанні ASP.NET Core з AddSession() без розподіленого сховища (у конфігурації за замовчуванням), дані сесії зберігаються в оперативній пам'яті конкретного EC2 instance. При балансуванні навантаження між кількома instances виникає критична проблема: якщо користувач після авторизації потрапляє на Instance 1, де зберігається його сесія, а наступний запит ALB направляє на Instance 2 — Instance 2 не знає про існування цієї сесії, і користувач виглядає як неавторизований.

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

actor "User\n(authenticated)" as U

rectangle "ALB" as ALB #fef3c7

package "ASG" {
    node "EC2 #1\nSession: {userId: 42}\n← сесія тут" as EC1 #bbf7d0
    node "EC2 #2\nNo session for this user\n← сесія відсутня" as EC2 #fca5a5
}

U --> ALB : "Request 1\n(login → session created)"
ALB --> EC1 : "→ EC2 #1\n✓ Session created"

U --> ALB : "Request 2\n(view profile)"
ALB --> EC2 : "→ EC2 #2 (Round Robin!)\n✗ Session not found!"

note bottom of EC2
    ПРОБЛЕМА: без sticky sessions
    ALB може направити запит
    на instance без сесії
end note

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

actor "User\n(authenticated)" as U

rectangle "ALB\n(Sticky Sessions увімкнено\nAWSALB cookie)" as ALB #fef3c7

package "ASG" {
    node "EC2 #1\nSession: {userId: 42}" as EC1 #bbf7d0
    node "EC2 #2" as EC2 #e5e7eb
}

U --> ALB : "Request 1\n(login)"
ALB --> EC1 : "→ EC2 #1\nSet-Cookie: AWSALB=abc123; Path=/; HttpOnly"

U --> ALB : "Request 2\nCookie: AWSALB=abc123"
ALB --> EC1 : "→ EC2 #1 (завжди!)\nALB читає cookie та\nнаправляє на той самий instance"

note bottom
    РІШЕННЯ (тимчасове): sticky sessions.
    ПРАВИЛЬНЕ рішення: розподілений кеш (Redis)
end note

@enduml

Sticky Sessions (Session Affinity) — механізм, за якого ALB встановлює спеціальний cookie (AWSALB) при першому запиті користувача та у всіх наступних запитах направляє його до того самого instance, що обробляв перший запит.

Target Groups → оберіть TG → вкладка AttributesEditStickiness → Enable → Type: Load balancer generated cookie → Duration: 1 day → Save changes
Sticky sessions — це технічний борг, а не вирішення проблеми. Sticky sessions знижують ефективність балансування: якщо один instance обслуговує «важких» користувачів з довгими сесіями, він стає перевантаженим, тоді як інші instances простоюють. Єдине правильне рішення — перейти на розподілений кеш для зберігання стану сесії.

Правильна реалізація: розподілені сесії через Redis

Program.cs
// Правильний підхід для production: сесії у Redis
// Будь-який instance може обробити запит будь-якого користувача
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration["Redis:ConnectionString"];
    options.InstanceName = "MyApp:Sessions:";
});

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
    options.Cookie.SameSite = SameSiteMode.Strict;
});

// Middleware
app.UseSession();

Практичний приклад: ALB + ASG + HTTPS від А до Я

У цьому розділі ми побудуємо повноцінну production-готову інфраструктуру покроково: C# проєкт → Базовий EC2 інстанс → Custom AMI з .NET API → SSL-сертифікат → Security Groups → Target Group → ALB → Launch Template → Auto Scaling Group.

Крок 1: Створення C# проєкту для тестування

Для демонстрації роботи Application Load Balancer нам потрібен вебзастосунок, який буде повертати інформацію про сервер, що обробляє запит. Ми створимо простий API на ASP.NET Core (.NET 10), який виводить ім'я хоста (інстансу), HTTP-заголовки (включаючи X-Forwarded-* від балансера), та має /health endpoint для Target Group, а також /stress для симуляції навантаження на процесор.


Крок 2: Створення та налаштування базового EC2 інстансу

Тепер нам потрібно створити базовий EC2 інстанс, встановити на нього .NET 10, розгорнути наш C# застосунок та налаштувати його запуск як фонову службу (systemd service). З цього налаштованого інстансу ми згодом зробимо Custom AMI.

1. Створення EC2 інстансу

Створіть віртуальну машину (Instance) в AWS, яка буде нашою базою для AMI.

  1. Перейдіть до EC2 DashboardInstancesLaunch instances.
  2. Вкажіть ім'я: dotnet-api-base.
  3. Оберіть AMI: Ubuntu Server 24.04 LTS (64-bit x86).
  4. Instance type: t3.micro (входить до Free Tier).
  5. Key pair: оберіть вашу наявну пару ec2-lab-key або створіть нову.
  6. Security Groups (Налаштування мережі):
    • Створіть нову Security Group dotnet-api-base-sg.
    • Додайте Inbound правила:
      • SSH (порт 22) → джерело: My IP (для безпечного підключення).
      • Custom TCP (порт 5000) → джерело: Anywhere IPv4 (0.0.0.0/0) (тимчасово для тестування з інтернету).
  7. Натисніть Launch instance.
REGION="eu-central-1"
KEY_NAME="ec2-lab-key"

# 1. Отримання default VPC ID
VPC_ID=$(aws ec2 describe-vpcs \
    --filters "Name=isDefault,Values=true" \
    --query "Vpcs[0].VpcId" --output text --region "$REGION")

# 2. Створення тимчасової Security Group для налаштування
BASE_SG=$(aws ec2 create-security-group \
    --group-name dotnet-api-base-sg \
    --description "Temporary SG for EC2 setup" \
    --vpc-id "$VPC_ID" \
    --region "$REGION" \
    --query GroupId --output text)

# Дозволити SSH
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws ec2 authorize-security-group-ingress \
    --group-id "$BASE_SG" \
    --protocol tcp --port 22 \
    --cidr "${MY_IP}/32" --region "$REGION"

# Дозволити порт 5000 для тестування
aws ec2 authorize-security-group-ingress \
    --group-id "$BASE_SG" \
    --protocol tcp --port 5000 \
    --cidr 0.0.0.0/0 --region "$REGION"

# 3. Запуск інстансу Ubuntu 24.04 LTS (знайдіть актуальний AMI ID для вашого регіону)
AMI_ID=$(aws ec2 describe-images \
    --owners amazon \
    --filters "Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*" \
    --query "sort_by(Images, &CreationDate)[-1].ImageId" \
    --output text --region "$REGION")

INSTANCE_ID=$(aws ec2 run-instances \
    --image-id "$AMI_ID" \
    --instance-type t3.micro \
    --key-name "$KEY_NAME" \
    --security-group-ids "$BASE_SG" \
    --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=dotnet-api-base}]" \
    --query "Instances[0].InstanceId" --output text --region "$REGION")

echo "Instance ID: $INSTANCE_ID"

# Очікування запуску та отримання Public IP
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" --region "$REGION"
PUBLIC_IP=$(aws ec2 describe-instances \
    --instance-ids "$INSTANCE_ID" \
    --query "Reservations[0].Instances[0].PublicIpAddress" \
    --output text --region "$REGION")

echo "Public IP: $PUBLIC_IP"

::

2. Встановлення ASP.NET Core Runtime

Підключіться до інстансу через SSH та встановіть середовище виконання .NET 10.

# Підключення до інстансу (замініть EC2_PUBLIC_IP)
ssh -i ~/.ssh/ec2-lab-key.pem ubuntu@EC2_PUBLIC_IP

# Оновлення пакетів та встановлення ASP.NET Core Runtime 10
sudo apt-get update
sudo apt-get install -y aspnetcore-runtime-10.0
Встановлення .NET Runtime на EC2
$ ssh -i ~/.ssh/ec2-lab-key.pem ubuntu@54.93.120.45
Welcome to Ubuntu 24.04 LTS...
ubuntu@ip-172-31-10-25:~$ sudo apt-get update && sudo apt-get install -y aspnetcore-runtime-10.0
... (installing packages) ...
Setting up aspnetcore-runtime-10.0 ... done.
ubuntu@ip-172-31-10-25:~$ dotnet --list-runtimes
Microsoft.AspNetCore.App 10.0.0 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.0 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

3. Публікація та розгортання проєкту

Зберіть ваш C# проєкт локально та скопіюйте скомпільовані файли на EC2.

# 1. Локально у папці проєкту публікуємо під Linux x64
dotnet publish -c Release -r linux-x64 --self-contained false -o ./publish

# 2. Переносимо файли на EC2 (створюємо папку app)
ssh -i ~/.ssh/ec2-lab-key.pem ubuntu@EC2_PUBLIC_IP "mkdir -p ~/app"
scp -i ~/.ssh/ec2-lab-key.pem -r ./publish/* ubuntu@EC2_PUBLIC_IP:~/app/
Локальна збірка та копіювання через SCP
$ dotnet publish -c Release -r linux-x64 --self-contained false -o ./publish
MSBuild version 17.12.0 for .NET
ec2lab-api -> /path/to/bin/Release/net10.0/linux-x64/ec2lab-api.dll
SUCCESS: Publish completed in 2.4s.
$ scp -i ~/.ssh/ec2-lab-key.pem -r ./publish/* ubuntu@54.93.120.45:~/app/
ec2lab-api.dll 100% 120KB 1.2MB/s 00:00
ec2lab-api.runtimeconfig.json 100% 340B 3.4KB/s 00:00
appsettings.json 100% 150B 1.5KB/s 00:00

4. Налаштування Systemd служби

Для того, щоб застосунок автоматично запускався при старті сервера та працював у фоні, ми створимо службу systemd.

# 1. Створення файлу конфігурації служби
sudo nano /etc/systemd/system/ec2lab-api.service

Вставте наступну конфігурацію:

[Unit]
Description=EC2 Lab .NET API Service
After=network.target

[Service]
WorkingDirectory=/home/ubuntu/app
ExecStart=/usr/bin/dotnet /home/ubuntu/app/ec2lab-api.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=ec2lab-api
User=ubuntu
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target
# 2. Перезавантаження демона та запуск служби
sudo systemctl daemon-reload
sudo systemctl enable ec2lab-api
sudo systemctl start ec2lab-api

# 3. Перевірка статусу служби
sudo systemctl status ec2lab-api
Активація systemd сервісу
ubuntu@ip-172-31-10-25:~$ sudo systemctl enable ec2lab-api
Created symlink /etc/systemd/system/multi-user.target.wants/ec2lab-api.service → /etc/systemd/system/ec2lab-api.service.
ubuntu@ip-172-31-10-25:~$ sudo systemctl start ec2lab-api
ubuntu@ip-172-31-10-25:~$ sudo systemctl status ec2lab-api
● ec2lab-api.service - EC2 Lab .NET API Service
Loaded: loaded (/etc/systemd/system/ec2lab-api.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2026-05-25 15:00:00 UTC; 5s ago
Main PID: 12345 (dotnet)
Tasks: 12 (limit: 1141)
CGroup: /system.slice/ec2lab-api.service
└─12345 /usr/bin/dotnet /home/ubuntu/app/ec2lab-api.dll

5. Тестування роботи застосунку

Перевірте, чи працює ваш застосунок локально на EC2, а потім з вашого локального комп'ютера.

#  Локально на EC2 інстансі:
curl http://localhost:5000/health

#  З вашого власного (робочого) комп'ютера (замініть EC2_PUBLIC_IP):
curl http://EC2_PUBLIC_IP:5000/
Перевірка відповіді API
$ curl http://54.93.120.45:5000/
{
"message": "Hello from load-balanced EC2 instance!",
"serverName": "ip-172-31-10-25",
"localIPs": ["172.31.10.25"],
"requestCount": 1,
"dotnetVersion": "10.0.0",
"headers": {
"Host": "54.93.120.45:5000",
"User-Agent": "curl/8.4.0"
},
"time": "2026-05-25T12:05:00Z"
}

::

Базовий інстанс повністю готовий. Тепер можна переходити до створення AMI образу на його основі.


Крок 3: Підготовка Custom AMI з .NET 10

Перед початком нам потрібен Custom AMI — образ EC2 instance з вже встановленим .NET 10 та розгорнутим застосунком. ASG буде створювати нові instances саме з цього AMI.

  1. EC2 → Instances → оберіть ваш налаштований instance
  2. ActionsImage and templatesCreate image
  3. Image name: dotnet-api-ready-v1
  4. Create image → зачекайте 5–10 хвилин поки AMI стане available
  5. Запишіть AMI ID: EC2 → AMIs → скопіюйте ID (формат: ami-0a1b2c3d4e5f67890)
aws ec2 create-image
$ CUSTOM_AMI=$(aws ec2 create-image ...)
Custom AMI ID: ami-0a1b2c3d4e5f67890
$ aws ec2 wait image-available --image-ids $CUSTOM_AMI ...
(waiting ~5-10 minutes...)
AMI is ready: ami-0a1b2c3d4e5f67890

Крок 4: SSL/TLS сертифікат через AWS Certificate Manager (ACM)

AWS Certificate Manager (ACM) — повний огляд

AWS Certificate Manager (ACM) — це повністю керований сервіс AWS, що забезпечує весь lifecycle SSL/TLS сертифікатів: запит, валідацію права власності на домен, зберігання, прив'язку до AWS-сервісів та автоматичне поновлення до закінчення терміну дії. Ключова перевага ACM перед традиційними центрами сертифікації (Let's Encrypt, Comodo, DigiCert) — повна автоматизація та безкоштовність для сертифікатів, що використовуються з AWS-сервісами.

Принцип роботи ACM. Традиційний підхід до SSL-сертифікатів вимагає: генерації приватного ключа, створення CSR (Certificate Signing Request), верифікації у центрі сертифікації, встановлення сертифіката на сервер, моніторингу дати закінчення та ручного поновлення. ACM автоматизує всі ці кроки: AWS генерує та зберігає приватний ключ у захищеному апаратному модулі (HSM), проводить валідацію, встановлює сертифікат у ALB/CloudFront та поновлює його автоматично за 60 днів до закінчення.

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

title "ACM Certificate Lifecycle"

participant "Розробник" as DEV
participant "ACM" as ACM #fef3c7
participant "DNS-провайдер" as DNS #dbeafe
participant "ALB / CloudFront" as ALB #d1fae5
participant "Браузер клієнта" as BR

DEV -> ACM : Request certificate\n(domain: api.example.com)
ACM -> DEV : CNAME record для DNS валідації\n(_abc123.api.example.com → _def456.acm-validations.aws.)
DEV -> DNS : Додати CNAME запис
DNS -> ACM : (ACM перевіряє CNAME ~5-30 хв)
ACM -> DEV : Status: Issued ✓

DEV -> ALB : Прив'язати сертифікат\n(до HTTPS Listener)

loop "Кожні 60 днів до закінчення (автоматично)"
    ACM -> DNS : Перевірка CNAME запису
    alt "CNAME ще існує"
        ACM -> ACM : Поновлення сертифіката
        ACM -> ALB : Оновлення без простою
    else "CNAME видалено"
        ACM -> DEV : Попередження: поновлення неможливе
    end
end

note over BR
    Браузер бачить
    замок 🔒 та HTTPS
end note

@enduml

Типи валідації домену

ACM підтримує два методи валідації права власності на домен:

DNS Validation (рекомендований)

Принцип: ACM надає CNAME-запис, який необхідно додати до DNS зони домену. ACM перевіряє наявність цього запису.

Переваги:

  • Автоматичне поновлення (CNAME залишається назавжди)
  • Не потребує доступу до електронної пошти
  • Підходить для wildcard сертифікатів
  • Один CNAME-запис для всіх сертифікатів одного домену

Недоліки: Потребує доступу до DNS панелі

Email Validation

Принцип: ACM надсилає листа на стандартні адміністративні адреси: admin@, webmaster@, hostmaster@, administrator@, postmaster@ вашого домену.

Переваги: Швидко, не потребує DNS доступу

Недоліки:

  • Поновлення не є повністю автоматичним
  • Потребує активних поштових скриньок
  • Не підходить для wildcard

Типи сертифікатів

Single-domain сертифікат покриває лише один конкретний домен: api.example.com. Запит на www.example.com або admin.example.com не буде захищений цим сертифікатом.

Wildcard сертифікат (*.example.com) покриває всі субдомени першого рівня: api.example.com, admin.example.com, static.example.com. Важливо: wildcard не покриває кореневий домен example.com та вкладені субдомени api.v2.example.com. Для повного покриття необхідно запросити обидва: example.com та *.example.com в одному сертифікаті (Subject Alternative Names).

Multi-domain сертифікат (SAN) — один сертифікат покриває до 10 різних доменів: example.com, api.example.com, anothersite.com. Корисний при консолідації кількох доменів.

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

title "Типи ACM сертифікатів: покриття доменів"

rectangle "Single-domain\n(api.example.com)" as SD #d1fae5 {
    rectangle "api.example.com ✓" as S1 #bbf7d0
    rectangle "www.example.com ✗" as S2 #fca5a5
    rectangle "admin.example.com ✗" as S3 #fca5a5
}

rectangle "Wildcard\n(*.example.com)" as WC #dbeafe {
    rectangle "api.example.com ✓" as W1 #bbf7d0
    rectangle "admin.example.com ✓" as W2 #bbf7d0
    rectangle "example.com ✗" as W3 #fca5a5
    rectangle "api.v2.example.com ✗" as W4 #fca5a5
}

rectangle "Multi-domain SAN\n(example.com + *.example.com)" as MD #fff3e0 {
    rectangle "example.com ✓" as M1 #bbf7d0
    rectangle "api.example.com ✓" as M2 #bbf7d0
    rectangle "admin.example.com ✓" as M3 #bbf7d0
    rectangle "anothersite.com ✓" as M4 #bbf7d0
}

@enduml

Обмеження ACM: безкоштовність та .pp.ua домени

ACM сертифікати безкоштовні лише при використанні з AWS-сервісами. Конкретно: ALB, CloudFront, API Gateway, Elastic Beanstalk, App Runner. Ви не можете завантажити приватний ключ ACM-сертифіката та встановити його на власний сервер поза AWS — це архітектурне обмеження безпеки: приватний ключ ніколи не покидає HSM AWS.

ACM не є заміною Let's Encrypt для зовнішніх серверів. Якщо ваш застосунок розміщений на власному VPS або хостингу поза AWS — ACM вам не допоможе. Використовуйте Let's Encrypt (Certbot) або платні сертифікати від Comodo/DigiCert.

Чи буде .pp.ua домен безкоштовним з ACM?

Коротка відповідь: ACM-сертифікат для домену .pp.ua буде безкоштовним. Сам сертифікат ACM завжди безкоштовний незалежно від доменного розширення — .com, .ua, .pp.ua, .io тощо. Однак повна відповідь складніша:

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

title ".pp.ua домен + ACM: аналіз витрат"

' Оголошення елементів
rectangle "Реєстрація домену .pp.ua" as REG #fef3c7
rectangle "ACM сертифікат" as ACM #d1fae5
rectangle "ALB (Application Load Balancer)" as ALB #fef3c7

' Прив'язка нотаток до відповідних елементів
note right of REG
    myapp.pp.ua
    Вартість реєстрації:
    БЕЗКОШТОВНО (безстроково)
    від Hosting Ukraine та
    деяких інших реєстраторів
end note

note right of ACM
    SSL/TLS для myapp.pp.ua
    Вартість: БЕЗКОШТОВНО
    (при використанні з ALB)
    Автоматичне поновлення: ТАК
end note

note right of ALB
    ПЛАТНИЙ ресурс:
    ~$16-22/місяць
    (+ $0.008 за LCU-hour)
end note

' Зв'язки між елементами
REG -down-> ACM : DNS validation CNAME
ACM -down-> ALB : Прив'язка до HTTPS Listener

' Фінальний підсумок знизу
note bottom of ALB
    Загальна вартість для .pp.ua:
    Домен: $0 (безкоштовно)
    ACM: $0 (безкоштовно)
    ALB: ~$16-22/місяць ← основна витрата
end note

@enduml

Деталі щодо .pp.ua:

1. Реєстрація домену .pp.ua. Домени у зоні .pp.ua є безкоштовними для фізичних осіб в Україні — їх надає HOSTMASTER (адміністратор .ua) безкоштовно через уповноважених реєстраторів (наприклад, nic.ua). Це означає, що на відміну від .com ($12-15/рік) або .ua ($10-20/рік) — .pp.ua домен не потребує щорічних платежів за реєстрацію.

2. ACM сертифікат для .pp.ua. ACM підтримує будь-який домен, включаючи .pp.ua. Обмежень за TLD не існує. Єдина умова — ви повинні мати можливість додати CNAME-запис до DNS зони цього домену для підтвердження (DNS validation). Більшість DNS-панелей для .pp.ua підтримують CNAME-записи, тому проблем не виникне.

3. Підводні камені .pp.ua для production. Хоча технічно все працює, для production-середовища слід врахувати:

  • .pp.ua домени сприймаються як «персональні» та можуть знижувати довіру корпоративних користувачів
  • Деякі корпоративні фаєрволи можуть блокувати нестандартні TLD
  • Для академічних та навчальних проектів .pp.ua — цілком прийнятний вибір
Рекомендація для студентів: Домен .pp.ua є ідеальним вибором для навчальних проектів та лабораторних робіт — безкоштовна реєстрація + безкоштовний ACM сертифікат. Єдина реальна витрата — ALB (~$16-22/місяць) або EC2 instances. Після завершення лабораторної роботи — видаліть ALB, і витрат не буде.

Регіональні обмеження ACM

Критично важлива деталь: ACM сертифікат дійсний лише в тому регіоні AWS, де він був виданий, — за одним виключенням.

  • ALB, ASG, API Gateway: сертифікат ACM має бути виданий у тому самому регіоні, що і ресурс. Для ALB у eu-central-1 — сертифікат потрібен у eu-central-1.
  • CloudFront: є глобальним сервісом і вимагає сертифікат виключно у регіоні us-east-1 (N. Virginia), незалежно від того, де розгорнуто ваш застосунок.
Якщо ви плануєте використовувати CloudFront — запросіть два сертифікати ACM: один у eu-central-1 (для ALB), інший у us-east-1 (для CloudFront).

Отримання ACM сертифіката: покрокова інструкція

Крок 1: Відкрити ACM у потрібному регіоні

  1. AWS Console → пошук Certificate Manager → переконайтесь, що в правому верхньому куті обрано EU (Frankfurt) eu-central-1
  2. Request a certificateRequest a public certificateNext

Крок 2: Вказати домен(и) 3. Fully qualified domain name: api.yoursite.com 4. Add another name to this certificate*.yoursite.com (wildcard — покриє всі субдомени) 5. Якщо хочете покрити й кореневий домен — додайте yoursite.com окремим рядком

Крок 3: Вибрати метод валідації 6. Validation method:DNS validation (рекомендовано — підтримує автопоновлення) 7. Key algorithm: RSA 2048 (стандарт; ECDSA P-256 — швидший, але менша сумісність зі старими клієнтами) 8. Request → натисніть View certificate

Крок 4: Додати CNAME до DNS 9. Сторінка сертифіката покаже статус Pending validation та секцію Domains з CNAME-записом:

  • Name: _abc123def456.api.yoursite.com
  • Value: _xyz789.acm-validations.aws.
  1. Додайте цей CNAME у вашому DNS-провайдері (Route 53, Cloudflare, NIC.UA тощо)
  2. (Якщо використовуєте Route 53) Натисніть Create records in Route 53 — ACM автоматично додасть CNAME
  3. Статус зміниться на Issued протягом 5–30 хвилин
ACM → запит сертифіката та DNS валідація
$ CERT_ARN=$(aws acm request-certificate --domain-name yoursite.com ...)
Certificate ARN: arn:aws:acm:eu-central-1:123456789012:certificate/a1b2c3d4-1234-5678-abcd-ef1234567890
$ aws acm describe-certificate ... --query "...DomainValidationOptions..." --output table
-------------------------------------------------------------------------------------------
| DescribeCertificate |
+------------------+--------------------------------------+-------------------------------+
| Domain | Name | Value |
+------------------+--------------------------------------+-------------------------------+
| yoursite.com | _abc123.yoursite.com. | _xyz789.acm-validations.aws. |
| *.yoursite.com | _abc123.yoursite.com. | _xyz789.acm-validations.aws. |
+------------------+--------------------------------------+-------------------------------+
← Обидва домени share один CNAME (якщо вони на одному base domain)
# Після додавання CNAME до DNS:
$ aws acm describe-certificate ... --query "Certificate.Status"
"Issued"
Прив'язка сертифіката до ALB HTTPS Listener
$ aws elbv2 create-listener --protocol HTTPS --port 443 --certificates CertificateArn=... ...
{
"Listeners": [{
"ListenerArn": "arn:aws:elasticloadbalancing:eu-central-1:...:listener/app/.../...",
"Port": 443,
"Protocol": "HTTPS",
"SslPolicy": "ELBSecurityPolicy-TLS13-1-2-2021-06",
"Certificates": [{ "CertificateArn": "arn:aws:acm:..." }]
}]
}
Для отримання сертифіката ACM потрібен власний домен. Сам сертифікат є безкоштовним, але реєстрація домену коштує від $12/рік для .com. Домени .pp.ua є безкоштовними. Якщо домену немає — пропустіть цей крок і використовуйте HTTP для лабораторної роботи.

Крок 5: Створення Security Groups

Правильне налаштування Security Groups є критичним для безпеки інфраструктури. Загальний принцип: трафік з інтернету надходить лише до ALB, а EC2 instances приймають трафік виключно від ALB, але не безпосередньо з інтернету.

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

actor "Internet\n0.0.0.0/0" as I

rectangle "alb-sg\n(Security Group)" as ALBSG #fef3c7 {
    note right
      Inbound:
      TCP :80 from 0.0.0.0/0
      TCP :443 from 0.0.0.0/0
      Outbound: All traffic
    end note
}

rectangle "ec2-asg-sg\n(Security Group)" as EC2SG #d1fae5 {
    note right
      Inbound:
      TCP :5000 from alb-sg (SG reference!)
      TCP :22 from YOUR_IP/32
      Outbound: All traffic
    end note
}

package "EC2 Instances (ASG)" as EC2 #bbf7d0 {
    node ".NET API :5000" as API
}

I --> ALBSG : "HTTP/HTTPS"
ALBSG --> EC2SG : "Лише порт 5000\nджерело = SG ID (не IP!)"
EC2SG --> API

note bottom
    Security Group reference замість CIDR:
    Якщо IP ALB зміниться — правило
    залишається дійсним автоматично.
    Це best practice AWS.
end note

@enduml

Security Group для ALB:

  1. VPC → Security groupsCreate security group
  2. Name: alb-sg, VPC: default
  3. Inbound rules:
    • HTTP (80) → Anywhere IPv4 (0.0.0.0/0)
    • HTTPS (443) → Anywhere IPv4 (0.0.0.0/0)
  4. Create

Security Group для EC2:

  1. Create security group → Name: ec2-asg-sg
  2. Inbound rules:
    • Custom TCP, Port 5000, Source: alb-sg (вкажіть SG ID, не CIDR!)
    • SSH (22) → My IP
  3. Create
Security Groups setup
$ VPC_ID=$(aws ec2 describe-vpcs ...)
VPC ID: vpc-0a1b2c3d4e5f67890
$ ALB_SG=$(aws ec2 create-security-group --group-name alb-sg ...)
ALB SG: sg-0abc123def456789a
$ EC2_SG=$(aws ec2 create-security-group --group-name ec2-asg-sg ...)
EC2 SG: sg-0def456abc789012b

Крок 6: Створення Target Group

  1. EC2 → Target GroupsCreate target group
  2. Target type: Instances
  3. Target group name: dotnet-api-tg
  4. Protocol: HTTP, Port: 5000
  5. VPC: default
  6. Health checks:
    • Protocol: HTTP
    • Path: /health
    • Port: Traffic port (5000)
    • Healthy threshold: 2
    • Unhealthy threshold: 3
    • Timeout: 5 seconds
    • Interval: 15 seconds
    • Success codes: 200
  7. Create target group
  8. Запишіть ARN Target Group
aws elbv2 create-target-group
$ TG_ARN=$(aws elbv2 create-target-group --name dotnet-api-tg ...)
Target Group ARN:
arn:aws:elasticloadbalancing:eu-central-1:123456789012:targetgroup/dotnet-api-tg/abc123def456

Крок 7: Створення Application Load Balancer

  1. EC2 → Load BalancersCreate load balancer
  2. Оберіть Application Load BalancerCreate
  3. Basic configuration:
    • Name: dotnet-api-alb
    • Scheme: Internet-facing
    • IP address type: IPv4
  4. Network mapping:
    • VPC: default
    • Mappings: ✅ оберіть мінімум 2 Availability Zones
  5. Security groups: оберіть alb-sg
  6. Listeners:
    • HTTP:80 → Forward to: dotnet-api-tg
    • Add listener → HTTPS:443 → Forward to: dotnet-api-tg → SSL cert: ваш ACM сертифікат
  7. Create load balancer
  8. Запишіть DNS name ALB
aws elbv2 create-load-balancer
$ ALB_ARN=$(aws elbv2 create-load-balancer --name dotnet-api-alb ...)
ALB ARN: arn:aws:elasticloadbalancing:eu-central-1:...
$ echo "ALB DNS: $ALB_DNS"
ALB DNS: dotnet-api-alb-123456789.eu-central-1.elb.amazonaws.com

Крок 8: HTTP → HTTPS Redirect

Для production-середовища обов'язково налаштуйте автоматичний редирект з HTTP на HTTPS. Без цього частина користувачів буде використовувати незахищений HTTP-протокол навіть за наявності HTTPS.

  1. EC2 → Load Balancers → dotnet-api-alb → вкладка Listeners
  2. Оберіть HTTP:80Edit listener
  3. Default actions: видаліть Forward → Redirect
    • Protocol: HTTPS, Port: 443, Status code: 301 (Moved Permanently)
  4. Save changes

Крок 9: Launch Template для ASG

Launch Template є специфікацією, за якою ASG автоматично створює нові EC2 instances при масштабуванні. User Data скрипт у Launch Template запускається при першому старті кожного нового instance та забезпечує автоматичний запуск .NET API.

  1. EC2 → Launch TemplatesCreate launch template
  2. Launch template name: dotnet-api-lt
  3. Template version description: v1 - .NET 10 API
  4. AMI: My AMIs → оберіть dotnet-api-ready-v1
  5. Instance type: t3.micro
  6. Key pair: ec2-lab-key
  7. Security groups: ec2-asg-sg
  8. Advanced details → User data:
#!/bin/bash
# Цей скрипт запускається ONE TIME при першому старті нового EC2 instance
# systemd сервіс вже налаштований у Custom AMI

systemctl start ec2lab-api
systemctl enable ec2lab-api

# Логуємо успішний запуск
echo "$(date): .NET API service started" >> /var/log/userdata.log
  1. Create launch template
aws ec2 create-launch-template
$ LT_ID=$(aws ec2 create-launch-template --launch-template-name dotnet-api-lt ...)
Launch Template ID: lt-0a1b2c3d4e5f67890

Крок 10: Створення Auto Scaling Group

  1. EC2 → Auto Scaling GroupsCreate Auto Scaling group
  2. Name: dotnet-api-asg
  3. Launch template: dotnet-api-ltNext
  4. Network:
    • VPC: default
    • Availability Zones: ті самі 2 AZ, що і для ALB → Next
  5. Load balancing:
    • Attach to an existing load balancer
    • Target groups: dotnet-api-tg
    • Turn on Elastic Load Balancing health checks
    • Health check grace period: 120 seconds → Next
  6. Group size:
    • Desired: 2, Minimum: 2, Maximum: 5
  7. Scaling policies:
    • ✅ Target tracking scaling policy
    • Metric: Average CPU utilization
    • Target value: 70
    • Create Auto Scaling group
aws autoscaling create-auto-scaling-group
$ aws autoscaling create-auto-scaling-group --auto-scaling-group-name dotnet-api-asg ...
(no output = success)
$ aws autoscaling put-scaling-policy --policy-name cpu-target-tracking ...
{
"PolicyARN": "arn:aws:autoscaling:eu-central-1:123456789012:scalingPolicy:...",
"Alarms": [...]
}

Крок 11: Перевірка роботи

Після створення ASG зачекайте ~3 хвилини, поки instances запустяться та пройдуть health check.

Перевірка балансування через ALB DNS
$ curl http://dotnet-api-alb-123456789.eu-central-1.elb.amazonaws.com/
{"message":"Hello from EC2!","server":"ip-172-31-10-25","dotnetVersion":"8.0.0"}
$ curl http://dotnet-api-alb-123456789.eu-central-1.elb.amazonaws.com/
{"message":"Hello from EC2!","server":"ip-172-31-22-43","dotnetVersion":"8.0.0"}
← поле "server" відрізняється між запитами — Round Robin балансування працює!

Перевірка стану targets у Target Group:

EC2 → Target Groupsdotnet-api-tg → вкладка Targets. Обидва instances мають бути у стані healthy.
Target Health status
---------------------------------------------
| DescribeTargetHealth |
+-------------------+------+---------------+
| ID | Port | Health |
+-------------------+------+---------------+
| i-0a1b2c3d4e5f678 | 5000 | healthy |
| i-0b2c3d4e5f6789a | 5000 | healthy |
+-------------------+------+---------------+

Крок 12: Тестування автоматичного масштабування

Симулюємо штучне навантаження на один із EC2 instances, щоб спостерігати автоматичний Scale Out:

# Підключіться до одного з EC2 через SSH
ssh -i ~/.ssh/ec2-lab-key.pem ubuntu@INSTANCE_IP

# Встановіть утиліту stress
sudo apt-get install -y stress

# Навантажте CPU на 5 хвилин (2 ядра)
stress --cpu 2 --timeout 300 &

Спостерігайте за поведінкою ASG:

EC2 → Auto Scaling Groupsdotnet-api-asg → вкладка Activity → через 1–3 хвилини після зростання CPU з'явиться подія Launching a new EC2 instance.
ASG масштабування в реальному часі
----------------------------
| DescribeAutoScalingGroups |
+------+---------+-----+---+
| Min | Desired | Max | Running |
+------+---------+-----+---------+
| 2 | 3 | 5 | 3 |
+------+---------+-----+---------+
← ASG збільшив Desired з 2 до 3 (Scale Out спрацював!)
Спостережуваність: Scale Out (додавання instances) спрацьовує швидко — через 1–3 хвилини після зростання CPU. Scale In (видалення instances) навмисно відбувається повільніше — через 5+ хвилин після нормалізації CPU, оскільки ScaleInCooldown = 300с. Це захищає від ситуації, коли ASG поспіхом видаляє instance під час тимчасового спаду між піками.

Крок 13: ОБОВ'ЯЗКОВО — Очищення ресурсів

ALB з двома EC2 t3.micro instances коштує приблизно: ALB ~$0.04/год + EC2 ×2 $0.046/год = **$62/місяць**. Завжди видаляйте ресурси після завершення лабораторної роботи!

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

  1. EC2 → Auto Scaling Groupsdotnet-api-asgDelete → підтвердіть (ASG автоматично завершить EC2 instances)
  2. Зачекайте ~2 хвилини поки всі instances перейдуть у стан terminated
  3. EC2 → Load Balancersdotnet-api-albActionsDelete
  4. EC2 → Target Groupsdotnet-api-tgActionsDelete
  5. EC2 → Launch Templatesdotnet-api-ltActionsDelete template
  6. EC2 → AMIsdotnet-api-ready-v1ActionsDeregister AMI
  7. EC2 → Snapshots → видаліть snapshot від вашого AMI
  8. VPC → Security Groups → видаліть alb-sg та ec2-asg-sg

Підсумок

Elastic Load Balancing

  • ALB — Layer 7 (HTTP/HTTPS): URL-маршрутизація, SSL termination, WebSocket, gRPC
  • NLB — Layer 4 (TCP/UDP): ultra-low latency, статична IP
  • Target Group — пул серверів з health check; ALB виключає unhealthy targets автоматично
  • Path/Host routing — один ALB для всіх мікросервісів

Auto Scaling Group

  • min/desired/max — три параметри що визначають границі масштабування
  • Launch Template — специфікація нового instance (AMI + type + SG + user data)
  • Target Tracking — «підтримуй CPU 70%», найпростіший підхід
  • ScaleInCooldown > ScaleOutCooldown — захист від передчасного масштабування вниз

Health Checks та Безпека

  • /health перевіряє реальні залежності (БД, Redis) — не лише return 200
  • Security Group reference замість CIDR для EC2 → ALB
  • Sticky sessions — технічний борг; правильне рішення — Redis
  • HTTP→HTTPS redirect обов'язковий для production

SSL/TLS та ACM

  • ACM — безкоштовні SSL-сертифікати для AWS-сервісів
  • DNS validation — рекомендований метод підтвердження
  • SSL termination відбувається на ALB — EC2 отримує незашифрований HTTP
  • ACM автоматично поновлює сертифікати до закінчення терміну дії

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

Рівень 1 — Концептуальне розуміння

Завдання 1. Поясніть різницю між ALB та NLB з точки зору моделі OSI. Що означає «Layer 7» та «Layer 4»? Наведіть конкретний приклад сценарію, де кожен тип є оптимальним вибором.

Завдання 2. Що відбудеться, якщо /health endpoint завжди повертає HTTP 200, але застосунок насправді не може підключитись до бази даних? Як ALB визначає стан сервера? Як правильно реалізувати health check?

Рівень 2 — Практична реалізація

Завдання 3. Налаштуйте ALB з path-based routing для двох .NET сервісів: UserService (порт 5001) за шляхом /api/users/* та OrderService (порт 5002) за /api/orders/*. Протестуйте routing через curl. Поясніть, чому це зручніше ніж два окремих ALB.

Завдання 4. Налаштуйте ASG із Scheduled Scaling: вранці (08:00 UTC, пн-пт) desired = 3, ввечері (20:00 UTC) desired = 1. Поясніть, чому ScaleInCooldown зазвичай встановлюють більшим за ScaleOutCooldown.

Рівень 3 — Системне проектування

Завдання 5. Спроектуйте інфраструктуру для .NET API з наступними вимогами: HTTPS з автоматичним редиректом, автомасштабування від 2 до 10 instances, RDS PostgreSQL у приватній підмережі, Redis для розподіленого кешування сесій, health check перевіряє одночасно БД і Redis. Security Groups мають дозволяти EC2→RDS та EC2→Redis трафік виключно між собою. Намалюйте архітектурну схему у PlantUML та обґрунтуйте кожне рішення.

Copyright © 2026