OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

Multi-tenant SaaS на VDS: изоляция клиентов через systemd и cgroup

Разберём, как построить multi-tenant SaaS на одном или нескольких VDS так, чтобы клиенты не мешали друг другу и не заваливали всю платформу. Поговорим про модели изоляции, cgroup v2 и systemd, лимиты CPU, памяти и IO, а также минимальный мониторинг.
Multi-tenant SaaS на VDS: изоляция клиентов через systemd и cgroup

Multi-tenant SaaS на VDS кажется простой идеей: поднимаем один мощный сервер, ставим приложение, добавляем базу — и вперёд. На практике всё сложнее: один «шумный» клиент выедает CPU, другому не хватает памяти, у третьего отчёты падают по таймауту. А в итоге страдает вся платформа, метрики скачут, пользователи недовольны, а DevOps внезапно превращается в SRE.

В этом тексте я соберу практические подходы к multi-tenant SaaS на VDS: архитектурные варианты, уровни изоляции, использование cgroup и systemd для установки лимитов ресурсов, сетевые ограничения и подходы к мониторингу. Цель — чтобы вы могли спроектировать платформу, которая не развалится от одного неаккуратного клиента.

Модели multi-tenant: с чего вообще выбирать

Multi-tenant по факту — это ответ на два вопроса:

  • Как вы изолируете данные клиентов?
  • Как вы изолируете ресурсы (CPU, RAM, диски, сеть) клиентов друг от друга?

На уровне данных выбор обычно понятен: отдельные базы, отдельные схемы, разделение по колонке tenant_id. На уровне ресурсов всё гораздо интереснее, особенно на VDS с ограниченными CPU и RAM.

Основные модели изоляции для SaaS на VDS

Для одного или нескольких VDS чаще всего встречаются такие варианты:

  1. Общее приложение + общий runtime (PHP-FPM пул, один Node.js процесс, один Python WSGI-пул). Клиенты делят всё.
  2. Общее приложение, но разные пулы/процессы (отдельные PHP-FPM пулы на клиента или группу клиентов, отдельные worker-процессы). Можно навешивать лимиты cgroup.
  3. Контейнеры на одном VDS (Docker/Containerd/k3s и аналоги). Каждый клиент — контейнер/namespace с лимитами.
  4. Отдельный VDS на клиента (single-tenant на уровне сервера). Самая консервативная модель, чаще — для крупных чеков.

Мы сосредоточимся на втором и третьем варианте, потому что они позволяют реализовать настоящий multi-tenant на одном или нескольких VDS, не переходя к полному single-tenant.

Чем плох «наивный» multi-tenant без изоляции

Стартовый сценарий обычно выглядит так: на VDS крутится Nginx/Apache, один PHP-FPM/Node.js пул, одна база, несколько десятков или сотен клиентов. Пока нагрузки мало — всё норм.

Проблемы начинаются после появления «тяжёлых» клиентов:

  • один клиент запускает тяжёлые отчёты, которые держат CPU на 100% по 5–10 минут. Остальные клиенты получают высокую латентность;
  • кому-то завезли интеграцию с внешней системой, которая гонит много данных, и процесс стал активно жрать RAM, провоцируя OOM-killer или swap-шторм;
  • клиент массово загружает или выгружает файлы, колеблется IO, всем остальным становится больно.

Ключевая идея: в multi-tenant SaaS без изоляции вы продаёте не «выделенные ресурсы», а «право пострадать вместе с соседями».

На VDS эта проблема обостряется: вы не контролируете слой ниже (hypervisor), но отвечаете за всё, что сверху. Поэтому граница изоляции между клиентами должна быть внутри вашего VDS — на уровне процессов, cgroup и сервисов.

Если вам нужен быстрый старт без администрирования железа и гипервизора, проще сразу строить SaaS на управляемом облачном VDS и сосредоточиться на логике изоляции внутри системы: cgroup, systemd и грамотное разбиение на сервисы.

Уровни изоляции на VDS: от грубого к тонкому

В Linux сегодня изоляцию для multi-tenant SaaS в основном строят вокруг двух механизмов:

  • cgroup (control groups) — лимиты и учёт ресурсов: CPU, память, IO, сеть (через дополнительные подсистемы и класс трафика);
  • namespaces — имитация отдельных окружений (process, mount, network и т.д.), чаще через контейнеры.

Если контейнеризация у вас ещё не заехала или по бизнес-причинам не подходит, вы всё равно можете получить хороший multi-tenant за счёт systemd и cgroup v2.

cgroup v1 vs cgroup v2 для SaaS

Большинство современных дистрибутивов (Debian 12, Ubuntu 22.04+, RHEL 9, Alma/Rocky 9) по умолчанию работают с cgroup v2 или в гибридном режиме. Для новых проектов имеет смысл сразу ориентироваться на v2:

  • единая иерархия;
  • более предсказуемое деление CPU;
  • нормальная работа с распределением ресурсов между несколькими группами;
  • хорошая интеграция с systemd (slice, service, scope).

С точки зрения SaaS это значит: вы можете выразить SLA в терминах «клиент не может съесть больше X CPU и Y RAM» и жёстко enforce-ить это на уровне ядра.

Подробно про связку systemd и cgroup применительно к PHP-FPM я разбирал в статье про разделение PHP-FPM по slice и cgroup; многие идеи оттуда напрямую переносятся и на multi-tenant SaaS.

Схема архитектуры multi-tenant SaaS на одном VDS с cgroup и slice

Архитектура: как маппить клиентов на процессы и cgroup

Перед тем как крутить ручки cgroup, важно определиться, что именно вы будете ограничивать. На практике встречаются три подхода:

  1. Per-tenant процессы: у каждого клиента свои worker-процессы, которые легко положить в отдельную cgroup.
  2. Per-plan группы: клиенты пакуются в «тарифные классы», и cgroup создаются под тариф (например, Basic/Pro/Enterprise).
  3. Per-feature пулы: отдельные пулы под ресурсоёмкие подсистемы (отчёты, импорт/экспорт, интеграции), а внутри уже multi-tenant по данным.

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

Пример: PHP-FPM multi-tenant с отдельными пулами

Классическая модель для PHP SaaS:

  • на уровне Nginx запросы клиентов маршрутизируются по домену или поддомену на разные PHP-FPM пулы;
  • каждый пул крутится под своим Unix-пользователем;
  • systemd кладёт каждый пул в свой slice или service с лимитами.

Так вы получаете изоляцию не только по ресурсам, но и по правам на файловой системе.

Пример: Node.js / Python multi-tenant с worker-процессами

Если SaaS написан на Node.js или Python, вы можете:

  • поднимать отдельный процесс приложения на каждого крупного клиента или группу клиентов;
  • запускать тяжёлые задачи в отдельных воркерах (Celery, RQ, custom workers) с отдельным systemd unit;
  • класть эти unit в slice, который имеет свои лимиты cgroup.

Логика маршрутизации (к какому процессу шлём запрос) реализуется либо в HTTP-роутере (Nginx/Haproxy), либо на уровне внутреннего RPC.

Практичный путь для старта — развернуть SaaS на надёжном VDS, чтобы гибко масштабировать ресурсы под рост количества tenant-ов.

systemd + cgroup: как это реально выглядит

Практический минимум для multi-tenant SaaS на VDS — научиться использовать systemd как фронтенд к cgroup. Это избавляет от необходимости руками писать в /sys/fs/cgroup и дружит конфигурации с перезапусками.

Слои systemd: slice, service, scope

Для SaaS нас интересуют два основных объекта:

  • slice — логическая группа для сервисов, с которой удобно работать как с контуром (например, все процессы тарифа Pro);
  • service — конкретная служба: worker-пул, task runner, отдельный инстанс приложения.

Типичный паттерн: вы создаёте один slice на тариф или группу клиентов, а затем запускаете сервисы внутри этого slice.

Базовая конфигурация slice с лимитами

Например, для тарифа Pro вы хотите дать до 2 vCPU и 4 ГБ RAM, но не дать клиентам уронить весь VDS при всплеске.

[Slice]
CPUQuota=200%
MemoryMax=4G
IOReadBandwidthMax=/dev/vda 50M
IOWriteBandwidthMax=/dev/vda 20M

Важные моменты:

  • CPUQuota=200% на двух vCPU означает, что slice может занять оба виртуальных ядра на 100%, но не больше.
  • MemoryMax=4G — жёсткий потолок. При его превышении процессы в этом slice будут убиваться OOM-killer внутри cgroup, а не хаотично по всей системе.
  • IOReadBandwidthMax и IOWriteBandwidthMax работают только с cgroup v2 и поддерживаемыми драйверами; стоит проверить в документации вашего дистрибутива и ядра.

Привязка сервисов клиента к slice

Дальше вы описываете unit сервиса приложения или worker-а, указывая для него нужный slice.

[Unit]
Description=SaaS Pro tenant workers

[Service]
Slice=saas-pro.slice
User=tenant_pro
Group=tenant_pro
ExecStart=/usr/bin/php-fpm -y /etc/php/saas_pro_fpm.conf
Restart=always

[Install]
WantedBy=multi-user.target

Все процессы из этого сервиса автоматически попадают в cgroup saas-pro.slice и начинают жить по его лимитам.

Лимиты CPU: честный multi-tenant без «шумных соседей»

Лимитирование CPU в multi-tenant сценариях — одна из самых недооценённых тем. Часто вспоминают только про память, но именно CPU-спайки чаще всего убивают SLA.

Как распределять CPU между клиентами

Есть два основных механизма:

  • CPUQuota — верхний потолок («не больше X% CPU») для slice или service;
  • CPUWeight — относительный приоритет между группами («если батл за CPU — у кого выше вес, тот и молодец»).

В multi-tenant SaaS практично сочетать оба:

  • через CPUQuota вы декларируете жёсткий максимум тарифа;
  • через CPUWeight — приоритеты между тарифами (например, Pro > Basic).

Пример для трёх тарифов:

# Basic: до 50% одного vCPU, низкий приоритет
[Slice]
CPUQuota=50%
CPUWeight=50

# Pro: до 200%, средний приоритет
[Slice]
CPUQuota=200%
CPUWeight=100

# Enterprise: до 400%, высокий приоритет
[Slice]
CPUQuota=400%
CPUWeight=200

Так вы можете визуально привязать лимиты к тарифам и честно описать их в коммерческом предложении.

Лимиты памяти: баланс между OOM и UX

С памятью всё сложнее: жёсткий лимит MemoryMax спасает от падения всего VDS, но убьёт длинный отчёт клиента, если тот выберет слишком широкие параметры. Поэтому важно грамотно подобрать пороги и иметь graceful degradation.

Базовые лимиты памяти для multi-tenant

Для SaaS на одном VDS типичный подход:

  • определить «технический минимум» памяти для своего стека (Nginx, база, кеш, фоновый воркер, мониторинг);
  • вычесть это из общего объёма RAM VDS;
  • остаток разделить между тарифами, оставив 10–20% на буферы и форс-мажоры.

Пример для VDS с 16 ГБ RAM:

  • системные сервисы, база, кеши, мониторинг — 6 ГБ;
  • остаток под SaaS-воркеров — 10 ГБ;
  • из них 2 ГБ — буфер (никакому тенанту не отдаём как жёсткий лимит);
  • 8 ГБ делим между Basic/Pro/Enterprise по выбранной пропорции.

Потом эти числа превращаются в MemoryMax для slice или отдельных service.

Что делать, когда tenant упирается в MemoryMax

Важно не только выставить лимит, но и правильно отреагировать на его достижение:

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

Таким образом OOM внутри slice превращается не в катастрофу, а в контролируемое ограничение, интегрированное в ваш продукт.

IO и сеть: когда диски и трафик тоже multi-tenant

CPU и RAM — не единственные ресурсы, о которых стоит думать. В multi-tenant SaaS на VDS часто возникает проблема: один клиент начал массовый импорт или экспорт и положил диск.

Ограничение IO через cgroup

В cgroup v2 IO-лимиты задаются через контроллер io. Через systemd это выражается параметрами:

  • IOReadBandwidthMax / IOWriteBandwidthMax — ограничение пропускной способности по устройству;
  • IOReadIOPSMax / IOWriteIOPSMax — ограничение числа операций в секунду.

Использовать их нужно аккуратно, иначе можно навредить и самому приложению:

  • ставьте лимиты только на тяжёлые batch-процессы (импорт, экспорт, генерацию отчётов), а не на весь веб-слой;
  • начинайте с мягких ограничений и повышайте их по мере измерений;
  • следите за метриками latency и backlog на уровне базы и приложения.

Сеть: user space лимиты и QoS

На большинстве VDS вы планируете сеть не столь агрессивно, как CPU/RAM, но в multi-tenant всё равно встречается:

  • клиент, который массово скачивает или заливает файлы и забивает канал;
  • внешние интеграции, создающие много исходящих соединений.

В боевых системах чаще используют:

  • лимиты скорости на уровне Nginx/Haproxy (download/upload per connection или per tenant);
  • троттлинг API на уровне приложения (rate limit per tenant или per token);
  • prefetch и буферизацию, чтобы тяжёлые клиенты не забирали CPU во время ожидания сети.

Теоретически можно использовать eBPF/TC для сетевого QoS, но для большинства SaaS на одном-двух VDS это избыточно и сложнее поддерживать, чем лимиты на уровне приложения.

Иллюстрация лимитов CPU, памяти и IO для разных tenant-ов через systemd slice

Учет ресурсов по tenant-ам: метрики и биллинг

Multi-tenant архитектура не заканчивается на лимитах: вы захотите считать, кто сколько потребляет. Это важно и для внутренней оптимизации, и для тарификации.

Что имеет смысл измерять

Минимальный набор метрик на tenant или тариф:

  • CPU time (user+system) за период;
  • использование памяти (среднее и пики);
  • количество запросов и средняя/95p/99p латентность;
  • объём операций чтения и записи (по возможности — хотя бы на уровне подсистемы, обслуживающей конкретного тенанта);
  • объём переданных данных (если у вас тарификация по трафику).

Часть этих метрик можно извлечь напрямую из cgroup (через системные файлы или экспортеры), часть — на уровне приложения (например, прометки в Prometheus с label tenant_id).

Связка cgroup и метрик

Практический вариант:

  • каждый tenant или тариф — это slice/service с понятным именем (например, saas-tenant-123.service);
  • метрики по cgroup вытягиваются Prometheus-экспортером, который маппит имя cgroup на tenant_id или тариф;
  • дальше вы строите дашборды: кто забрал сколько CPU, памяти, IO и т.д.

Это даёт прозрачность и вовремя подсказывает, когда клиенту или тарифу пора переехать на отдельный VDS или получить другие лимиты.

Когда multi-tenant на одном VDS уже не тянет

Рано или поздно один физический VDS становится узким местом. Признаки типичные:

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

На этом этапе вы переходите от «один большой VDS с multi-tenant внутри» к «несколько VDS или кластеров, каждый из которых внутри тоже multi-tenant». Архитектурно это выглядит как:

  • разделение на шардированные кластеры по регионам, типам клиентов или ID-шардированию;
  • выделение инфраструктуры под крупных клиентов (почти single-tenant), при этом мелкие продолжают жить на общих кластерах;
  • унификация деплоймента, чтобы новые кластеры поднимались и конфигурировались автоматически.

Важно: даже при переходе на несколько VDS всё, что вы сделали с cgroup и systemd, остаётся актуальным — просто теперь это применяется на каждом сервере по отдельности.

Типичные грабли и как их обойти

И напоследок — несколько проблем, с которыми чаще всего сталкиваются при построении multi-tenant SaaS на VDS.

Грабля 1: слишком жёсткие лимиты с самого начала

Часто хочется «перестраховаться» и сразу выставить строгие CPUQuota и MemoryMax. В итоге приложение задыхается, фоновые задачи не успевают, а вы вынуждены экстренно поднимать лимиты.

Лучше подход:

  • сначала померить нормальную рабочую нагрузку под синтетическим и реальным трафиком;
  • добавить 30–50% запаса;
  • выставить эти значения как исходные лимиты;
  • дальше плавно их донастраивать по метрикам.

Грабля 2: отсутствие границы между «вебом» и «батчем»

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

Решение: разделить критичные по латентности веб-запросы и тяжёлые фоновые работы:

  • отдельный пул или сервис для web (жёсткий SLA по latency, приоритет по CPU и памяти);
  • отдельный пул или сервис для batch (ниже приоритет, можно сильнее ограничить CPU и IO);
  • очереди задач (RabbitMQ, Redis и т.п.), чтобы гибко регулировать конкуренцию worker-ов.

По работе воркеров и очередей через systemd у меня есть отдельный разбор — как запускать очереди и worker-ы под systemd и supervisorом; он хорошо дополняет эту статью.

Грабля 3: игнорирование IO и swap

Часто думают: «главное — CPU и память, диск сам разберётся». На деле:

  • одно неудачное решение по кешированию или логированию может начать активно долбить диск;
  • активный swap на VDS превращает всё приложение в «слайд-шоу».

Рекомендации:

  • следите за метриками IO и swap отдельно для каждого VDS и, по возможности, для тяжёлых сервисов;
  • не злоупотребляйте swap; лучше добавить RAM или вынести часть воркеров на отдельный VDS;
  • по возможности используйте быстрые диски (SSD или NVMe) для критичных компонентов.

Выводы

Multi-tenant SaaS на VDS — это не «поставить одно приложение и завести много клиентов». Чтобы платформа жила стабильно, нужно:

  • выбрать модель изоляции: per-tenant, per-plan или hybrid;
  • научиться управлять ресурсами через cgroup v2 и systemd (slice + service);
  • чётко задать и документировать лимиты CPU, памяти и IO;
  • развести веб-трафик и тяжёлые batch-задачи;
  • собирать метрики по tenant-ам и тарифам, чтобы видеть, кто как нагружает систему;
  • уметь вовремя масштабироваться на несколько VDS или выделенные инсталляции для крупных клиентов.

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

Поделиться статьей

Вам будет интересно

Self‑hosted feature flags: OpenFeature, Unleash и Flagsmith OpenAI Статья написана AI (GPT 5)

Self‑hosted feature flags: OpenFeature, Unleash и Flagsmith

Feature flags перестали быть игрушкой продуктовых команд: без rollout и feature toggle сложно делать безопасные релизы, A/B‑тесты ...
ACME‑клиенты для SSL: Certbot, lego, acme.sh и builtin‑интеграции OpenAI Статья написана AI (GPT 5)

ACME‑клиенты для SSL: Certbot, lego, acme.sh и builtin‑интеграции

ACME стал стандартом автоматической выдачи SSL‑сертификатов у Let’s Encrypt и коммерческих центров сертификации. Разберём четыре п ...
Static sharding и умный DNS: ускоряем статику без боли OpenAI Статья написана AI (GPT 5)

Static sharding и умный DNS: ускоряем статику без боли

Static sharding когда‑то был популярным приёмом: статику раскидывали по нескольким поддоменам, чтобы обойти лимиты браузера на кол ...