Два вопроса портят жизнь любому боевому сервису: «выдержит ли он наплыв пользователей?» и «что будет, когда что-то сломается?». Первое — про масштабирование, второе — про доступность. В облаке оба решаются не ночными дежурствами, а правильной сборкой из готовых кирпичиков. Всё это стоит на сети и вариантах запуска сервиса.
Сверху продублировано всё: шлюз с публичным адресом, копии по трём зонам под набором масштабирования. Внизу то, что дублируют отдельным решением: у базы резерв в другой зоне появляется только с включённой зоно-избыточной высокой доступностью.
Вертикальное и горизонтальное масштабирование
Есть два способа дать сервису больше мощности.
- Вертикальное — взять машину помощнее. Просто, но упирается в потолок самой большой машины, требует перезапуска, и одна машина всегда единая точка отказа.
- Горизонтальное — поднять несколько одинаковых копий и раздать нагрузку между ними. Сложнее, но так строят и масштаб, и надёжность: одна копия упала — остальные держат.
Общее правило облака: масштабируются горизонтально, а сервис делают stateless — без состояния в памяти конкретной копии. Сессии, корзины, промежуточные данные выносят во внешнее хранилище (базу или кэш), чтобы любой запрос обслужила любая копия.
Масштабируемые наборы виртуальных машин
Днём нагрузка растёт, ночью падает, а копии всё время одни и те же: либо простаивают, либо не справляются. Руками поднимать и гасить их — не вариант. В Azure для этого есть Virtual Machine Scale Sets (VMSS): вы описываете шаблон одной машины, а набор сам поддерживает нужное число одинаковых копий. Ключевые свойства:
- Автомасштабирование — нагрузка перевалила порог по процессору: набор добавил копии; спала — убрал лишние;
- Автовосстановление — копию, переставшую проходить проверку здоровья, набор может пересоздать. Само это не включается: в наборе заводят политику автоматического восстановления (automatic instance repairs) и указывают, откуда брать признак здоровья — расширение Application Health Extension или проверку балансировщика, назначенную в модели набора. Без этого не пересоздаётся ничего;
- Распределение по зонам — копии раскидывают по нескольким зонам доступности; выпадет зона — сервис продолжит работать на оставшихся.
Аналог группы автомасштабирования (Auto Scaling Group) в AWS. Для контейнерных нагрузок ту же роль играют AKS и Container Apps с их автомасштабированием.
Балансировщик нагрузки
Раз копий несколько, кто-то раздаёт между ними входящие запросы. В Azure два варианта (подробнее — в статье про сеть):
- Load Balancer — на уровне соединений (L4), быстрый и простой. Берите вариант Standard: дешёвый Basic зон не поддерживает и с 30 сентября 2025 года выведен из эксплуатации, так что резерва по зонам с ним не выйдет;
- Application Gateway — на уровне HTTP (L7): маршрутизация по путям и хостам, терминация TLS, проверки здоровья. Обычно он и стоит публичной точкой входа, а приложения прячутся во внутренней сети.
Проверки здоровья
Балансировщик не должен слать запросы в мёртвую копию. За это отвечает проверка здоровья (health probe) — балансировщик периодически стучится по адресу вроде GET /health и считает копию живой, только если та отвечает 200. Не отвечает — трафик в неё не идёт. Тут легко склеить два разных механизма: сама проверка только выводит копию из ротации, пересоздаёт её отдельная политика автоматического восстановления набора — если вы её включили.
Практический совет: делайте /health честным. Если он отвечает 200 всегда, даже когда база недоступна, балансировщик будет слать трафик в заведомо сломанную копию. Хорошая проверка отражает, готов ли сервис реально обслуживать запросы.
Две разные проверки и что в них включать
«Честный /health» — совет, у которого есть подвох, и о нём надо сказать прямо.
Проверки бывают двух видов, и они отвечают на разные вопросы. Готовность — «можно ли давать этому экземпляру трафик»: отрицательный ответ убирает его из ротации. Живость — «не завис ли процесс»: отрицательный ответ ведёт к перезапуску или замене экземпляра.
И теперь главное. Если в проверку добавить зависимость — базу, кэш, соседний сервис, — то при мигании этой зависимости все экземпляры разом ответят отрицательно. Для готовности это означает, что сервис исчезнет из ротации целиком, хотя часть запросов он мог бы обслужить; для живости — что платформа начнёт перезапускать все экземпляры одновременно, и короткий сбой базы превратится в получасовую недоступность сервиса.
Рабочее разделение:
- В живость не включают ничего внешнего. Она отвечает только за то, что процесс жив и не завис.
- В готовность включают только то, без чего сервис бесполезен — обычно свою базу. Внешний платёжный провайдер, поисковый индекс, сервис уведомлений туда не попадают: их недоступность обрабатывается в коде (деградация, очередь, ответ с оговоркой).
- Пороги у живости свободнее, чем у готовности: цена ошибки разная — вывод из ротации на секунды против перезапуска.
Практический минимум: два разных адреса (/health/live и /health/ready), а не один на всё. Балансировщик смотрит на готовность, платформа перезапуска — на живость.
Масштабирование вниз: куда деваются запросы
Про добавление копий говорят все, а больше половины реальных проблем даёт обратное движение — удаление копии. Оно происходит при снижении нагрузки, при выкате и при обслуживании узлов.
Четыре механизма, которые делают его незаметным:
Пауза между шагами (cooldown). После каждого изменения набор ждёт заданное время, прежде чем принимать следующее решение. Без неё метрика, скачущая вокруг порога, вызывает «пилу»: копии то поднимаются, то гасятся, и каждая пара таких движений — это оборванные соединения. Разумная пауза — минуты, и для масштабирования вниз она больше, чем для вверх.
Дренаж соединений. Балансировщик перестаёт отправлять новые запросы на снимаемую копию, но уже начатые доводит до конца — в течение заданного времени. Без этой настройки удаление копии обрывает всё, что она обрабатывала.
Корректная остановка приложения. Получив сигнал завершения, приложение перестаёт брать новое и дожидается текущего. Это часть кода, а не настройки платформы, и без неё дренаж бесполезен: платформа даёт время, а процесс умирает мгновенно.
Защита от снятия (scale-in protection). Отдельная пометка на экземпляре, означающая «этот не снимать при уменьшении». Нужна там, где экземпляр выполняет долгую работу, которую нельзя прервать: обработку большой задачи, длинную выгрузку. Приложение ставит пометку на время работы и снимает после — и тогда масштабирование вниз выбирает кого-то другого.
Порядок, который получается: решение снять копию → пометка «не снимать», если она есть, отменяет выбор → вывод из ротации с дренажом → сигнал приложению → завершение → удаление. Проверяется это так же, как всегда: постоянная нагрузка плюс принудительное уменьшение набора, и в отчёте не должно быть ни одной ошибки соединения.
Доступность данных
Копии приложения размножить легко — они одинаковые. С базой сложнее: данные должны быть согласованы. Здесь помогают управляемые базы: у Azure Database for PostgreSQL можно включить зоно-избыточную высокую доступность (zone-redundant HA) — резервный сервер в другой зоне и автопереключение при отказе. Это снимает самую страшную единую точку отказа — базу — без ручной возни. Для приложения при этом меняется одно: адрес подключения остаётся прежним, но при отказе основного узла соединения рвутся на десятки секунд, и пул должен уметь переподключиться, а не упасть.
Два уточнения, на которых спотыкаются. Резервный сервер не читается: это горячий резерв, а не реплика для чтения, разгрузить им отчёты не получится — под это заводят отдельную реплику. И переключение не мгновенное: оно занимает порядка минуты, и все открытые соединения в этот момент рвутся. Чтобы пользователь увидел паузу, а не ошибку, приложение должно уметь переподключиться и повторить запрос.
Где это применяется
Собранная схема высокой доступности: балансировщик как публичная точка входа, за ним — масштабируемый набор копий в нескольких зонах, автомасштабирование под нагрузку, автовосстановление упавших копий, а данные — в управляемой базе с резервом в другой зоне. Типовой каркас боевого развёртывания.
Типичные ошибки новичков:
- держат состояние в памяти копии — теряют при масштабировании и пересоздании;
- всё в одной зоне — авария зоны уносит весь сервис;
- фиктивный
/health, который всегда200— трафик идёт в сломанные копии; - забывают про базу — приложение продублировано, а база в одном экземпляре осталась точкой отказа.
Глубже: числа, без которых масштабирование обманываетрасширенное
Две группы цифр, которые меняют ожидания от автомасштабирования и от доступности.
Сколько ждать новую копию. Правило масштабирования смотрит на метрику, а метрика усредняется за период (обычно от минуты до пяти). Дальше: правило срабатывает, набор создаёт машину (десятки секунд), в ней стартует система и приложение (ещё от секунд до минуты), проба подтверждает готовность (ещё несколько интервалов). Итог — от трёх до семи минут от роста нагрузки до работающей копии.
Практический вывод тот же, что и в любом облаке: автомасштабирование спасает от роста в течение дня и не спасает от всплеска за тридцать секунд. Пики, которые известны заранее, закрывают расписанием; неизвестные — запасом, то есть целью по загрузке 50–60 %, а не 85 %.
Что обещает соглашение об уровне услуги. Цифры у Azure привязаны к тому, как развёрнуто приложение:
| Схема | Обещанная доступность |
|---|---|
| Одна машина с диском на быстрых носителях | 99,9 % (около 43 минут простоя в месяц) |
| Две и более машин в группе доступности (один дата-центр, разные стойки) | 99,95 % |
| Две и более машин в разных зонах доступности | 99,99 % (около 4 минут в месяц) |
Разница между первой и последней строкой — это разница в порядок по допустимому простою, и стоит она ровно одного решения при создании: разнести копии по зонам. Отдельно стоит помнить, что обещание относится к инфраструктуре: если ваше приложение падает само, никакая схема размещения этого не покроет.
Глубже: квоты: почему копии не поднимаютсярасширенное
Отдельная причина, по которой автомасштабирование «молча не работает»: у подписки есть квота на ядра по семействам типов в каждом регионе, и набор не может выйти за неё. Максимум набора вы поставили тридцать, квота позволяет десять машин — на одиннадцатой появится отказ, и в событиях набора будет написано про квоту.
az vm list-usage --location westeurope -o table | head -20
Что делают: сверяют максимум набора с квотой до нагрузочного испытания, поднимают квоту заранее (заявка обрабатывается от минут до суток) и ставят оповещение на приближение к пределу. И отдельно проверяют квоты в резервном регионе — там они по умолчанию низкие, а нужны как раз в момент аварии.
Глубже: прогрев: копия получает трафик раньше, чем готоварасширенное
Свежая копия отвечает на запрос проверки раньше, чем прогрела пул соединений, кэши и компилятор. Внешне всё правильно — проверка зелёная, трафик пошёл, — а первые запросы получают таймауты или ответ в разы медленнее обычного.
Три настройки, которые это закрывают:
- Задержка до первой пробы. Проверка начинается не сразу после запуска, а через заданное время — достаточно, чтобы приложение успело поднять зависимости.
- Прогрев экземпляра. У набора машин есть время, в течение которого новая копия считается прогревающейся: её метрики не участвуют в решениях о масштабировании, и трафик на неё подаётся не сразу.
- Плавный ввод у балансировщика. Часть балансировщиков умеет подавать на новую копию долю трафика, увеличивая её постепенно. Это лучший вариант, если он доступен: копия прогревается на реальных запросах, но не на всех сразу.
И то, что можно сделать в приложении: прогрев при старте. Перед тем как ответить «готов», приложение само открывает соединения к базе, выполняет один запрос, заполняет критичные кэши. Тогда «готов» означает готов, и никакие настройки платформы не нужны.
Глубже: если состояние всё-таки естьрасширенное
Правило «приложение без состояния» — верное, и в жизни встречается код, который его нарушает: сессия в памяти, файл на локальном диске, прогретый кэш, от которого зависит корректность. Пока это не переписали, вопрос стоит практически: что делать.
Липкие сессии (у балансировщика Azure — привязка по адресу клиента, у App Service — специальная cookie) заставляют запросы одного пользователя приходить на ту же копию. Это работает и это временный обход, а не решение. Что он ломает:
- Копия исчезает вместе с сессиями своих пользователей. Выкат, масштабирование вниз, сбой — и эти люди начинают заново. То есть обычные плановые операции становятся заметными для части пользователей.
- Нагрузка перестаёт распределяться ровно. Новая копия стоит почти пустой, пока старые пользователи держатся за прежние.
- Выкат превращается в событие. Обновление означает потерю сессий, и его начинают делать по ночам — то есть теряется главная выгода от нескольких копий.
Поэтому липкость включают с одной целью: дожить до того, как состояние вынесут, и с записанным сроком. А выносят в том же порядке, что везде: сессии — во внешний кэш или в токен у клиента, файлы — в хранилище объектов, общие счётчики и блокировки — в общий кэш, долгую работу — в очередь.
Исключение, где липкость уместна постоянно: соединения, которые по природе привязаны к экземпляру (WebSocket, поток событий). Там вопрос не в липкости, а в том, чтобы клиент умел переподключаться.
Коротко
- Растут вширь копиями без состояния: вертикальный рост упирается в самую большую машину и остаётся одной точкой отказа.
- Virtual Machine Scale Sets добавляют и убирают копии по нагрузке, пересоздают упавшие по политике автоматического восстановления и раскладывают по зонам.
- Метрика усредняется за минуты, машина стартует десятки секунд, приложение дольше: автомасштабирование спасает от роста в течение дня, а известные пики закрывают расписанием.
- Одна машина обещает 99,9 %, две в группе доступности 99,95 %, две в разных зонах 99,99 %: порядок разницы в простое стоит одного решения при создании.
- Квота ядер на регион молча останавливает набор ниже его максимума: сверяют до нагрузочного испытания и поднимают заранее.
- Load Balancer уровня соединений только Standard, Basic снят с эксплуатации; Application Gateway на HTTP стоит публичной точкой входа.
- Живость без внешних зависимостей, готовность только с тем, без чего сервис бесполезен, два разных адреса; прогрев через задержку первой пробы, время прогрева экземпляра и плавный ввод трафика.
- Снятие копии делают незаметным пауза между шагами, дренаж соединений, корректная остановка в коде и защита от снятия для долгой работы.
- База остаётся точкой отказа, пока не включена зоно-избыточная высокая доступность: резерв не читается, переключение не мгновенно, соединения рвутся; липкие сессии только как временный обход с датой.
Что почитать дальше
- Отказоустойчивость и восстановление в Azure — отказ региона, RPO и RTO.
- Serverless в Azure — модель, которая масштабируется сама.
- Управляемые данные в Azure — как база переживает отказ зоны.
- Сеть в Azure — балансировщики и подсети, на которых стоит схема.