RE:NODE

Чему нас научила работа хостинг-платформы на 500+ клиентов

Автор Команда RE:NODE
17 мин чтения


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

Платформа выросла до 500+ клиентов и 1 200+ серверов в играх, VDS, вебе, базах данных и хранилищах, с десятками нод и 30+ сервисами под автоматическим управлением жизненным циклом, при среднем времени деплоя меньше пяти минут. По меркам облаков это небольшой парк. Но именно на таком размере упрощения, сделанные на пятидесяти серверах, перестают быть дешёвыми и начинают приходить письмами.

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

Автоматизируйте восстановление раньше создания#

Любой хостинговый проект начинается с приятной части: кнопки, которая создаёт сервер. Оплата проходит, появляется контейнер, выделяется порт, генерируется пароль, клиент получает письмо. Это радует при разработке и хорошо смотрится на демо.

Но это и самая неважная автоматизация на платформе. Создание происходит один раз на сервер. Восстановление происходит каждый раз, когда падает процесс, перезагружается нода, заполняется диск или неудачно проходит обновление, а за время жизни сервера это случается много раз. Если создание ручное, вы теряете несколько минут на заказ. Если ручное восстановление, вы теряете ночи.

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

  1. Должен ли он работать?
  2. Работает ли он на самом деле?
  3. Если ответы расходятся, что в этой ситуации безопасно сделать?

Весь дизайн сосредоточен в третьем вопросе. "Перезапустить" - очевидный ответ и часто неверный. Допустим, игровой сервер падает при старте из-за сломанного мода. Бесконечные перезапуски только жгут CPU на общей ноде и забивают лог. Поэтому логике восстановления нужна память: она должна знать, сколько попыток уже было, и когда пора остановиться и сообщить человеку.

В RE:NODE это стало наблюдателем, который каждые две минуты проверяет, не уменьшился ли uptime и не ушёл ли сервер в offline. Перезапуски, которые запросил сам клиент, не считаются. Три незапрошенных перезапуска за час добавляют предупреждение на страницу сервера и автоматически открывают тикет; шесть - приостанавливают сервер. Сами числа менее важны, чем схема: автоматическое восстановление, затем автоматическая эскалация, затем жёсткая остановка. Если интересна сторона клиента, она разобрана в статье почему игровой сервер постоянно перезапускается.

расхождениеследующий опроспорогвсё ещё сбоитНаблюдательопрос раз в 2 минНужное и текущееработает? упал?Предупреждение + тикет3 за часПерезапускне по запросу клиентаПриостановка6 за час
Восстановление должно знать, когда остановиться

Второе, к чему восстановление вынуждает, - идемпотентность. Действие по восстановлению будет повторено: наблюдателем, человеком или второй копией наблюдателя, о которой все забыли. "Запустить этот сервер" должно быть безопасно выполнить дважды. "Выделить порт" должно заметить, что порт уже выделен. Каждое неидемпотентное действие в итоге когда-нибудь выполнялось дважды, и результат всегда был хуже, чем если бы оно не выполнялось вообще.

Желаемое состояние, а не скрипты#

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

Модель, с которой стоит начинать с первого дня, - желаемое состояние и сверка. База данных говорит, что должно существовать: этот сервер, столько памяти, эти порты, этот образ, запущен или остановлен. Цикл сравнивает это с тем, что реально есть на ноде, и закрывает разницу по одному безопасному шагу. Создание - это просто сверка с нуля. Восстановление - сверка из сломанного состояния. Смена тарифа, смена порта и миграции - тот же цикл с другой целью.

python
# The whole idea, minus the hard parts.def reconcile(server):    want = desired_state(server)       # from the database    have = observed_state(server)      # from the node    for step in plan(have, want):      # smallest safe steps first        if not step.apply():           # every step is idempotent            record_failure(server, step)            return                     # retry on the next pass    mark_converged(server)

Ничего нового тут нет: так устроено мышление Kubernetes, Terraform и большинства систем управления конфигурацией. Урок не в самом паттерне, а в том, насколько рано он нужен. Не стоит ждать первого наполовину собранного сервера. Прикручивать желаемое состояние к системе, построенной на скриптах, гораздо дороже, чем начать с него, потому что к тому моменту каждый частный случай уже обработан отдельной веткой в скрипте, которую никто не хочет трогать.

Изоляция клиентов - это и есть продукт#

Клиенты покупают не контейнер. Они покупают уверенность, что их сервер ведёт себя одинаково, простаивает сосед или гоняет в цикле плохо написанный плагин. Если этого не дать, остальные пункты списка возможностей не имеют значения.

Есть три ресурса, на которых изоляция определяет, ощущается ли платформа справедливой: CPU, память и диск. Наши твёрдые убеждения по каждому:

РесурсЧто мы делаем всегдаПочему
CPUЖёсткое ограничение до купленной долиБесплатный burst приятен, пока его не отобрали
ПамятьОстановка на лимите, без swapSwap замедляет одного клиента для всех
ДискКвота на сервер с принудительным контролемОдин полный диск не должен быть аварией ноды

В RE:NODE каждый сервер - отдельный контейнер, CPU жёстко ограничен купленной долей, а на лимите памяти ядро останавливает контейнер и он перезапускается начисто, а не уходит в swap - это противоположно поведению upstream Pterodactyl по умолчанию. Последнее удивляет людей. Сервер, который убили и он перезапустился, в моменте выглядит хуже, чем сервер, который медленно свапится. На практике свапящийся сервер забирает с собой дисковый I/O всех остальных на ноде, и больше всех страдает клиент, который ничего не сделал. Механика разобрана в статье Linux swap и OOM killer, а сторону общего железа - в общий CPU и шумные соседи.

Неприятно то, что строгая изоляция делает платформу менее щедрой на вид. Хост, который даёт бесплатно разгоняться до 400% CPU, выиграет бенчмарк у того, кто ограничивает вас оплаченными 200%. Но этот burst взят в долг у соседей и исчезает именно тогда, когда нода загружена, то есть когда он нужен больше всего. Мы предпочитаем продавать меньшее число, которое верно в пятницу в девять вечера.

Изоляция касается и всего, что не является ресурсом. Учётные данные на сервер, а не на аккаунт. Бэкапы, привязанные к серверу, который их создал. Логи, не способные утечь в чужой вывод. Порты, которые выделяются, а не угадываются. В каждом из этих мест упрощение экономит вечер и порождает класс багов, при которых один клиент видит данные другого. Это единственный класс багов, после которого хостинг-платформа не восстанавливает репутацию.

Каждый ручной шаг становится тикетом#

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

Арифметика простая и жёсткая. Шаг, который занимает пять минут времени сотрудника и нужен одному заказу из десяти, звучит безобидно. На нескольких сотнях клиентов это работа на полставки, причём состоящая из прерываний. Хуже того, клиент ждёт, пока это происходит, то есть это ещё и заметная ему задержка.

Как это использовать: если категория тикетов встречается больше нескольких раз, это не проблема поддержки, а недостающая функция. Тикет - это баг-репорт, написанный человеком, который не знал, что это баг. Типичные случаи примерно в том порядке, в котором с ними сталкивается хостинг-платформа:

  • "Откройте мне порт" - клиенту нужен экран с портами, а не человек.
  • "Восстановите вчерашний бэкап" - у бэкапов должна быть кнопка восстановления, которую клиент нажимает сам.
  • "Моему другу нужен доступ, но не к оплате" - нужны субпользователи с детальными правами.
  • "Я загрузил не тот файл, и сервер не запускается" - нужны файловый менеджер и консоль, показывающая настоящую ошибку.
  • "Это упал сервер или у меня проблемы" - нужны страница статуса и графики по каждому серверу.

Не каждый ручной шаг можно убрать. В RE:NODE игровые, app-, web- и серверы баз данных создаются сразу после оплаты, обычно в течение минуты, а вот VDS по-прежнему готовится вручную в течение 24 часов. Это сознательный компромисс: целая виртуальная машина со своей операционной системой ломается чаще, чем контейнер из известного образа, а объёмы пока не оправдывают автоматизацию. Важно принимать такой компромисс осознанно, писать задержку на странице продукта, чтобы никто не удивлялся, и пересматривать решение, когда меняются цифры.

Следствие: пользовательский интерфейс продукта - это в основном перечень тикетов, которые вы больше не получаете. Глядя на хостинг-панель, мы читаем её как список того, что команде надоело делать руками.

RBAC с первого дня, а не с пятидесятого клиента#

Контроль доступа - функция, которую все собираются добавить потом, а потом всегда слишком поздно. Причина не в показной безопасности. Первая версия любой панели моделирует "пользователь владеет сервером", и как только сервером делятся двое - игровое сообщество с тремя админами, агентство и его клиент, - эта модель ломается так, что трещины расходятся по каждой таблице и каждой проверке прав в коде.

Что стоит заложить в первый же день, даже если интерфейс этого пока не показывает:

  1. Право - это один глагол над одним видом объекта: запустить сервер, читать файлы, записывать файлы, видеть оплату.
  2. Роль - именованный набор прав.
  3. Команда - набор людей.
  4. Грант связывает команду с сервером через роль.

Именно такая модель получилась в RE:NODE: роли содержат права, команды содержат людей, гранты связывают команды с серверами; приглашения по ссылке, коду или email, доступ с ограничением по времени и журнал активности по каждому серверу. Для одного клиента с одним сервером это выглядит избыточно. Но это единственная форма, которая переживает встречу с реальными сообществами, где тот, кто платит, не тот, кто перезапускает, а тот, кто перезапускает, не должен иметь возможность удалить мир.

Две детали важнее, чем кажутся. Доступ с ограничением по времени, потому что самая частая ошибка с доступом - не выдать слишком много, а забыть отозвать. И журнал активности, потому что при проблеме первый вопрос всегда "кто это сделал", и без журнала ответом становится спор. Совет для клиентов - в статье субпользователи и минимальные привилегии.

То же правило относится к сотрудникам. Поддержка должна видеть состояние сервера, не читая его файлы, и никому в команде не должен быть нужен root-пароль ноды для ежедневной работы. Если доступ сотрудников устроен по принципу "всё или ничего", то у всех и навсегда будет "всё".

Мониторинг, который будит по делу#

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

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

СигналКто узнаётКогда
Нода недоступнаДежурныйСразу
Диск ноды быстро заполняетсяДежурныйДо заполнения
Задача бэкапа завершилась ошибкойОчередь сотрудниковВ ближайший рабочий час
Один сервер в цикле паденийКлиент, затем тикетПосле порога
Один сервер на 100% CPUНиктоРаботает как продано

С последней строкой люди спорят чаще всего. Сервер клиента на 100% своей доли CPU работает медленно, а не сломан, и платформа не должна ни будить кого-то из-за этого, ни приостанавливать сервер. Он делает ровно то, что велит ограничение. Алерт на это создаёт шум, скрывающий действительно важные сигналы.

Второе правило: оповещайте о симптоме и тренде, а не только о пороге. Допустим, диск ноды заполнен на 85% в три часа ночи. Само по себе это не срочно. Если в полночь было 60%, то очень срочно. Проверка скорости изменения ловит разросшийся лог или бэкап, который не очистил старое, задолго до того, как сработает плоский порог, и не срабатывает на ноде, которая месяц спокойно стоит на 85%.

Третье: у каждого алерта есть ссылка на runbook, пусть короткий. Полусонный человек плохо рассуждает, и сообщение "диск ноды заполняется" плюс три строки о том, куда смотреть сначала, ценнее красивого дашборда. Общая версия этого разбора - в статье мониторинг, который что-то сообщает.

Страница, которая не требует действий человека, - не алерт. Это привычка игнорировать алерты, которую вы в себе воспитываете.

Бэкапы - функция продукта, а не работа ops#

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

Поэтому на самом деле есть две системы бэкапов с разными задачами:

  • Бэкапы платформы защищают бизнес: ночные, копируются на отдельное железо, проверяются, чистятся по расписанию. Клиенты их не трогают. Они существуют на тот день, когда умрёт нода.
  • Бэкапы клиента защищают клиента от самого себя: слоты на каждом тарифе, создаются по требованию или по расписанию, восстанавливаются кнопкой, скачиваются, блокируются от ротации и хранятся вне машины, которую защищают.

Именно клиентский вариант меняет нагрузку на поддержку. Большая часть потери данных на хостинг-платформе - не отказ железа. Это клиент, поставивший плагин, который съел мир, или удаливший не ту папку, или обновивший версию игры, которая не читает старое сохранение. Кнопка восстановления, которую клиент нажимает в полночь, ни у кого не спрашивая, стоит больше любого количества резервирования с нашей стороны.

Два проектных решения стоит повторить. Блокировка: возможность закрепить один бэкап, чтобы ротация его не удалила, потому что нужен часто именно бэкап до начала проблемы, а его ротация собирается удалить первым. И честное удаление: в RE:NODE удаление сервера удаляет его бэкапы, в том числе заблокированные. Некоторых это удивляет, но бэкап, переживший сервер, которому он принадлежит, - это данные, которые вы храните без причины, и об этом нужно говорить прямо, а не оставлять на самостоятельное открытие.

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

Реакция на инциденты - это в основном подготовка#

Сам инцидент - наименьшая часть реагирования на инциденты. То, что определяет исход, происходит за недели до него: можете ли вы понять, каких клиентов это затрагивает, можете ли быстро им сообщить и можете ли потом выяснить, что на самом деле произошло.

Что обязательно нужно на любой платформе такого размера:

  • Возможность перейти от "нода X нездорова" к "затронуты эти клиенты" одним запросом. Если для этого нужна таблица, обновление статуса будет поздним и расплывчатым.
  • Страница статуса, которую во время инцидента обновляет человек, а в остальное время система, размещённая там, где она не разделит судьбу с тем, о чём сообщает.
  • Каждое автоматическое действие записывается в журнал событий: что, когда, почему и какой компонент это сделал. Когда цикл восстановления что-то перезапускает, этот перезапуск должен быть объясним через неделю.
  • Короткий письменный разбор после всего, что заметили клиенты, сосредоточенный на том, что система допустила, а не на том, кто что нажал.

Последний пункт больше про культуру, чем про инструменты. Если разбор ищет виноватого, о почти-инцидентах перестают сообщать, а они - единственные бесплатные уроки.

Что стоило бы сделать иначе#

Если начинать платформу заново завтра, зная то, что известно сейчас, вот какие изменения стоит внести. Ни одно не драматично. Все они дешевле в первый день, чем в тысячный.

Желаемое состояние с первого коммита

Об этом уже было выше, но это самое важное. Каждый шаг выдачи, написанный как скрипт, - будущий проект миграции. Мы завели бы один цикл сверки с самого начала и сделали бы создание, изменение и восстановление одним и тем же путём в коде.

Одно место для лимитов и квот

Лимиты расползаются: память в определении контейнера, диск в инструменте квот, слоты бэкапов в биллинге, порты в таблице выделения. Каждое верно по отдельности, а вместе они расходятся. Все лимиты тарифа мы хранили бы в одной записи, которую читают все компоненты, чтобы смена тарифа была одной записью, а не пятью.

Журнал событий как полноценный продукт

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

Локация как данные, а не допущение

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

Проверять путь восстановления по расписанию

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

Что остаётся без изменений

Строгие лимиты ресурсов. Наблюдатель, который эскалирует, а не перезапускает бесконечно. Бэкапы, которые клиент запускает кнопкой. Детальные роли. Панель, которая показывает настоящий вывод консоли, а не удобное резюме. Каждое из этих решений делало платформу менее щедрой на вид в первый день и сэкономило куда больше, чем стоило.

За всем этим стоит одна привычка: сначала проектировать сбой. Она работает и тогда, когда значительную часть кода пишут AI-агенты: агент напишет путь создания за полдня, а путь восстановления по-прежнему требует, чтобы человек думал над ним всерьёз.

FAQ#

Что самое сложное в работе multi-tenant хостинг-платформы?

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

Что автоматизировать сначала небольшому хосту: выдачу или восстановление?

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

Стоит ли делать RBAC платформе всего с несколькими клиентами?

Да, хотя бы в модели данных. Прикручивать общий доступ к схеме "один пользователь владеет одним сервером" - значит затрагивать каждую проверку прав в коде. Роли и команды можно скрывать в интерфейсе, пока клиенты их не попросят, но таблицы должны существовать с самого начала.

Сколько алертов должно будить человека?

Очень мало. Будите только если клиенты затронуты или вот-вот будут затронуты, система не может исправить это сама, а ожидание сделает хуже. Недоступная нода и быстро заполняющийся диск подходят. Сбой бэкапа - утренний тикет. Один сервер на полной загрузке CPU вообще не алерт.

Почему останавливать сервер на лимите памяти, а не давать ему свапиться?

Потому что swap на общем железе превращает проблему памяти одного клиента в проблему диска для всех. Чистый перезапуск виден и исправим клиентом; нода, тихо буксующая в swap, задевает людей, которые ничего не сделали, и её гораздо сложнее диагностировать.