Управление ресурсами контейнеров

TL;DR: --memory=512m — жёсткий лимит RAM. --cpus=1.0 — лимит CPU. Без лимитов один контейнер может убить весь хост через OOM.

По умолчанию контейнер не имеет ограничений и может использовать все доступные ресурсы хоста (все ядра CPU, всю оперативную память). Это опасно: один сбойный процесс может вызвать отказ в обслуживании (DoS) для всего сервера.

В этом руководстве мы настроим жесткие (Hard) и мягкие (Soft) лимиты.

1. Лимиты оперативной памяти (Memory)

Управление памятью критично. Если память на хосте закончится, ядро Linux (OOM Killer) начнет убивать процессы (часто — сам Docker Daemon или базу данных).

Hard Limit (--memory)

Жесткое ограничение. Если контейнер попытается взять больше — он будет убит (Exit Code 137).

# Ограничить потребление до 512 МБ
docker run -d --name my-app \
  --memory="512m" \
  nginx

Soft Limit (--memory-reservation)

Мягкое ограничение. Docker гарантирует контейнеру этот объем, но разрешает взять больше, если на хосте есть свободная память.

# Гарантировать 256МБ, но разрешить расти до 512МБ
docker run -d \
  --memory="512m" \
  --memory-reservation="256m" \
  nginx

Если на хосте кончится память, Docker в первую очередь будет “ужимать” контейнеры, потребляющие больше своей резервации.

Swap (--memory-swap)

По умолчанию Docker разрешает использовать Swap в объеме 2 * memory. Чтобы отключить swap для контейнера (для предсказуемой производительности):

# Память 512МБ, Swap 512МБ (то есть swap = memory, доп. свопа нет)
docker run -d --memory="512m" --memory-swap="512m" nginx

2. Лимиты процессора (CPU)

CPU Count (--cpus)

Самый простой способ. “Дать контейнеру мощность 1.5 ядер”.

# Ограничить использование до 1.5 ядер (например, 1 ядро на 100% + 1 на 50%)
docker run -d --cpus="1.5" nginx

CPU Pinning (--cpuset-cpus)

Привязка контейнера к конкретным физическим ядрам. Полезно для баз данных и High-Load систем, чтобы избежать переключений контекста (Context Switching).

# Использовать только 0-е и 1-е ядро
docker run -d --cpuset-cpus="0,1" nginx

CPU Shares (--cpu-shares)

Относительный вес. Работает только когда CPU загружен на 100%. По умолчанию у всех 1024.

  • Контейнер А (1024) и Контейнер Б (512).
  • Если CPU свободен — оба могут жарить на 100%.
  • Если CPU забит — А получит 66% времени, Б — 33%.

3. Обновление ресурсов на лету

Вам не нужно пересоздавать контейнер, чтобы изменить лимиты. Используйте docker update.

# Увеличить память до 1ГБ и CPU до 2 ядер для работающего контейнера
docker update --memory "1g" --cpus "2.0" my-app

Примечание: Работает только если cgroups v2 корректно настроены на хосте (стандарт для современных Linux).

4. Java и Garbage Collection

JVM (Java) и Node.js (V8) должны знать о лимитах контейнера, чтобы правильно настроить Heap Size и сборщик мусора.

  • В старых версиях Java (8u121 и ниже) JVM видела всю память хоста (64ГБ) и падала с OOM, пытаясь выделить 4ГБ в контейнере с лимитом 512МБ.
  • В современных версиях (-XX:+UseContainerSupport включен по умолчанию) это работает автомтически.

Best Practice для Java: Всегда задавайте -XX:MaxRAMPercentage=75.0. Это заставит JVM использовать 75% от лимита контейнера (--memory) под Heap, оставляя 25% на native memory и стек потоков.

# compose.yaml
services:
  java-app:
    image: openjdk:17
    deploy:
      resources:
        limits:
          memory: 1G
    environment:
      - JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75.0"

5. Мониторинг

Быстрый способ проверить нагрузку в консоли:

docker stats
  • Показывает CPU %, Mem Usage / Limit, Net I/O.
  • --no-stream — вывести снимок один раз.
  • --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" — отформатировать вывод.

Типичные ошибки

ОшибкаСимптомРешение
Нет --memory на продеОдин контейнер съел всю RAM → OOM Killer убил sshdВсегда ставить --memory и --cpus
Java -Xmx больше --memoryКонтейнер убит с exit code 137 (OOM)MaxRAMPercentage=75 автоматически подстраивается под cgroup
--memory-swap не заданКонтейнер использует swap, «тормозит» вместо падения--memory-swap=512m (равно —memory) отключает swap
--cpu-shares вместо --cpusЛимит не работает когда CPU свободен--cpu-shares — относительный вес, не жёсткий лимит. Используй --cpus