автор команда XIMTRX

Кейс: incentivized testnet, 50 нод burst за 72 часа, top-5 оператор

Анонимизированный кейс: burst-инцидент для incentivized testnet. 50 нод за 72ч, финиш в top-5 по аптайму. И скрытый AWS quota, который чуть не запорол первые 24 часа.

#case-study #testnet #burst #multi-region

Этот кейс анонимизирован настолько, насколько это возможно сделать в рамках NDA. Клиент: mid-stage протокол, Cosmos SDK-based L1 с кастомным IBC-style cross-chain settlement, готовился к шестинедельному incentivized testnet. Внутренняя команда считала, что инфраструктуры хватит. Early-access wave прошёл, трафик за неделю вырос в 8 раз, и им понадобилось 50 production-grade нод в пяти+ регионах внутри 72 часов.

Контракт был на восемь недель: 72-часовой emergency stand-up, steady-state операции через весь testnet, чистый off-boarding. Headline: 50 нод подняли за 70 часов, финишировали в top-5 оператор по validated uptime из ~120 участников.

Ниже хронология. Часы считаются от пятницы 18:00 UTC, когда пришёл звонок.

Setup

Звонок в пятницу, 18:00 UTC. Реферрал от другого оператора, с которым мы пересекались на прошлом тестнете. Спрос: 50 нод по всему миру к Monday EOD. Спека: 16 vCPU, 64GB RAM, 2TB NVMe, нормальный bandwidth (минимум 1 Гбит и низкий p95 на запад и восток). Cosmos SDK-нода в стандартной Docker-обвязке, плюс sidecar для метрик и custom relayer под их IBC-вариант. Ничего экзотического по нагрузке на ноду, экзотика только в количестве и сроке.

У нас на момент звонка было ~28 нод свободной capacity в существующих colo: места во Франкфурте, Амстердаме и Сингапуре, где железо уже стояло и ждало нагрузки. Остальные 22 надо было где-то взять.

T+2h: план

Сели считать supply window к 20:00 UTC. Получилось три потока поставки.

Первый: 14 нод у существующих colo-партнёров (Frankfurt 6, Amsterdam 4, Singapore 4). Физическая поставка на стороне провайдера, типовой SLA на ordering у этих площадок около 24 часов от подтверждения заказа. Поверх provisioning у нас лежат прединсталлированные образы и заранее согласованные спеки, поэтому наш слой (Terraform-apply, валидатор-клиент, мониторинг, runbook-выкладка) ложится параллельно по числу инженеров на смене и добавляет 30-45 минут на ноду к моменту, когда хост уже physical-ready.

Второй: 8 bare-metal-on-demand. Latitude.sh даёт нам 6 машин в их LA и Dallas POP-ах с гарантией provision за 90 минут (мы держим у них прединсталлированные образы), OpenMetal даёт 2 в их Amsterdam-кластере. Это самый дорогой слой, но самый предсказуемый: bare-metal-on-demand по сути цена за приоритет.

Третий: 28 cloud burst. AWS us-east-1 + us-west-2 (15 нод), Hetzner Cloud FRA (6), OVH GRA (4), GCP asia-southeast1 (3). Cloud burst мы используем именно для таких ситуаций: дорого по unit economics, но даёт три фактора, которые в этом окне важнее цены: скорость, региональная гибкость, и возможность безболезненно вывести их из эксплуатации после testnet.

План подписали с клиентом в 20:00 UTC. Terraform-модули у нас были готовы на все три потока, оставалось параметризовать под их Cosmos-образ и pre-pull кастомные релейерные бинари.

T+6 to T+30: разворачиваемся

С полуночи UTC поехали Terraform-applies, параллельно по трём инженерам в смене. Cosmos-нода у клиента сборная: основной node-image из их public registry, два sidecar-контейнера (метрики и relayer) и набор кастомных конфигов через configmap-стиль. Pre-pull во все целевые регионы сделали ещё на T+3h, чтобы не упереться в registry rate-limit на пике.

Key ceremony c клиентом прошёл по нашему стандартному playbook: HSM-backed, мы держим инфраструктуру, клиент подписывает локально, валидаторские ключи никогда не выходят за их периметр. На 50 нод это 50 отдельных церемоний с роутинговой проверкой каждого endpoint, занимает примерно три часа суммарно (параллелится с разворотом).

Мониторинг и алерты подняли как часть apply-флоу: Prometheus-стек, blackbox-probes по peer-портам, алерты в их Discord + наш PagerDuty.

К субботе 18:00 UTC (T+24h) первые 22 ноды были в продакшене и синхронизировались с testnet. Существующие colo плюс bare-metal-on-demand отработали штатно. По графику.

T+28h: us-east-1 встал

Дальше мы пошли в AWS us-east-1 поднимать ноды 35-50 cloud-потока. Первые 40 нод по всем облакам провизились нормально. На ноде 41 EC2 console показал InsufficientInstanceCapacity. Нода 42 - то же самое. Только наш конкретный instance type (мы используем m6id, под NVMe + memory balance).

Первая гипотеза: классический региональный capacity-issue. AWS периодически выгребает популярные типы в us-east-1, обычно лечится сменой AZ или ожиданием 30-60 минут. Сменили AZ - не помогло. Подождали 90 минут - не помогло.

И вот тут начался второй симптом. Существующие ноды в этом же AWS-аккаунте (те 13, что мы уже подняли в us-east-1 до этого) начали терять связность с примерно 30 процентами peer-IP testnet'а. TCP connects тайм-аутили, при этом из других регионов и других AWS-аккаунтов те же peer-IP отзывались штатно.

В этот момент два симптома (capacity и peer-connectivity) ещё не выглядели связанными. Capacity-issue плюс совпавший network glitch на стороне testnet это правдоподобная гипотеза, и мы её сначала и проверяли.

T+30 to T+33: что проверили

Триаж на скорость, по слоям:

  • Peer-IP не блокируют нас. Подняли controlled-probe из Hetzner FRA: те же peer-IP, на которые таймаутил us-east-1, отвечают за 40-60мс. Симптом локален в нашем us-east-1 аккаунте.
  • Security group и NACL. Не трогали с момента stand-up'а 24 часа назад, работали до этого. Ручная сверка: правила те же.
  • VPC NAT capacity. Single-AZ NAT gateway, трафик далеко в пределах лимитов CloudWatch.
  • Subnet IP exhaustion. /22 subnet, ~30 процентов адресов занято.
  • AWS service health dashboard. Всё зелёное.

После третьего часа стало ясно: симптом не объясняется ни одним из стандартных подозреваемых. И вот здесь была развилка. Можно было продолжать копать гипотезы (на это уходило по часу за гипотезу), либо признать, что симптом странный, и эскалировать.

T+33h: открываем support ticket P1

Открыли P1 в AWS Support в T+33h, описали оба симптома (capacity на новых инстансах + connectivity-потеря на существующих, всё в одном аккаунте, в одном регионе). AWS TAM подхватил тикет за час, и в T+34h позвонил с гипотезой.

Гипотеза оказалась диагнозом. Две недели до нас он работал с другим клиентом по похожему симптому. У некоторых enterprise-аккаунтов AWS по дефолту настраивает hidden per-account quota на egress к специфичным peering-сетям. Это не публичный Data Transfer Out, это отдельный внутренний квот-конфиг, который не показывается в aws service-quotas list-service-quotas и не виден через Trusted Advisor. Квота тихо rate-limit'ит новые TCP-коннекты к конкретным AS-номерам, когда суммарный трафик к ним пересекает порог за 24 часа.

Testnet, в котором участвовал клиент, имел заметную концентрацию пиров в одном из таких AS. Наш суммарный трафик пересёк порог примерно в T+27h, после чего AWS начал тихо отбивать новые коннекты к этой подсети, и параллельно (это второй side-effect той же квоты) пометил наш аккаунт как «high risk» для capacity allocation, что и дало InsufficientInstanceCapacity.

TAM подтвердил аккаунт, поднял квоту, и в T+34h 40m оба симптома схлопнулись: новые EC2 пошли в provision, существующие ноды восстановили peer-connectivity за пять минут.

T+30 to T+38: параллельные действия

Параллельно с support-тикетом, начиная с T+30h, мы не сидели и ждали, а открыли второй и третий контингентный поток: пере-skoping'нули нагрузку, чтобы не зависеть от исхода AWS-эскалации.

  • 8 из 15 оставшихся us-east-1 нод перенесли на GCP asia-southeast1 + Hetzner Cloud FRA. Terraform-план был готов за 18 минут (потому что модули уже были, надо было только поменять provider-блок и region-параметр), apply занял 47 минут.
  • 7 us-east-1 нод оставили в плане, ставка была на то, что support либо подтвердит квоту, либо нет, и в обоих случаях за ближайшие 3-4 часа мы поймём, переносить остальное или нет.
  • Параллельно подготовили эвакуационный план для существующих 13 us-east-1 нод на случай, если AWS откажет в квот-лифте: cutover-скрипт переподписи через standby-инфру в GCP, drain-и-релоад без потери signing-state. Запускать не пришлось.

После того как TAM поднял квоту в T+34h 40m, 7 оставшихся us-east-1 нод провизились за час. Полностью все 50 нод были в продакшене к T+70h, в понедельник 10:00 UTC, за 14 часов до EOD-дедлайна.

Что попало в раннбук

Из этого инцидента в наш внутренний runbook ушло пять записей.

Первое: любой burst больше 30 нод в один cloud-аккаунт за окно меньше 48 часов получает proactive support-тикет до того, как мы упрёмся в лимит, а не после. Дешевле открыть тикет с описанием «планируем такой-то burst, проверьте квоты на нашем аккаунте» и иметь подтверждение, чем триажить hidden quota на T+30h.

Второе: целевая региональная диверсификация. Ни один cloud-аккаунт не держит больше 15 нод в рамках одного burst-инджаджмента. На 50 нод это автоматически означает минимум четыре провайдера, что само по себе хеджирует и от capacity-issues, и от account-level фолтов вроде того, что мы поймали.

Третье: AWS hidden egress quota теперь known unknown. Любой новый клиент, который использует AWS в продакшене, получает quota-inspection support-тикет в первую неделю setup'а. Цель тикета: явно запросить у TAM перечисление всех скрытых квот на аккаунте, включая те, что не видны в публичных API. Это не закроет все возможные сюрпризы (потому что AWS не обязан раскрывать всё), но снимет самые частые.

Четвёртое: pre-pull кастомных Docker-образов во все целевые регионы до начала key ceremony. Мы это и так делали, но раньше это был implicit step в чек-листе ведущего инженера. Теперь это explicit gate: ceremony не начинается, пока pre-pull не подтверждён по всем регионам.

Пятое: emergency move-and-restart playbook для миграции ноды mid-testnet без потери rewards. До этого кейса у нас был общий migration runbook, но без жёстких таймингов под testnet-сценарий, где даунтайм критичен. Сейчас он формализован: drain, snapshot signing-state, restore на целевой инфре, верификация peer-handshake, и всё это с rolling window, в которое слот пропуска не успеет накопиться до slash-threshold.

Урок

Один: cloud capacity это маркетинговый термин. Реальный capacity это то, что разрешено использовать вашему конкретному аккаунту сегодня, и это значение может отличаться от вчерашнего и от того, что написано в публичной документации. Hidden quotas существуют, и часть из них вы увидите только через P1-тикет.

Два: географический разброс это не только uptime-хедж. Это account-level fault-isolation хедж. Если бы 50 нод были в одном AWS-аккаунте, мы бы не уложились в 72 часа никакими усилиями. Три cloud-провайдера, два bare-metal-вендора и три colo дали нам возможность перемаршрутизировать нагрузку посередине инцидента, не дожидаясь решения AWS.

Три: P1 в первые 30 минут странного симптома это почти всегда правильный ход, даже если у вас ещё нет гипотезы. TAM, который нам помог, узнал симптом сразу, потому что видел его на прошлой неделе. Мы могли копать его гипотезу четыре часа сами, а получили её за час просто потому, что эскалировали раньше, чем додумали свою.

50 нод за 70 часов получилось не потому, что первая полоса плана сработала. Сработало то, что playbook держал три контингентных полосы наготове.

Команда XIMTRX

← Все статьи