Без лимитов один контейнер способен исчерпать всю память хоста или загрузить все ядра процессора, что приведёт к деградации остальных сервисов или к массовому OOM-kill. Docker предоставляет встроенные механизмы ограничения ресурсов на уровне cgroup, которые задаются при запуске контейнера.
Флаг
Значение принимает суффиксы
Здесь контейнер может использовать до 1 ГБ, но при давлении на память хоста ядро начнёт освобождать страницы этого контейнера раньше, чем контейнеров с более высоким reservation.
Флаг
Если
Флаг
Рекомендуется только в связке с
Флаг
Механизм работает через CFS bandwidth control в cgroup: контейнер получает квоту процессорного времени в рамках каждого периода планирования (по умолчанию 100 мс). При
Если нужно зафиксировать контейнер на определённых ядрах (например, для NUMA-оптимизации или изоляции от других нагрузок):
В отличие от
В
В Swarm-режиме
Команда
Ограничения:
Возвращает
Типичная строка:
Показывает текущее потребление CPU, памяти, сети и дискового I/O. Колонка
Для детальной диагностики можно читать файлы cgroup напрямую. Путь зависит от версии cgroup (v1 или v2) и идентификатора контейнера:
Java-приложения исторически не учитывали cgroup-лимиты. Начиная с JDK 8u191+ и JDK 10+ флаг
Для Go начиная с версии 1.19 доступна переменная окружения
Рекомендация: оставляйте запас 10–20% между
Помимо CPU и памяти, Docker позволяет ограничить скорость чтения/записи на блочное устройство и количество процессов:
После применения лимитов убедитесь, что они действительно активны:
Альтернативно — через
Ограничения ресурсов решают проблему изоляции, но не заменяют мониторинг и алертинг. Если контейнер регулярно упирается в потолок памяти, это сигнал к масштабированию или оптимизации, а не к бесконечному увеличению лимита. Комбинируйте лимиты с healthcheck'ами Healthcheck в Docker Compose: как отличить запущенный контейнер от реально работающего сервиса и настройте алерты на
Ограничение памяти через docker run
Флаг
--memory (сокращённо -m) задаёт жёсткий потолок оперативной памяти, доступной контейнеру. Если процесс внутри превышает лимит, ядро убивает его через OOM-killer.
Bash:
docker run -d --name app --memory=512m nginx:alpine
Значение принимает суффиксы
b, k, m, g. Слишком малый лимит (единицы мегабайт) приведёт к тому, что контейнер не сможет запуститься — процессу не хватит памяти даже на инициализацию.Мягкий лимит: --memory-reservation
--memory-reservation задаёт мягкий порог. Ядро старается удерживать потребление ниже этого значения, но не убивает процесс при превышении. Полезен для балансировки нагрузки в условиях нехватки памяти на хосте.
Bash:
docker run -d --memory=1g --memory-reservation=768m myapp
Здесь контейнер может использовать до 1 ГБ, но при давлении на память хоста ядро начнёт освобождать страницы этого контейнера раньше, чем контейнеров с более высоким reservation.
Ограничение swap: --memory-swap
Флаг
--memory-swap задаёт общий лимит RAM + swap. Поведение зависит от соотношения с --memory:Значение --memory-swap | Поведение |
|---|---|
| Не задан | Общий лимит RAM + swap равен 2 × --memory, то есть swap доступен в объёме --memory |
-1 | Swap не ограничен |
0 | Трактуется как «не задан» — применяется поведение по умолчанию |
Равен --memory | Swap полностью отключён: контейнер может использовать только RAM |
Больше --memory | Swap доступен в объёме --memory-swap минус --memory |
Bash:
## Запретить swap полностью: memory-swap = memory
docker run -d --memory=256m --memory-swap=256m myapp
## Разрешить 256 МБ swap поверх 512 МБ RAM
docker run -d --memory=512m --memory-swap=768m myapp
Если
--memory-swap меньше --memory, Docker выдаст ошибку при запуске.Отключение OOM-kill: --oom-kill-disable
Флаг
--oom-kill-disable предотвращает убийство процесса ядром при превышении лимита. Вместо этого процесс будет заблокирован до освобождения памяти. Использовать с осторожностью: контейнер может зависнуть навсегда.
Bash:
docker run -d --memory=512m --oom-kill-disable myapp
Рекомендуется только в связке с
--memory, иначе процесс может исчерпать всю память хоста без каких-либо последствий для себя.Ограничение CPU
Доля процессорного времени: --cpus
Флаг
--cpus задаёт количество ядер (или долю ядра), доступных контейнеру. Значение дробное.
Bash:
## Ограничить контейнер половиной одного ядра
docker run -d --cpus=0.5 myapp
## Ограничить двумя ядрами
docker run -d --cpus=2 myapp
Механизм работает через CFS bandwidth control в cgroup: контейнер получает квоту процессорного времени в рамках каждого периода планирования (по умолчанию 100 мс). При
--cpus=0.5 квота составляет 50 мс из каждых 100 мс.Привязка к конкретным ядрам: --cpuset-cpus
Если нужно зафиксировать контейнер на определённых ядрах (например, для NUMA-оптимизации или изоляции от других нагрузок):
Bash:
## Только ядра 0 и 1
docker run -d --cpuset-cpus="0,1" myapp
## Ядра с 0 по 3
docker run -d --cpuset-cpus="0-3" myapp
Относительный вес: --cpu-shares
--cpu-shares задаёт относительный приоритет при конкуренции за CPU. Значение по умолчанию — 1024. Если два контейнера конкурируют за одно ядро и у одного --cpu-shares=512, а у другого --cpu-shares=1024, второй получит примерно вдвое больше времени. При отсутствии конкуренции лимит не применяется.
Bash:
docker run -d --cpu-shares=512 low-priority-app
В отличие от
--cpus, этот флаг не задаёт абсолютный потолок — только пропорцию при конкуренции.Лимиты в Docker Compose
В
docker-compose.yml ограничения задаются по-разному в зависимости от режима запуска.Standalone-режим (docker compose up без Swarm)
YAML:
services:
web:
image: nginx:alpine
mem_limit: 512m
mem_reservation: 384m
cpus: 1.5
memswap_limit: 512m
oom_kill_disable: false
Swarm-режим (deploy)
YAML:
services:
web:
image: nginx:alpine
deploy:
resources:
limits:
cpus: '1.5'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
В Swarm-режиме
reservations используются для планирования: менеджер не разместит задачу на ноде, где свободных ресурсов меньше, чем указано в reservation.Обновление лимитов без пересоздания контейнера
Команда
docker update позволяет изменить лимиты работающего контейнера без его остановки:
Bash:
docker update --memory=1g --cpus=2 myapp
Ограничения:
--memory нельзя безопасно уменьшить ниже текущего потребления без риска немедленного OOM-kill. Для --cpus изменение применяется мгновенно через cgroup.Диагностика OOM и превышения лимитов
Проверка через docker inspect
Bash:
docker inspect --format='{{.State.OOMKilled}}' myapp
Возвращает
true, если контейнер был убит OOM-killer'ом.Логи ядра
Bash:
dmesg | grep -i "oom|killed process"
Типичная строка:
Код:
Memory cgroup out of memory: Killed process 12345 (java) total-vm:1048576kB, anon-rss:524288kB
Статистика в реальном времени
Bash:
docker stats myapp
Показывает текущее потребление CPU, памяти, сети и дискового I/O. Колонка
MEM % отображает долю от лимита (если он задан) или от общей памяти хоста.Файлы cgroup
Для детальной диагностики можно читать файлы cgroup напрямую. Путь зависит от версии cgroup (v1 или v2) и идентификатора контейнера:
Bash:
## cgroup v2 (современные дистрибутивы)
cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/memory.current
cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/memory.max
memory.current — текущее потребление, memory.max — установленный лимит (или max, если лимит не задан).Типичные ошибки и их последствия
| Ошибка | Что происходит | Решение |
|---|---|---|
--memory задан, но приложение использует JVM/Go без ограничения кучи | Процесс не знает о лимите контейнера, аллоцирует больше, получает OOM | Передать -Xmx для JVM или GOMEMLIMIT для Go |
--memory-swap равен --memory при активной работе с диском | Любая потребность в подкачке приводит к мгновенному OOM | Либо разрешить swap, либо увеличить --memory |
--oom-kill-disable без --memory | Процесс может исчерпать память хоста | Всегда комбинировать с жёстким лимитом |
--cpus=0.1 для CPU-интенсивной задачи | Задача выполняется крайне медленно, таймауты | Увеличить лимит или вынести на отдельный хост |
Лимит задан только в Compose, но контейнер запущен через docker run | Лимиты из Compose не применяются | Использовать единый способ запуска |
--memory-swap=0 в ожидании отключения swap | Swap не отключается — значение трактуется как «не задан» | Для отключения swap установить --memory-swap равным --memory |
JVM и Go: настройка под лимит контейнера
Java-приложения исторически не учитывали cgroup-лимиты. Начиная с JDK 8u191+ и JDK 10+ флаг
-XX:+UseContainerSupport (включён по умолчанию) заставляет JVM читать лимит из cgroup и рассчитывать MaxHeapSize автоматически. Для старых версий или точного контроля:
Bash:
docker run -d --memory=1g myapp java -Xmx768m -jar app.jar
Для Go начиная с версии 1.19 доступна переменная окружения
GOMEMLIMIT, которая задаёт мягкий лимит памяти для garbage collector:
Bash:
docker run -d --memory=512m -e GOMEMLIMIT=480MiB myapp
Рекомендация: оставляйте запас 10–20% между
GOMEMLIMIT/-Xmx и --memory на стек, метаданные и аллокации вне кучи.Ограничение дискового I/O и PID
Помимо CPU и памяти, Docker позволяет ограничить скорость чтения/записи на блочное устройство и количество процессов:
Bash:
## Ограничить запись на /dev/sda до 10 МБ/с
docker run -d --device-write-bps /dev/sda:10mb myapp
## Лимит количества процессов внутри контейнера
docker run -d --pids-limit=100 myapp
--pids-limit защищает от fork-бомб. Значение -1 снимает ограничение (поведение по умолчанию).Проверка результата после настройки
После применения лимитов убедитесь, что они действительно активны:
Bash:
docker inspect myapp --format='Memory: {{.HostConfig.Memory}}, CPUs: {{.HostConfig.NanoCpus}}'
NanoCpus возвращает значение в наносекундах: 1500000000 соответствует --cpus=1.5. Если Memory равен 0, лимит не задан.Альтернативно — через
docker stats под нагрузкой: потребление не должно превышать заданный порог.Когда лимиты не спасают
Ограничения ресурсов решают проблему изоляции, но не заменяют мониторинг и алертинг. Если контейнер регулярно упирается в потолок памяти, это сигнал к масштабированию или оптимизации, а не к бесконечному увеличению лимита. Комбинируйте лимиты с healthcheck'ами Healthcheck в Docker Compose: как отличить запущенный контейнер от реально работающего сервиса и настройте алерты на
OOMKilled в системе мониторинга, чтобы ловить деградацию до полного отказа Docker-контейнер постоянно перезапускается: как диагностировать restart loop.
