MAATRIX / Блог / Антипаттерн: переезд в облако без смены архитектуры

Антипаттерн: переезд в облако без смены архитектуры

MAATRIX

«Переезжаем в облако» почти всегда на практике означает одно и то же: взяли образ существующего сервера, подняли такую же виртуалку у облачного провайдера, перенесли код и базу — готово, теперь у нас облако. Через несколько месяцев выясняется, что счёт вырос, а обещанная эластичность так и не случилась: приложение как упиралось в потолок одного инстанса, так и упирается, просто теперь это дороже. Разберём, почему буквальный перенос архитектуры «как есть» не даёт облачных выгод сам по себе — и что нужно поменять, чтобы они действительно появились.

Что такое lift-and-shift и почему он кажется очевидным решением

Lift-and-shift («поднять и перенести») — это миграция, при которой архитектура приложения не меняется вообще: тот же монолит, та же локальная СУБД на том же хосте, та же файловая структура, тот же способ деплоя. Меняется только адрес — вместо стойки в дата-центре или своего сервера теперь виртуальная машина у облачного провайдера. С точки зрения ОС и приложения буквально ничего не изменилось: ssh на тот же порт, тот же nginx.conf, та же база в /var/lib/mysql.

Это самый быстрый и самый дешёвый в моменте способ мигрировать. Не нужно трогать код, не нужно переписывать логику работы с файлами или сессиями, не нужно тестировать поведение под новой архитектурой — снял образ (или развернул с нуля по тому же плейбуку), перенёс данные, переключил DNS. Именно поэтому lift-and-shift — почти всегда первый шаг, который приходит в голову, когда компания слышит от руководства «нам нужно в облако» без уточнения, зачем именно.

Проблема начинается с ожиданий. От переезда в облако обычно ждут трёх вещей: автоматической масштабируемости под нагрузку, оплаты по факту использования вместо фиксированной аренды и повышенной отказоустойчивости «потому что это же облако, у них всё зарезервировано». Ни одно из этих трёх свойств не появляется само по себе от смены хостинг-провайдера — они появляются только тогда, когда архитектура приложения спроектирована так, чтобы ими воспользоваться. Облако предоставляет *возможность* — API для создания инстансов за секунды, балансировщики нагрузки, автоскейлинг-группы, объектное хранилище, managed-базы. Но эта возможность остаётся неиспользованным API, если приложение физически не умеет ей пользоваться.

Облако не масштабирует то, что не умеет масштабироваться

Горизонтальное масштабирование — это способность обслуживать возросшую нагрузку, добавляя новые одинаковые инстансы приложения за балансировщиком, а не увеличивая мощность одного сервера. Именно это чаще всего имеют в виду, говоря «облачная эластичность». И именно это лифт-энд-шифт не даёт автоматически: то, что вы можете *создать* второй инстанс по клику или по API, не значит, что ваше приложение *корректно работает* при двух инстансах одновременно.

Типичная монолитная архитектура, спроектированная под один постоянный сервер, обычно нарушает горизонтальное масштабирование сразу в нескольких местах:

  • Сессии пользователей хранятся локально — в файлах на диске (/var/lib/php/sessions) или в памяти процесса. Второй инстанс за балансировщиком ничего не знает о сессиях первого — пользователь логинится, а на следующем запросе, который балансировщик отправляет на другую машину, оказывается разлогинен.
  • Загруженные файлы пишутся на локальный диск приложения (/var/www/uploads). Файл, загруженный через первый инстанс, физически не существует на втором — пользователи получают 404 на свои же аватарки или документы в зависимости от того, на какой инстанс их перенаправил балансировщик.
  • Фоновые задачи и cron рассчитаны на единственный экземпляр. Если тот же самый crontab с рассылкой писем или пересчётом отчётов окажется на двух инстансах одновременно, письма уйдут дважды, а отчёт посчитается дважды параллельно с гонкой за одни и те же строки в базе.
  • Локальный кеш в памяти процесса (даже что-то простое вроде статического массива в PHP-процессе или локального словаря в Python) не синхронизируется между инстансами — на каждом свои устаревшие данные.

Ни одна из этих проблем не решается облаком. Она решается только переработкой архитектуры так, чтобы каждый инстанс приложения был *stateless* — не хранил у себя ничего, что должно пережить его собственный рестарт или быть видимым другому инстансу. Пока это не сделано, «облачная эластичность» на практике означает одну переменную нагрузки — вертикальный апгрейд того же единственного инстанса, то есть ровно то же самое, что вы делали на выделенном сервере, только по более дорогому прайсу.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Локальное состояние — корень проблемы

Если разобрать типичную «переехавшую как есть» архитектуру, почти всегда обнаруживается один и тот же корень: приложение и его состояние физически не разделены. Приложение (код, который обрабатывает запросы) и состояние (сессии, файлы, кеш, блокировки) живут на одном и том же диске одного и того же сервера, потому что так было спроектировано изначально — сервер один, зачем усложнять.

Переработка под горизонтальное масштабирование начинается именно с выноса состояния наружу, в отдельные сервисы, к которым имеет доступ любой инстанс приложения:

# было: сессии в файлах на локальном диске инстанса
session.save_handler = files
session.save_path = "/var/lib/php/sessions"

# стало: сессии в Redis, доступном всем инстансам
session.save_handler = redis
session.save_path = "tcp://redis.internal:6379?auth=REDACTED"
# было: загрузка файла напрямую на локальный диск
def save_upload(file):
    file.save(f"/var/www/uploads/{file.filename}")

# стало: загрузка в объектное хранилище, доступное с любого инстанса
def save_upload(file):
    s3_client.upload_fileobj(file, "app-uploads", file.filename)

Для фоновых задач решение — не «тот же cron на всех инстансах», а выделенный воркер (один инстанс или очередь с распределённой блокировкой), который не дублируется вместе с веб-слоем:

# было: cron прямо на каждом веб-инстансе
* * * * * /usr/bin/php /var/www/app/cron/send_reports.php

# стало: очередь задач + отдельный воркер-инстанс,
# который не масштабируется вместе с веб-слоем
# (например, отдельный systemd-сервис на выделенном инстансе-воркере)

Отдельно стоит проблема, которую разбирали в статье про состояние приложения в контейнере: контейнеризация сама по себе тоже не решает вопрос состояния, а часто маскирует его — контейнер выглядит «одноразовым и переносимым», но если внутри него по-прежнему пишутся файлы на локальный слой, при масштабировании или пересоздании контейнера эти данные точно так же теряются или расходятся между инстансами. Смена упаковки (VM → контейнер, свой сервер → облачная VM) не эквивалентна смене архитектуры хранения состояния — это два независимых шага.

Платите за гибкость, которой не пользуетесь

Здесь кроется вторая часть разочарования — финансовая. Облачные провайдеры продают вычислительную мощность по модели, ориентированной на переменную, скачкообразную нагрузку: возможность создать десять инстансов на час пиковой нагрузки и удалить их сразу после — это то, за что имеет смысл переплачивать по сравнению с фиксированной арендой железа. Но если приложение в силу архитектуры не умеет создавать и удалять инстансы динамически, вы весь месяц держите один и тот же инстанс включённым 24/7 — то есть используете облако ровно как выделенный сервер или VPS, но платите по прайсу, рассчитанному на гибкость, которой не пользуетесь.

К этому добавляются статьи расходов, которые в классической аренде сервера просто не существуют или включены в тариф: плата за исходящий трафик сверх лимита, отдельная оплата IOPS диска, стоимость публичного IP, managed-балансировщик, который держат «на будущее», хотя реально балансировать пока нечего — балансировщик стоит денег даже когда за ним висит один-единственный инстанс.

СценарийЧто происходитИтог по деньгам
Lift-and-shift, 1 инстанс 24/7, без автоскейлингаПостоянная нагрузка, облачный прайсинг за гибкость, которая не используетсяДороже эквивалентного VPS/выделенного сервера с теми же характеристиками
Lift-and-shift + балансировщик «на будущее» перед единственным инстансомПлатите за managed-балансировщик, который не балансируетЛишняя статья расходов без функциональной пользы
Адаптированное приложение, автоскейлинг под реальные пикиИнстансы создаются под нагрузку и удаляются послеСебестоимость соответствует фактическому использованию ресурсов

Ориентир, а не точная цифра: разница в цене между «час аренды облачного инстанса» и «час аренды сопоставимого по CPU/RAM выделенного сервера, размазанный на месяц» обычно заметно не в пользу облака при постоянной круглосуточной нагрузке без масштабирования — у каждого провайдера и тарифа она своя, но направление устойчивое. Подробный разбор механики удорожания — в статье почему переезд в облако утраивает счёт за инфраструктуру: там разложено по статьям расходов, откуда именно берётся разница.

Когда lift-and-shift оправдан

Важно не скатиться в другую крайность — вывод «лифт-энд-шифт — это всегда плохо» тоже неверен. У этого подхода есть законные сценарии применения, просто ни один из них не подразумевает, что на лифт-энд-шифте миграция заканчивается:

  • Жёсткий дедлайн на переезд. Заканчивается контракт с текущим дата-центром, старое железо физически выходит из строя, провайдер закрывается — когда счёт идёт на дни, переписывать архитектуру под облако некогда. Быстро поднять то же самое в новом месте и разобраться с эластичностью потом — рационально.
  • Временный мост, а не финальное состояние. Lift-and-shift как первая фаза плана из нескольких этапов — нормальная практика: сначала физически переехать без риска сломать бизнес-логику под давлением времени, потом, уже без спешки, постепенно выносить состояние и адаптировать масштабирование.
  • Proof of concept и оценка провайдера. Если цель — понять, насколько вообще целесообразен переезд в облако для конкретного приложения (в том числе оценить, окупится ли переработка архитектуры), временный лифт-энд-шифт дешевле и быстрее полноценного пилота с переписыванием кода.
  • DR- и резервная площадка. Копия инфраструктуры «как есть» в другом регионе на случай катастрофы — это ровно тот случай, где элегантность архитектуры вторична, а важна именно идентичность продакшну и скорость поднятия.

Общий признак оправданного лифт-энд-шифта — он явно временный или явно ограниченный по масштабу, и про это заранее знают. Проблема начинается не с самого переноса «как есть», а с того, что он тихо становится постоянной архитектурой без явного решения «мы осознанно остаёмся на этом уровне» — просто потому что переработка не была приоритетом, а счета росли постепенно и к ним привыкли.

Что значит реальная переработка архитектуры

Полный переход на микросервисы не требуется — это отдельная и часто избыточная крайность, разобранная в статье про миф о том, что монолит устарел: хорошо спроектированный монолит вполне может масштабироваться горизонтально, если из него вынесено состояние. Речь не о смене парадигмы, а о нескольких конкретных, инкрементных шагах:

  1. Инвентаризация состояния. Пройтись по коду и найти всё, что пишет на локальный диск или держит данные в памяти процесса дольше одного запроса: сессии, загрузки файлов, локальные кеши, временные файлы, блокировки, счётчики. Без этого списка непонятно, что вообще нужно вынести.
  2. Разделение вычислительного и стейтового слоя. Веб/API-слой становится stateless и может свободно тиражироваться; состояние переезжает в отдельные сервисы — Redis/Memcached для сессий и кеша, объектное хранилище (S3-совместимое) для файлов, СУБД остаётся единой точкой правды для данных, но с ней работают все инстансы одинаково.
  3. Выбор managed-сервисов там, где это реально снимает эксплуатационную нагрузку, а не добавляется «для галочки» — managed-база данных с автоматическими бэкапами и репликами имеет смысл, если команда не хочет тратить время на администрирование СУБД; но это отдельное решение от вопроса масштабирования веб-слоя.
  4. Настройка автоскейлинга под реальный профиль нагрузки, а не «включили и забыли». Триггеры на CPU/RAM подходят не для всех сценариев — если нагрузка растёт по очереди задач, а не по CPU, скейлить нужно по длине очереди.
  5. Контроль стоимости по факту, а не по ощущению. Тегирование ресурсов, регулярный просмотр детализации счёта, отключение неиспользуемых managed-компонентов (тот самый балансировщик перед одним инстансом) — без этого даже правильно спроектированная архитектура постепенно обрастает лишними статьями расходов.

Здесь же стоит честно оценить альтернативу: если после инвентаризации выясняется, что реальных скачков нагрузки, которые оправдывали бы автоскейлинг, у приложения нет и не предвидится — возможно, правильный вывод не «переработать архитектуру под облако», а вернуться на выделенный сервер с предсказуемой фиксированной стоимостью. Переработка архитектуры имеет смысл, когда нагрузка реально переменная и от эластичности есть польза; если нагрузка стабильна, выделенный сервер с тем же железом обычно оказывается и проще, и дешевле в эксплуатации.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли постепенно дорабатывать архитектуру уже после лифт-энд-шифта, не мигрируя заново?

Да, и это обычная практика — вынос состояния (сессии в Redis, файлы в объектное хранилище) делается поверх уже перенесённого приложения без повторного переезда, шаг за шагом, начиная с самого критичного узкого места.

Как понять, что именно является узким местом в конкретном приложении?

Начните с инвентаризации всего, что приложение пишет на локальный диск или держит в памяти процесса дольше одного запроса — сессии, загрузки, кеши, блокировки. Это и есть список того, что нужно вынести первым, в порядке частоты обращения к этому месту в коде.

Обязательно ли переходить на Kubernetes, чтобы получить горизонтальное масштабирование?

Нет, Kubernetes — это оркестратор контейнеров, а не решение проблемы состояния. Можно горизонтально масштабировать и без него — несколько stateless-инстансов за обычным балансировщиком уже дают эффект, если состояние вынесено наружу. Kubernetes оправдан, когда число сервисов и частота деплоев делают ручное управление инстансами неудобным, это отдельное решение с собственной ценой сложности.

Что дешевле — переработать архитектуру под облако или остаться на выделенном сервере?

Зависит от профиля нагрузки. Если нагрузка стабильна и предсказуема, выделенный сервер с фиксированной ценой почти всегда дешевле и проще в эксплуатации. Если нагрузка скачкообразная (сезонность, рекламные кампании, пиковые часы), переработанная облачная архитектура с реальным автоскейлингом окупает и усилия на переработку, и премию в цене за гибкость.

Как оценить, окупится ли переработка архитектуры для конкретного проекта?

Сравните разницу между текущим облачным счётом за постоянно включённые инстансы и стоимостью инженерного времени на вынос состояния и настройку автоскейлинга. Если экономия на счёте за несколько месяцев превышает стоимость переработки — она окупается; если нагрузка небольшая и стабильная, зачастую выгоднее не переработка, а простой переезд на выделенный сервер.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →