Два вопроса портят жизнь любому боевому сервису: «выдержит ли он наплыв пользователей?» и «что будет, когда что-то сломается?». Первое — про масштабирование, второе — про доступность. В облаке оба решаются не ночными дежурствами, а правильной сборкой из готовых кирпичиков. Всё это стоит на сети и вариантах запуска сервиса.
Сверху продублировано всё: один глобальный адрес, копии по зонам под управляемой группой. Внизу то, что дублируют отдельным решением: у базы резерв в соседней зоне появляется только с включённой высокой доступностью, и он не читается.
Вертикальное и горизонтальное масштабирование
Есть два способа дать сервису больше мощности.
- Вертикальное — взять машину помощнее. Просто, но упирается в потолок самой большой машины, требует перезапуска, и одна машина всегда единая точка отказа.
- Горизонтальное — поднять несколько одинаковых копий и раздать нагрузку между ними. Сложнее, но так строят и масштаб, и надёжность: одна копия упала — остальные держат.
Общее правило облака: масштабируются горизонтально, а сервис делают stateless — без состояния в памяти конкретной копии. Сессии, корзины, промежуточные данные выносят во внешнее хранилище (базу или кэш), чтобы любой запрос обслужила любая копия.
Группы экземпляров
Руками поднимать и гасить копии — не вариант. В Compute Engine для этого есть управляемая группа экземпляров (managed instance group, MIG) — вы описываете шаблон одной машины, а группа сама поддерживает нужное число одинаковых копий. У группы три свойства, и каждое закрывает свой вид неприятности. От нагрузки — автомасштабирование: группа добавляет копии при росте нагрузки (например, по загрузке процессора) и убирает лишние при спаде. От отказа одной копии — автовосстановление: копия, не прошедшая проверку здоровья, пересоздаётся. От отказа целой зоны — региональная группа: копии раскиданы по нескольким зонам региона, выпадет зона — сервис продолжит работать на оставшихся.
Аналог группы автомасштабирования (Auto Scaling Group) в AWS. Для контейнерных нагрузок ту же роль играют GKE и Cloud Run с их автомасштабированием (Cloud Run масштабируется до нуля сам).
Проверки здоровья
Балансировщик не должен слать запросы в мёртвую копию. За это отвечает проверка здоровья (health check) — по адресу вроде GET /health периодически стучатся и считают копию живой, только если та отвечает 200.
Тут легко слить в одно две разные проверки, хотя выглядят они одинаково:
- проверка балансировщика решает, слать ли в копию трафик. Не отвечает — копию выводят из ротации, но сама машина продолжает работать;
- проверка автовосстановления у группы экземпляров решает, пересоздавать ли машину. Это отдельная настройка, и Google советует держать её отдельной и более терпеливой, чем первая.
Настроив одну, легко ждать поведения обеих — и потом удивляться: «копия помечена нездоровой, почему её никто не пересоздал?».
Самая первая авария новичка в GCP случается даже не в приложении, а в файрволе. Пробы приходят не от пользователя и не изнутри вашей сети, а с адресов Google — 35.191.0.0/16 и 130.211.0.0/22. Не открыли их правилом — здоровых копий не будет ни одной: балансировщик отдаёт ошибку, группа считает машины мёртвыми, а в логах приложения тишина, потому что запросы до него просто не дошли.
Вторая ловушка — слишком нетерпеливое автовосстановление. Сервис стартует не мгновенно (JVM особенно), и если проверку начинают спрашивать сразу после запуска машины, группа убьёт копию, которая ещё поднимается, поднимет новую — и так по кругу. Поэтому у автовосстановления есть начальная задержка (initialDelaySec): столько времени машине дают на старт, прежде чем спрашивать с неё здоровье. Ставьте её с запасом от честного времени старта вашего приложения.
Практический совет: делайте /health честным. Если он отвечает 200 всегда, даже когда база недоступна, балансировщик будет слать трафик в заведомо сломанную копию. Хорошая проверка отражает, готов ли сервис реально обслуживать запросы.
Выкат: обновление группы и дренаж
Масштабирование и доступность без выката — половина темы, потому что замена копий происходит при каждом обновлении, а не только при росте нагрузки.
Обновление группы экземпляров (rolling update). Группа умеет заменять копии по частям, и управляют этим два числа:
gcloud compute instance-groups managed rolling-action start-update app-mig \
--version=template=app-template-v2 --region europe-west4 \
--max-surge=1 --max-unavailable=0 --min-ready=30s
max-surge — сколько копий можно создать сверх заданного числа (новая поднимается до того, как старая гасится). max-unavailable — сколько копий может быть недоступно одновременно; ноль означает, что ёмкость не проседает вовсе. min-ready — сколько копия должна проработать исправной, прежде чем считаться готовой: это защита от приложения, которое отвечает «готов», а через пять секунд падает.
Есть и более осторожный режим: обновить часть копий и остановиться (канареечный выкат), посмотреть на ошибки и задержку, потом продолжить или откатиться на прежний шаблон.
Дренаж соединений на балансировщике. Когда копия выводится из работы, балансировщик перестаёт отправлять на неё новые запросы, а начатые доводит до конца — в течение заданного времени (настройка на бэкенде, по умолчанию порядка пяти минут; разумно поставить 30–60 секунд по самому долгому запросу).
И третья часть, без которой первые две бесполезны: приложение должно корректно завершаться. Получив сигнал, оно перестаёт брать новое, дожидается текущего и закрывает соединения. В Cloud Run это то же самое: экземпляру даётся время на завершение, и приложение обязано им воспользоваться.
Порядок получается такой: решение заменить копию → новая поднимается и проходит проверку → выдерживается время готовности → старая выводится из ротации с дренажом → сигнал приложению → завершение → удаление. Если из цепочки выпадает последний шаг, пользователи видят обрывы при каждом выкате.
Доступность данных
Копии приложения размножить легко — они одинаковые. С базой сложнее: данные должны быть согласованы. Здесь помогают управляемые базы: у Cloud SQL можно включить конфигурацию высокой доступности с резервным экземпляром в другой зоне и автопереключением при отказе. Это снимает самую страшную единую точку отказа — базу — без ручной возни.
Две оговорки, чтобы не строить лишних планов. Резерв не читается: это не реплика для чтения, а горячая копия, к которой нельзя подключиться, пока переключение не случилось, — разгрузить им отчёты не выйдет, для этого заводят отдельные реплики чтения. И само переключение не мгновенно: на него закладывают около минуты недоступности базы.
Где это применяется
Собранная схема высокой доступности: глобальный балансировщик как публичная точка входа, за ним — региональная группа копий в нескольких зонах, автомасштабирование под нагрузку, автовосстановление упавших копий, а данные — в управляемой базе с резервом в другой зоне. Типовой каркас боевого развёртывания.
Типичные ошибки новичков:
- держат состояние в памяти копии — теряют при масштабировании и пересоздании;
- всё в одной зоне — авария зоны уносит весь сервис;
- фиктивный
/health, который всегда200— трафик идёт в сломанные копии; - забывают про базу — приложение продублировано, а база в одном экземпляре осталась точкой отказа.
Глубже: числа: за сколько добавляется копиярасширенное
Автомасштабирование в GCP устроено так же, как везде, и знать надо те же величины.
Период сбора метрики. Автомасштабировщик смотрит на загрузку с усреднением — в группах экземпляров это порядка минуты. То есть решение принимается не мгновенно.
Период охлаждения (cool-down). Время, в течение которого новая копия считается прогревающейся и её метрики не участвуют в решениях; по умолчанию около минуты, и для приложения, которое стартует дольше, его увеличивают — иначе группа решит, что копия не помогает, и добавит ещё.
Запуск копии. Создание машины плюс загрузка системы — десятки секунд; дальше старт приложения, и вот он обычно и занимает основную часть.
Проверка работоспособности. Копия получает трафик после нескольких успешных проверок подряд — при значениях по умолчанию это ещё десятки секунд.
Итог тот же, что и в любом облаке: от трёх до семи минут от роста нагрузки до работающей копии. Отсюда практические следствия: цель по загрузке ставят 50–60 %, предсказуемые пики закрывают расписанием, а минимальное число копий держат не меньше двух — иначе отказ единственной копии превращается в недоступность на время её пересоздания.
Ещё два числа для ориентира: переключение управляемой базы на резервный экземпляр занимает порядка минуты (и рвёт соединения — приложение обязано переподключиться), а уменьшение группы происходит с задержкой, чтобы не гасить копии на каждом провале нагрузки.
Глубже: масштабирование не только по процессорурасширенное
Загрузка процессора — самая понятная метрика и самая плохая для веб-сервиса: приложение может упираться в ожидание базы, а процессор при этом простаивать. В GCP автомасштабировщик умеет три вида сигналов, и для HTTP-сервиса честнее второй и третий.
По загрузке процессора — значение по умолчанию, годится для вычислительной нагрузки.
По загрузке балансировщика — доля от заданной ёмкости бэкенда: вы говорите «одна копия держит 100 запросов в секунду», и группа добавляет копии, когда поток растёт. Это прямая связь между тем, что приходит извне, и числом копий.
По своей метрике из системы наблюдения — любое число, которое публикует приложение: длина очереди, число активных задач, задержка ответа. Самый гибкий вариант, и для обработчиков очередей единственно верный: масштабироваться надо по числу необработанных сообщений, а не по процессору.
gcloud compute instance-groups managed set-autoscaling app-mig --region europe-west4 \
--min-num-replicas=2 --max-num-replicas=10 \
--update-stackdriver-metric=pubsub.googleapis.com/subscription/num_undelivered_messages \
--stackdriver-metric-filter='resource.labels.subscription_id="orders-sub"' \
--stackdriver-metric-single-instance-assignment=100
Последняя строка читается так: одна копия «переваривает» сто сообщений; тысяча в очереди — десять копий. Для Cloud Run логика та же, только выражается пределом одновременных запросов на экземпляр и предельным числом экземпляров.
И общее правило выбора: сигнал должен быть тем, из-за чего пользователю плохо. Растёт очередь — масштабируемся по очереди; растёт задержка ответа — по числу запросов; жжём процессор — по процессору.
Раз копий несколько, кто-то раздаёт между ними входящие запросы — это Cloud Load Balancing. Его сильная сторона — глобальный HTTP(S)-балансировщик: один внешний адрес принимает пользователей со всего мира и направляет каждого в ближайший регион с работающим сервисом. По пути он снимает с приложения три заботы: разводит запросы по путям и хостам, расшифровывает TLS и сам проверяет, живы ли копии. Есть и региональные балансировщики уровня соединений (L4). Балансировщик прячет приложения во внутренней сети и раздаёт им трафик.
Глубже: куда девать состояниерасширенное
Совет «делайте приложение без состояния» верен, а путь обычно не показывают. Вот куда что уходит в GCP.
Сессии пользователей — в управляемый кэш (Memorystore для Redis) или, лучше, в подписанный токен у клиента, чтобы серверной сессии не было вовсе. Привязка пользователя к копии (липкие сессии на балансировщике) — временный обход: копия исчезает вместе с сессиями своих пользователей при каждом выкате.
Загруженные файлы — в хранилище объектов, не на локальный диск. У Cloud Run локального диска нет вовсе (файловая система в памяти), и это заставляет делать правильно с самого начала.
Общий кэш и блокировки — тот же управляемый кэш. Локальный кэш в памяти допустим, если он только ускоряет; всё, что должно быть одинаковым у всех копий, живёт снаружи.
Фоновые задачи по расписанию — планировщик (Cloud Scheduler) плюс задача или сообщение в очередь, а не таймер внутри приложения: иначе задача выполнится столько раз, сколько копий. Тот же приём закрывает и вопрос «кто запускает ночную выгрузку».
Долгая работа — в очередь (Pub/Sub или Cloud Tasks) и отдельный обработчик, чтобы потеря копии означала задержку, а не потерю работы.
Разбор самих сервисов — в статье про управляемые данные; там же про режимы кэша, от которых зависит, переживёт ли он отказ узла. А проверка, что состояние действительно вынесено, простая: погасите одну копию под нагрузкой и посмотрите, заметил ли это кто-нибудь.
Коротко
- Растут вширь копиями без состояния: вертикальный рост упирается в самую большую машину и остаётся одной точкой отказа.
- Управляемая группа экземпляров держит копии по шаблону: автомасштабирование по нагрузке, автовосстановление по проверке, региональная группа по зонам.
- От роста нагрузки до работающей копии от трёх до семи минут: цель по загрузке 50–60 %, предсказуемые пики закрывают расписанием, переключение базы занимает минуту и рвёт соединения.
- Сигнал масштабирования выбирают по тому, из-за чего плохо пользователю: процессор для вычислений, загрузка балансировщика для веб-сервиса, своя метрика очереди для обработчиков.
- Глобальный HTTP(S)-балансировщик даёт один адрес на весь мир; проверка балансировщика решает про трафик, проверка автовосстановления про пересоздание, и вторую держат терпеливее.
- Пробы приходят с адресов Google 35.191.0.0/16 и 130.211.0.0/22 и требуют правила файрвола; нетерпеливое автовосстановление убивает копию, которая ещё стартует; /health отвечает честно.
- Выкат идёт обновлением группы с max-surge и max-unavailable или канареечно, дренажем на балансировщике и корректным завершением в коде; без любого звена пользователи получают обрывы.
- База остаётся точкой отказа, пока не включена высокая доступность Cloud SQL: резерв не читается, переключение не мгновенно.
- Состояние уходит наружу: сессии в Memorystore или токен, файлы в Cloud Storage, расписание в Cloud Scheduler, долгая работа в Pub/Sub или Cloud Tasks.
Что почитать дальше
- Отказоустойчивость и восстановление в GCP — отказ региона, RPO и RTO.
- Serverless в GCP — модель, которая масштабируется сама.
- Управляемые данные в GCP — как база переживает отказ зоны.
- Сеть в GCP — правила файрвола для проб и балансировщик.