
Ограничения CPU и памяти Docker: защита VPS от одного контейнера
Измеряем потребление контейнера, задаём memory, swap, CPU и PIDs limits, диагностируем OOMKilled и переносим проверенные ограничения в Compose.
Содержание
Без явных ограничений контейнер может занять почти всю доступную память и процессорное время хоста. На небольшой VPS зациклившийся процесс способен оставить без ресурсов базу данных, веб-сервер и SSH-сессию администратора. Лимиты уменьшают этот риск, но слишком низкие значения сами становятся причиной отказа.
Что ограничивать и в каком порядке
Сначала измерьте потребление при старте, обычной и пиковой нагрузке. Затем задайте контейнеру предел памяти, решите, разрешено ли ему использовать swap, ограничьте процессорное время и число процессов. После перезапуска проверьте сохранённые настройки, docker stats, задержку ответов, healthcheck и поле OOMKilled. Оставьте ресурсы для ядра, службы Docker и остальных программ VPS.
Снимите исходные показатели
На хосте с Docker откройте интерактивную таблицу потребления ресурсов работающими контейнерами. Выйти из неё можно клавишами Ctrl+C, сами контейнеры продолжат работу:
docker stats
Разовый снимок удобнее сохранить без интерактивного обновления:
docker stats --no-stream \
--format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.PIDs}}'
Наблюдайте не только спокойный процесс. Отдельно проверьте:
- холодный запуск;
- прогрев кеша;
- импорт или миграцию;
- фоновую задачу;
- ожидаемый пик запросов;
- временную недоступность зависимости;
- корректную остановку и повторный запуск.
Одно среднее значение скрывает короткие пики. Сопоставляйте показатели Docker с состоянием хоста: свободной памятью, swap, средней нагрузкой, дисковыми операциями и сообщениями ядра.
Задайте тестовые пределы через docker run
Пример ограничивает тестовый сервис, а не предлагает готовые production-значения:
docker run -d --name limited-demo \
--memory=256m \
--memory-swap=256m \
--cpus=0.50 \
--pids-limit=128 \
-p 127.0.0.1:8080:80 \
nginx:alpine
Здесь --memory=256m задаёт жёсткий предел оперативной памяти. Равное значение --memory-swap означает, что дополнительный swap контейнеру не предоставляется. OOM означает нехватку памяти: ядро может завершить процесс, когда тот просит больше установленного предела. Такая политика подходит не каждому приложению.
--cpus=0.50 ограничивает доступное процессорное время примерно половиной одного CPU, но ничего не резервирует. Если host перегружен, контейнер может получить меньше. --pids-limit сдерживает бесконтрольное создание процессов и потоков, однако должен учитывать реальную модель приложения.
Проверьте применённые значения
docker inspect limited-demo --format \
'memory={{.HostConfig.Memory}} swap={{.HostConfig.MemorySwap}} nano_cpus={{.HostConfig.NanoCpus}} pids={{.HostConfig.PidsLimit}}'
docker stats --no-stream limited-demo
Docker показывает часть величин в байтах и NanoCPUs, поэтому не сравнивайте строку механически с исходным YAML. Важно, чтобы значения были ненулевыми и соответствовали рассчитанным пределам.
Проверьте приложение под контролируемой нагрузкой:
curl --fail --silent --show-error http://127.0.0.1:8080/
docker logs --tail 100 limited-demo
Не запускайте генератор нагрузки на production без отдельного окна и ограничений.
Перенесите ограничения в Compose
В compose.yaml те же ограничения записываются рядом с сервисом. Пример ниже задаёт пределы для web; числа остаются учебными и должны быть заменены результатами измерений вашего приложения:
services:
web:
image: example/web:tested
cpus: 0.50
mem_limit: 256m
memswap_limit: 256m
pids_limit: 128
Проверьте поддержку установленной версией и итоговую модель:
docker compose version
docker compose config
docker compose up -d --force-recreate web
docker compose ps web
После пересоздания снова посмотрите docker inspect и docker stats. Не считайте сам факт успешного старта доказательством правильного лимита: нехватка проявляется во время пика, сборки отчёта или обслуживания очереди.
Разберитесь с памятью и swap
--memory-swap имеет смысл только вместе с --memory. Положительное значение задаёт общий объём RAM плюс swap. Например, memory 300m и memory-swap 1g разрешают до 300m RAM и примерно 700m swap при наличии swap на host.
Если --memory-swap не задан, поведение зависит от конфигурации host и Docker. Не полагайтесь на free внутри контейнера: он может показывать swap host, а не фактический доступ cgroup.
Не отключайте OOM killer без веской причины. Docker отдельно предупреждает, что такое отключение без memory limit способно поставить под угрозу host. Правильнее измерить приложение, ограничить его и настроить наблюдение за приближением к пределу.
Подтвердите причину остановки по состоянию и журналу
После неожиданной остановки соберите три независимых признака: состояние контейнера, последние строки приложения и сообщения ядра за тот же период. Команды ничего не перезапускают и не удаляют:
docker inspect limited-demo --format \
'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}'
docker logs --tail 200 --timestamps limited-demo
journalctl -k --since '-15 min' | grep -i -E 'oom|out of memory|killed process'
Поле OOMKilled=true — сильный признак, но сохраните также журнал и контекст нагрузки. Exit code 137 может соответствовать SIGKILL, однако сам по себе не доказывает OOM: процесс могли остановить другой командой.
Если контейнер регулярно упирается в память, не увеличивайте предел автоматически. Проверьте утечку, размер кеша, параллелизм, число workers и объём одной задачи. После исправления повторите нагрузочный сценарий.
Проверьте задержки при ограничении процессора
При достижении CPU quota процесс не завершается — scheduler ограничивает его время. Симптомами могут стать рост latency, таймауты и неуспешный healthcheck. Снижение --cpus полезно для защиты соседей, но не заменяет достаточную ёмкость VPS.
CPU shares — относительный вес при конкуренции, а не жёсткий потолок. Для понятного первого ограничения удобнее --cpus; тонкую настройку period/quota и real-time scheduler оставьте задачам, где она измерена и действительно необходима.
Как подобрать значение и откатить
Начните с измеренного пика плюс обоснованный запас. Наблюдайте процентили latency, рестарты, OOM, throttling и свободные ресурсы host. Пересматривайте значения после изменения версии приложения, числа workers и нагрузки.
Если новый предел нарушил работу, верните предыдущую Compose-конфигурацию и пересоздайте только сервис:
docker compose config
docker compose up -d --force-recreate web
docker compose ps web
Не снимайте все ограничения навсегда из-за одной ошибки оценки. Сохраните измерения, скорректируйте конкретный предел и повторите тест.
Механизм контейнерной изоляции объяснён в обзорной статье о Docker. Состояние приложения проверяется через healthcheck, а последствия нехватки памяти на host разобраны в руководстве по swap и OOM.
Источники
Самопроверка
Проверьте, что материал усвоен
Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.
Разбираем коротко
Частые вопросы
Есть ли у контейнера лимит памяти по умолчанию?
Нет. Без явного ограничения контейнер может использовать столько памяти, сколько разрешат host и ядро. Поэтому предел выбирают после измерения рабочей и пиковой нагрузки.
Означает ли --cpus=0.5 резервирование половины ядра?
Нет. Это верхняя квота процессорного времени, а не гарантированный резерв. Фактическая производительность также зависит от нагрузки host и других контейнеров.
Как понять, что контейнер завершил OOM killer?
Проверьте поле .State.OOMKilled через docker inspect, exit code, журнал приложения и сообщения ядра. Один exit code без контекста недостаточен для вывода.


