open atlas
↑ К треку
AWS на практике AWS · 02 · 04

Автомасштабирование и балансировка: ALB, NLB и target tracking

ALB — уровень 7; NLB — уровень 4 для чистого TCP и статического IP. Health-check решает, кто получит трафик; deregistration delay сливает in-flight при scale-in. Автоскейл реактивен — запас, сигнал по числу запросов и тёплая ёмкость бьют scale-out lag, дающий 5xx во всплеске.

AWS Senior ◷ 19 min
Уровень
ОсновыJuniorMiddleSenior

Распродажа открывается в полдень. В 11:59 твой парк — четыре таска, простаивают, CPU около 12%. В 12:00:01 главная страница ловит десятикратный трафик, и четыре таска мгновенно прижаты к потолку. Автоскейлер делает ровно то, что ты настроил: target tracking по CPU на 50%. Поэтому он ждёт. CloudWatch нужна примерно минута, чтобы агрегировать метрику, затем alarm’у нужно несколько датапоинтов на окне оценки, чтобы объявить пробой, затем он просит у ECS больше тасков, затем каждый новый таск должен скачать образ, стартовать, прогреться и пройти три подряд health-check’а, прежде чем балансировщик направит на него хоть один запрос. Примерно три минуты твои четыре перегруженных таска в одиночку держат 10× всплеск, сыпля 5xx и отваливаясь по таймауту, пока дашборд бодро показывает масштабирование «в процессе». К моменту, когда ёмкость приходит, первая волна распродажи уже прошла, и конверсия, под которую ты оптимизировал весь квартал, потеряна — не потому что автомасштабирование сломалось, а потому что оно реактивно, а всплеск не ждёт alarm’а.

ALB против NLB: маршрутизация уровня 7 или пропускная способность уровня 4

Когда тянешься за балансировщиком, первый вопрос не «который быстрее» — а «нужна ли бэкенду маршрутизация на уровне HTTP или нужна сырая TCP-пропускная способность?» Ответ закрывает выбор ещё до того, как смотришь на любое другое свойство.

Elastic Load Balancing даёт два продакшен-балансировщика, и выбор тут структурный, а не косметический. Application Load Balancer (ALB) работает на уровне 7 — он терминирует HTTP/HTTPS, разбирает каждый запрос и маршрутизирует по host, path, заголовку, методу или query-строке в разные target group. Именно это позволяет одному ALB развести единый домен по множеству микросервисов: /api/* в один сервис, /static/* в другой, host: admin.example.com в третий. Цели — это EC2-инстансы, IP-адреса, функции Lambda или, как в нашем случае, ECS-таски, регистрируемые сервисом автоматически. Поскольку ALB понимает HTTP, он также делает терминацию TLS, sticky-сессии через cookie и решения о маршрутизации на каждый запрос.

Network Load Balancer (NLB) работает на уровне 4 — он форвардит TCP- и UDP-потоки, не читая полезную нагрузку. Он не разбирает HTTP, не умеет маршрутизировать по path и не может говорить с Lambda-целями. Взамен он даёт сверхнизкую латентность (нет разбора запроса), способность держать миллионы потоков в секунду, статический IP на зону доступности (и опцию Elastic IP), нативные эндпоинты PrivateLink и — что критично для части бэкендов — сохраняет исходный IP клиента вплоть до цели. Компромисс очевиден: ALB — выбор по умолчанию для HTTP-микросервисов, которым нужны маршрутизация, TLS и богатые health-check’и; NLB — для чистого TCP/UDP, экстремальной пропускной способности, фиксированного IP, который можно прибить в файрволах и allowlist’ах, или для всего, что должно видеть реальный IP клиента.

# ALB: уровень 7, маршрутизация по пути к двум target group ECS.
resource "aws_lb_listener_rule" "api" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 10
  action { type = "forward"; target_group_arn = aws_lb_target_group.api.arn }
  condition { path_pattern { values = ["/api/*"] } }
}

# NLB: уровень 4, проброс чистого TCP через порт 5432 к парку Postgres-прокси.
resource "aws_lb_listener" "pg" {
  load_balancer_arn = aws_lb.nlb.arn
  port              = 5432
  protocol          = "TCP"
  default_action { type = "forward"; target_group_arn = aws_lb_target_group.pgproxy.arn }
}

То, что реально решает, получает ли цель трафик, — это health-check target group: путь (для ALB, например GET /healthz), интервал, таймаут и пороги healthy/unhealthy (сколько успехов подряд помечают цель здоровой, сколько провалов — нездоровой). Это живой фильтр — цель остаётся вне ротации, пока не пройдёт проверку, и выводится в тот момент, когда пробивает порог провалов. Ошибиться можно в обе стороны: слишком агрессивная проверка (короткий интервал, низкий порог unhealthy, тугой таймаут) выбивает (flapping) здоровые цели из ротации на одной медленной паузе GC или коротком сбое зависимости, сжимая парк ровно тогда, когда нагрузка высока; слишком мягкая проверка (длинный интервал, высокий порог) продолжает слать трафик на мёртвую или зависшую цель десятки секунд, так что пользователи ловят 5xx, пока проверка наконец не заметит.

Target group и deregistration delay

Когда цель уходит — scale-in убрал таск или деплой его заменяет — балансировщик не выдёргивает её просто так. Он выполняет слив соединений (connection draining), управляемый параметром target group deregistration delay (атрибут deregistration_delay.timeout_seconds, по умолчанию 300 секунд). При дерегистрации балансировщик немедленно перестаёт слать цели новые запросы, но даёт in-flight запросам завершиться, вплоть до этой задержки; только потом цель полностью отключается. Это разница между чистым rolling-деплоем и стеной ошибок.

resource "aws_lb_target_group" "api" {
  name        = "api-tg"
  port        = 8080
  protocol    = "HTTP"
  target_type = "ip"            # задачи ECS awsvpc регистрируются по IP

  health_check {
    path                = "/healthz"
    interval            = 15     # проверка каждые 15 сек
    timeout             = 5
    healthy_threshold   = 2      # 2 подряд → в ротации
    unhealthy_threshold = 3      # 3 подряд → исключить
    matcher             = "200"
  }

  # Слив in-flight запросов при scale-in / деплое. Подбери по реальному
  # p99 длительности запроса плюс запас — НЕ 300с по умолчанию вслепую.
  deregistration_delay = 30

  stickiness { type = "lb_cookie"; enabled = false }
}

Режим отказа — двусторонняя ловушка. Поставь deregistration delay слишком коротким, и каждый scale-in или деплой обрывает in-flight запросы на полпути: 12-секундный экспорт отчёта или long-poll убивается на 5-й секунде, и ты отправляешь 5xx реальному пользователю при каждом деплое — самонаведённый всплеск ошибок, подозрительно коррелирующий со временем релизов. Поставь слишком длинным (дефолт 300с для приложения, чьи запросы завершаются за 200мс), и каждый rolling-деплой ползёт, потому что каждый снимаемый таск сливается пять простойных минут, прежде чем деплой объявит его ушедшим — медленные раскатки, медленные откаты и гораздо более широкое окно, где старый и новый код работают бок о бок. Правильное значение — реальный p99 длительности запроса плюс запас, а не дефолт.

Другой рычаг target group — cross-zone load balancing. Когда он включён (дефолт для ALB; выключен по умолчанию для NLB), каждая нода балансировщика может слать трафик целям в любой зоне, так что нагрузка распределяется ровно даже при неравном числе целей по зонам — но межзональный трафик на NLB тарифицируется как inter-AZ передача данных. Когда он выключен, каждая нода бьёт только по целям своей зоны, что бесплатно, но перекашивает нагрузку, если в одной зоне 2 цели, а в другой 8: эти 2 поглощают ту же долю трафика своей зоны, что и те 8 — своей, так что маленькая зона перегревается.

Почему это работает

Почему target tracking по числу запросов реагирует быстрее, чем по CPU. CPU — отстающий, сглаженный сигнал: запрос приходит, ставится в очередь, обрабатывается, и только потом CPU растёт — а CloudWatch отдаёт CPU как среднее за минуту, так что всплеск, упавший в 12:00:01, едва двигает датапоинт 12:00–12:01 и полностью проявляется лишь в среднем следующей минуты. Ты реагируешь спустя минуту и более после того, как нагрузка уже бьёт. ALBRequestCountPerTarget считает запросы в момент, когда балансировщик их принимает — до очереди, до роста CPU, до роста латентности. Всплеск виден в счётчике запросов практически в момент, когда он бьёт в балансировщик, так что alarm по запросам-на-цель пробивает раньше и запускает scale-out, пока CPU ещё карабкается. Это и более честный прокси ёмкости для I/O-bound сервисов, где CPU остаётся низким, пока парк тонет в одновременных соединениях. Сигнал по числу запросов — дефолт уровня senior именно потому, что он опережает, а не отстаёт.

Автомасштабирование: контур управления и его встроенная задержка

И ECS service autoscaling (через Application Auto Scaling), и EC2 Auto Scaling Group управляют ёмкостью по метрикам CloudWatch, и типы политик одни и те же три. Target tracking — дефолт уровня senior: ты называешь метрику и целевое значение — CPU на 50% или ALBRequestCountPerTarget на 1000 — а AWS управляет alarm’ами и добавляет/убирает ёмкость, удерживая метрику у цели, как термостат. Step scaling добавляет ступенчатый ответ (пробил чуть → +1, пробил сильно → +4), когда нужен явный контроль. Scheduled scaling задаёт ёмкость по часам для известных паттернов — поднять до 20 тасков в 08:55, потому что рабочий день начинается в 09:00.

{
  "PolicyName": "track-requests-per-target",
  "PolicyType": "TargetTrackingScaling",
  "TargetTrackingScalingPolicyConfiguration": {
    "TargetValue": 1000.0,
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ALBRequestCountPerTarget",
      "ResourceLabel": "app/prod-alb/abc123/targetgroup/api-tg/def456"
    },
    "ScaleInCooldown": 120,
    "ScaleOutCooldown": 30
  }
}

Контур управления — это и есть вся суть, и каждый этап стоит реального времени по часам: метрика отправлена → CloudWatch её агрегирует (~1 минута) → alarm’у нужно N датапоинтов за M минут, чтобы пробить → запрошено действие масштабирования → новый таск/инстанс должен стартовать, прогреться и пройти пороги health-check’а, прежде чем балансировщик на него направит трафик. Ничто не обслуживает трафик до этого последнего шага. Cooldown’ы дополнительно стопорят контур: слишком короткие cooldown’ы заставляют его дёргаться (flapping) — scale-out, метрика падает, scale-in, метрика растёт, снова scale-out — болтая ёмкостью и платя за запуски на каждом цикле; слишком длинные cooldown’ы оставляют тебя недопровиженным на всём затяжном росте. Заметь асимметрию в политике выше: масштабируй вверх быстро (короткий cooldown — опоздать стоит пользователей) и вниз медленно (длинный cooldown — поспешить стоит повторного подъёма).

Scale-out lag: почему всплеск всё равно даёт 5xx

Вот та самая боевая история в общем виде. Автомасштабирование реактивно — оно может ответить лишь после того, как метрика сдвинулась, агрегировалась и пробила alarm — а внезапный 10× всплеск этой форы не даёт. Сложи этапы: агрегация метрики ~1 минута, оценка alarm’а по его датапоинтам ~1–3 минуты, затем запуск и прогрев. Fargate-таск стартует за десятки секунд; запуск EC2-инстанса плюс загрузка ОС, регистрация агента, скачивание образа и прогрев приложения — это минуты. Сложи всё, и новая ёмкость часто отстаёт от всплеска на 3–5 минут — вечность при 10× нагрузке, в течение которой текущий парк в одиночку ест наплыв и сыплет 5xx и таймауты. Парк был рассчитан на steady state; всплеск — нет.

Все senior-митигации бьют по разным этапам этого контура. Target tracking с запасом — ставь цель CPU 50%, не 80% — чтобы steady state уже работал с резервом ёмкости для поглощения первой волны, пока scale-out догоняет. Масштабируй по числу запросов, а не по CPU, потому что это опережающий сигнал (см. вставку). Scheduled scaling для известных пиков (распродажа, логин-шторм в 09:00): прогрей ёмкость по часам, чтобы никогда не входить во всплеск холодным. Predictive scaling (EC2 ASG) учит дневной/недельный цикл по истории и провиженит заранее, по кривой прогноза. Warm pool (EC2) держит преинициализированные остановленные инстансы, которые возобновляются за секунды вместо загрузки с нуля. Provisioned capacity / provisioned concurrency держит базовую линию всегда тёплой. А когда ёмкость и правда не успевает, сбрасывай или ставь в очередь: верни 503 с Retry-After или столкни работу в SQS, чтобы всплеск буферизовался, а не отбрасывался. Ничто из этого не делает автомасштабирование мгновенным — оно сжимает или прячет задержку, чтобы всплеск встретил ёмкость, а не стену ошибок.

Выбери лучший вариант

HTTP-микросервис на ECS имеет предсказуемые 10× всплески в известное время (ежедневный логин-шторм в 09:00 и плановая распродажа). Сейчас он target-track'ит CPU на 70% и сыплет 5xx 2–3 минуты в начале каждого всплеска. Выбери основное исправление.

Викторина

Твой дашборд ошибок показывает чёткий всплеск 5xx, который падает ровно в начало каждого деплоя и длится несколько секунд, затем чистится. Health-check'и на новых тасках проходят. Какова наиболее вероятная причина?

Вспомните перед уходом
  1. 01
    Сравни ALB и NLB по уровню, возможностям и тому, когда выбирать каждый.
  2. 02
    Объясни deregistration delay, его дефолт и режим отказа на каждом краю.
  3. 03
    Пройди контур управления автомасштабированием и объясни, почему 10× всплеск всё равно даёт 5xx, с митигациями.
Итог

Elastic Load Balancing предлагает два структурно разных балансировщика. ALB — уровень 7: он разбирает HTTP/HTTPS и маршрутизирует по host, path, заголовку, методу или query-строке в target group, поддерживает цели EC2/IP/Lambda/ECS и делает TLS и sticky на cookie — дефолт для HTTP-микросервисов. NLB — уровень 4: он форвардит чистый TCP/UDP со сверхнизкой латентностью, держит миллионы потоков, даёт статический IP на зону и PrivateLink и сохраняет исходный IP клиента — для чистой пропускной способности, фиксированных IP или нужды в исходном IP. Health-check target group (путь, интервал, пороги healthy/unhealthy) — живой фильтр, решающий, кто получает трафик: слишком агрессивный выбивает здоровые цели на одной паузе GC; слишком мягкий продолжает слать на мёртвые цели десятки секунд. Когда цель уходит при scale-in или деплое, deregistration delay (по умолчанию 300с) сливает in-flight запросы — поставь слишком коротким, и ты режешь запросы и отправляешь 5xx при каждом деплое; слишком длинным, и rolling-деплои ползут; правильное значение — реальный p99 плюс запас. Cross-zone load balancing распределяет нагрузку ровно, но тарифицирует inter-AZ на NLB; выключенный он бесплатен, но перекашивает нагрузку при неравном числе целей. Автомасштабирование — ECS service или EC2 ASG — работает по политикам CloudWatch, где target tracking — дефолт уровня senior (держать CPU 50% или ALBRequestCountPerTarget 1000), step scaling — для ступенчатого контроля, scheduled scaling — для известных паттернов. Но контур управления реактивен и медленен: агрегация метрики ~1 мин, alarm ~1–3 мин, затем запуск и прогрев (Fargate десятки секунд, EC2 минуты), так что новая ёмкость приходит на 3–5 минут позже внезапного 10× всплеска, и текущий парк в одиночку сыплет 5xx, пока она не прибудет. Бей задержку запасом, масштабированием по числу запросов (опережающий сигнал), scheduled и predictive scaling для известных пиков, warm pool и provisioned capacity, а также сбросом или очередью — и держи scale-out cooldown короткими, scale-in — длинными, чтобы избежать flapping. Автомасштабирование никогда не мгновенно; работа senior — сделать так, чтобы всплеск встретил ёмкость, а не стену ошибок. Теперь, когда увидишь всплеск 5xx, точно совпадающий по времени с деплоем, сначала проверяй deregistration delay — не health check’и, не тип балансировщика. А когда видишь 5xx в начале известного пикового периода, сначала ищи отсутствующий scheduled scaling, прежде чем вообще сомневаться в правильности настройки автоскейлинга.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 7 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.