Один сервер под всё: где экономия превращается в убыток
Когда бюджет ограничен, идея посадить сайт, CRM, почтовый релей, бекап-скрипты и внутреннюю вики на один сервер выглядит здравой: одна фиксированная плата вместо пяти, меньше администрирования, меньше писем от биллинга. И на первых порах это действительно работает — сервер справляется, все сервисы отвечают, никто не жалуется. Проблема в том, что экономия здесь считается только по одной оси — стоимости аренды — и не учитывает, что происходит, когда что-то на этом сервере ломается не по расписанию. А ломается всегда не по расписанию.
Содержание
Почему один сервер под всё кажется экономией
Арифметика на бумаге выглядит убедительно. Пять VPS по условной цене X — это 5X в месяц. Один сервер, который тянет всё то же самое по CPU и RAM с запасом, — условно 2X-3X. Разница ощутимая, особенно для небольшой компании, где каждая статья расходов на инфраструктуру видна в отчёте.
К прямой экономии добавляется вторая, менее очевидная: административная. Один сервер — это один набор обновлений системы, один файрвол, один SSH-ключ, одна точка мониторинга. Вместо того чтобы синхронизировать патчи безопасности на пяти хостах, вы делаете это один раз. Для команды из одного-двух администраторов это реальный выигрыш времени, и умалять его не стоит.
Проблема в том, что оба этих расчёта — про стационарное состояние, когда всё работает штатно. Они ничего не говорят о том, что будет, когда один из пяти сервисов начнёт вести себя плохо. А статистически на длинной дистанции плохо себя начинает вести не «если», а «когда» — рано или поздно у любого сервиса случается утечка памяти, зависший процесс, сбойный деплой или уязвимость в зависимости. На отдельном сервере это авария одного сервиса. На общем — потенциально авария всех.
Риск №1: один сервис ест ресурсы — страдают все соседи
Самый частый сценарий: в одном из приложений — например, в PHP-скрипте, в Node.js-сервисе или в фоновом воркере — есть утечка памяти. Процесс медленно, за часы или дни, съедает всё больше RAM. На отдельном сервере это его личная проблема: он упадёт, перезапустится по systemd, максимум пользователи этого одного сервиса увидят ошибку. На общем сервере происходит другое.
Когда свободная память заканчивается, ядро Linux не выключает вежливо только виновника. Сначала система уходит в своп (если он есть), и это резко замедляет вообще все процессы на машине — включая базу данных для CRM и веб-сервер для сайта, у которых с самой утечкой не было ничего общего. Если свопа нет или он тоже кончился, включается OOM killer, который выбирает жертву по своей эвристике (oom_score_adj), и совершенно не обязан убить именно тот процесс, который жрёт память — иногда под раздачу попадает PostgreSQL или nginx.
# посмотреть, кого и когда убил OOM killer
dmesg -T | grep -i "killed process"
journalctl -k -o short-iso | grep -i "out of memory"
# текущее потребление памяти по процессам, отсортировано
ps aux --sort=-%mem | head -15
То же самое с CPU и диском. Один сервис, который начал писать логи без ротации, может забить диск в ноль — и тогда падают вообще все базы данных на сервере, потому что запись на диск невозможна ни для кого. Один процесс, ушедший в busy-loop, может выесть все ядра CPU, и веб-сервер начнёт отдавать 502 не потому что с ним что-то не так, а потому что ему физически не достаётся процессорного времени.
Частичное решение — контроль ресурсов на уровне cgroups, если сервисы запущены как systemd-юниты или в Docker-контейнерах:
# ограничить контейнер по памяти и CPU
docker run -d --name crm \
--memory=2g --memory-swap=2g \
--cpus=1.5 \
crm-image:latest
# то же для systemd-юнита без Docker
# в /etc/systemd/system/worker.service, секция [Service]:
# MemoryMax=2G
# CPUQuota=150%
Это снижает вероятность того, что один процесс заберёт себе всё, но не устраняет риск полностью: ядро одно на всех, диск один на всех, сетевой стек один на всех. Контейнеризация — это изоляция ресурсов внутри общего ядра, а не отдельная машина. При достаточно сильном сбое общие лимиты не спасают: например, диск, забитый логами одного контейнера, всё равно доступен только один на всех, и --memory не защищает соседей от переполненного /var/log.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверРиск №2: обслуживание одного сервиса тормозит всё остальное
Второй риск менее драматичный, но регулярный: любое обслуживание одного сервиса задевает остальные просто потому, что они физически на той же машине.
Обновление ядра требует перезагрузки — падают все сервисы сразу, а не только тот, ради которого вы обновляетесь. Миграция базы данных, которая требует эксклюзивной блокировки таблиц на несколько минут, забирает CPU и I/O у соседних процессов, даже если формально они «независимы». Смена версии PHP или Node.js для одного приложения нередко тянет за собой пересборку общих системных библиотек, а перезапуск веб-сервера (nginx, Apache) ради конфигурации одного сайта на секунду обрывает соединения ко всем остальным сайтам на этом же веб-сервере.
Практический пример: у вас на одном сервере крутится интернет-магазин (production, деньги) и внутренняя админ-панель для склада. Вечером пятницы нужно накатить патч безопасности на ядро — CVE критичный, тянуть нельзя. Перезагрузка занимает две минуты, но эти две минуты недоступен и магазин, хотя патч вообще не про него, а про уязвимость в сетевом стеке, которую эксплуатировали через совершенно другой сервис.
# проверить, какое обновление требует перезагрузки
cat /var/run/reboot-required 2>/dev/null && echo "нужна перезагрузка"
cat /var/run/reboot-required.pkgs 2>/dev/null
# на RHEL/AlmaLinux — needs-restarting из yum-utils
needs-restarting -r
Дальше это превращается в организационную проблему: любое окно обслуживания нужно согласовывать не с владельцем одного сервиса, а со всеми, кто на сервере есть. Если склад и магазин принадлежат разным командам с разным допустимым временем простоя, найти общее окно становится отдельной головной болью, которой не было бы, будь это разные машины.
Риск №3: конфликты версий и зависимостей
Третий риск менее заметен на старте, но накапливается со временем: разные сервисы на одном сервере часто хотят разные версии одних и тех же системных компонентов.
Классический пример — PHP. Легаси-CRM написана под PHP 7.4 с конкретным набором расширений, а новый внутренний сервис требует PHP 8.3 из-за используемого фреймворка. Держать обе версии одновременно можно (через alternatives, разные FPM-пулы, разные Docker-контейнеры), но это уже не «просто один сервер» — это отдельная настройка изоляции внутри сервера, которая по сложности приближается к тому, от чего вы пытались уйти консолидацией.
# несколько версий PHP на одном сервере через FPM-пулы
# /etc/php/7.4/fpm/pool.d/crm.conf -> слушает unix:/run/php/php7.4-crm.sock
# /etc/php/8.3/fpm/pool.d/new-app.conf -> слушает unix:/run/php/php8.3-app.sock
# nginx: разные server { } блоки указывают на разные сокеты
Похожая история с системными библиотеками (OpenSSL, glibc, libpq) и с Python-окружениями: одно приложение требует numpy==1.24, другое — numpy==2.0, и без виртуальных окружений (venv, pyenv, conda) они конфликтуют напрямую в системном site-packages.
# изоляция Python-зависимостей per-сервис — обязательна на общем сервере
python3 -m venv /opt/app1/venv
/opt/app1/venv/bin/pip install -r /opt/app1/requirements.txt
python3 -m venv /opt/app2/venv
/opt/app2/venv/bin/pip install -r /opt/app2/requirements.txt
Технически всё решаемо: контейнеры, виртуальные окружения, отдельные FPM-пулы. Но это дополнительный слой сложности, который нужно поддерживать бессрочно, и именно эта сложность имеет свойство накапливаться. Через год на сервере оказывается зоопарк из версий, где апгрейд одного компонента (например, обновление PHP ради закрытия уязвимости) требует ревизии всех остальных сервисов — не потому что вы плохо спланировали, а потому что план на пять сервисов и план на пятнадцать выглядят принципиально по-разному.
Риск №4: инцидент безопасности не остаётся локальным
Это самый серьёзный риск, и его часто недооценивают, потому что до первого реального инцидента он кажется абстрактным.
Если один из сервисов на сервере скомпрометирован — устаревший плагин WordPress с известной RCE-уязвимостью, слабый пароль в админке, неисправленная CVE в зависимости, — атакующий получает исполнение кода не «в песочнице этого сайта», а на реальной машине с реальным доступом ко всему остальному. Дальше вопрос только в том, насколько хорошо настроена изоляция между сервисами на уровне ОС.
Практические векторы бокового перемещения на общем сервере:
- Общая файловая система. Если веб-сервер работает от одного системного пользователя для всех сайтов, скомпрометированный сайт может читать и писать файлы соседних проектов — включая конфиги с паролями от баз данных.
- Общая сеть. Без сегментации по Docker-сетям или файрволу между контейнерами скомпрометированный контейнер может достучаться до портов соседних баз данных, даже если они не должны быть доступны извне.
- Общие учётные данные и ключи. SSH-ключи,
.env-файлы, токены API нередко лежат в домашней директории или в местах, доступных любому процессу с правами того же пользователя. - Cron и systemd-таймеры с широкими правами. Если бекап-скрипт запущен от root и читает данные всех сервисов, компрометация любого из них через уязвимость в самом скрипте открывает доступ ко всему остальному.
# базовая проверка: под каким пользователем реально работает каждый сервис
ps -eo user,cmd | grep -E "nginx|php-fpm|node|postgres" | sort -u
# сетевая изоляция контейнеров: у каждого сервиса — своя docker-сеть,
# а не общий bridge "по умолчанию"
docker network create --internal crm-net
docker network create --internal shop-net
Минимально разумная защита на общем сервере — отдельные системные пользователи под каждый сервис (никаких общих www-data для пяти разных проектов), отдельные Docker-сети без ненужного --link между контейнерами, и файрвол, который по умолчанию запрещает сервисам ходить друг к другу, кроме явно разрешённых портов. Подробнее об этом подходе — в статье про изоляцию сервисов через Docker для безопасности. Но даже при аккуратной настройке остаётся факт: ядро ОС общее, а значит теоретическая поверхность атаки через уязвимости самого ядра или container escape никуда не девается — на отдельной машине скомпрометированный сервис физически не может дотянуться до других.
Где проходит разумная граница консолидации
Из всего этого не следует вывод «никогда не консолидируйте сервисы» — иначе любая компания с десятком внутренних инструментов держала бы десяток серверов, что для многих реально избыточно. Вопрос в том, что консолидировать, а что — нет.
Практический критерий — не «сколько это стоит по отдельности», а «что случится с бизнесом, если это конкретное сочетание сервисов упадёт одновременно». Разделите сервисы на уровни критичности и решайте по уровню, а не по факту существования свободных ресурсов на сервере.
| Уровень | Примеры | Можно ли на общем сервере |
|---|---|---|
| Критичный, revenue-facing | Продакшен интернет-магазин, платёжный шлюз, клиентский API | Нет — отдельная машина или как минимум отдельный VPS на пиковую нагрузку |
| Важный, внутренний | CRM, ERP, бухгалтерия компании | Желательно отдельно от критичного уровня; друг с другом — осторожно, если разные владельцы SLA |
| Вспомогательный | Внутренняя вики, дашборд мониторинга, staging-окружение | Можно консолидировать между собой |
| Разовые задачи | Cron-скрипты, разовые ETL-джобы, тестовые песочницы | Хорошие кандидаты на общий «утилитарный» сервер |
Ключевое правило простое: никогда не смешивайте сервис, за простой которого вам выставят претензию клиенты или начальство, с сервисом, простой которого никто не заметит. Именно это смешение и превращает экономию в убыток — не сама консолидация, а консолидация без учёта разной цены простоя. У часа работы админа против экономии на тарифе есть своя граница выгоды, и она смещается в минус ровно в тот момент, когда авария вспомогательного сервиса блокирует критичный.
Если решаете консолидировать несколько сервисов уровня «важный» или выше на одной машине, минимальный набор мер:
- отдельные системные пользователи и права для каждого сервиса;
- лимиты по CPU/RAM на уровне cgroups или Docker (
--memory,--cpus), чтобы один процесс физически не мог забрать всё; - отдельные Docker-сети без прямой видимости друг друга;
- мониторинг каждого сервиса отдельно, а не «сервер жив — значит всё живо» (когда сам мониторинг стоит на том же сервере, это отдельная ловушка — она разобрана в статье про мониторинг, который стоял на том же сервере и умер вместе с ним);
- регулярные бекапы конфигурации и данных каждого сервиса по отдельности, чтобы восстановление одного не требовало трогать остальные.
И отдельно про ресурсные лимиты — они не панацея, но снижают вероятность каскада. Разбор конкретных значений --memory и --cpus для разных типов нагрузки — в статье про лимиты CPU и памяти в Docker.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если сервисы в разных Docker-контейнерах, разве это не такая же изоляция, как отдельные серверы?
Частичная. Контейнеры изолируют файловую систему, процессы и, при правильной настройке лимитов, часть ресурсов. Но ядро ОС общее для всех контейнеров на хосте — уязвимость типа container escape или банальная нехватка диска на хосте затрагивает всех. Это существенно лучше, чем вообще без изоляции, но не эквивалент отдельной виртуальной машины.
Как понять, что пора разносить сервисы по разным серверам, а не продолжать консолидировать?
Ориентир — не загрузка CPU/RAM (её можно нарастить), а частота инцидентов, которые задевают несвязанные сервисы: если за последние несколько месяцев проблема одного сервиса минимум дважды роняла или тормозила другой, это сигнал, что граница критичности нарушена и пора разделять.
Можно ли держать staging и production на одном сервере, если это временно, для экономии?
Технически можно, но рискованно даже «временно» — staging по определению менее стабилен (тестовые деплои, экспериментальные конфиги), и именно там чаще случаются утечки памяти и падения, которые способны задеть соседний production через общие ресурсы. Если действительно нужно сэкономить, staging лучше держать на минимальном отдельном VPS, а не рядом с продакшеном.
Что делать, если бюджет реально не позволяет несколько серверов прямо сейчас?
Начните с изоляции внутри одного сервера — отдельные пользователи, cgroups-лимиты, отдельные Docker-сети — и держите план миграции критичного сервиса на отдельную машину как первый пункт бюджета при следующем расширении. Частичная изоляция сегодня лучше, чем полное отсутствие изоляции до появления денег на второй сервер.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →