Что означает ошибка OOMKilled
Ошибка OOMKilled (Out Of Memory Killed) возникает, когда ядро Linux принудительно завершает процесс в контейнере Docker из-за нехватки оперативной памяти (RAM). Это срабатывание механизма OOM killer (Out-Of-Memory killer).
Контейнер завершается с кодом выхода 137 (128 + 9, где 9 — сигнал SIGKILL). В логах Docker вы увидите:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 my-app:latest "python app.py" 2 hours ago Exited (137) 5 minutes ago my-app
$ docker logs my-app
... (логи приложения) ...
Killed
Ошибка характерна для:
- Контейнеров с высоким потреблением памяти (базы данных, обработка данных, Java-приложения).
- Систем с ограниченной RAM (например, облачные инстансы малого размера).
- Сценариев, где несколько контейнеров конкурируют за память.
Причины возникновения
- Недостаточный объём RAM на хосте. Суммарное потребление памяти всеми контейнерами и системами превышает доступную физическую память.
- Отсутствие лимитов памяти у контейнера. Если не задать
--memory, контейнер может использовать всю свободную RAM хоста, что приведёт к OOM. - Утечки памяти в приложении. Программа внутри контейнера постепенно потребляет всё больше памяти (например, из-за неосвобождения ресурсов).
- Неправильно настроенный swap. На хосте может быть недостаточно или отсутствовать swap-пространство, что ускоряет исчерпание RAM.
- Агрессивные настройки OOM killer. Ядро может убивать контейнеры с высоким
oom_score(по умолчанию у контейнеров он выше, чем у системных процессов). - Запуск контейнера без ограничений на memory-swap. Если задан только
--memory, но не--memory-swap, контейнер может использовать swap, что иногда маскирует проблему, но приводит к деградации производительности.
Способ 1: Настройка лимитов памяти при запуске контейнера
Самый прямой способ — явно задать лимиты памяти для контейнера. Это предотвратит исчерпание памяти хоста и гарантирует, что контейнер не будет убит, пока не достигнет своего лимита.
Для docker run:
docker run -d \
--name my-app \
--memory=512m \ # жёсткий лимит RAM
--memory-swap=1g \ # общий лимит (RAM + swap). Если не задать, то по умолчанию равно --memory.
--memory-reservation=256m \ # мягкий лимит, который Docker пытается соблюдать
my-image:latest
Для docker-compose.yml:
version: '3.8'
services:
app:
image: my-image:latest
deploy:
resources:
limits:
memory: 512M
memory-swap: 1G
reservations:
memory: 256M
💡 Совет: Начинайте с лимита, немного превышающего нормальное потребление приложения (можно узнать через
docker stats). Не устанавливайте лимит равным всей RAM хоста — оставьте память для системы и других процессов.
Способ 2: Оптимизация приложения в контейнере
Если лимиты уже настроены, но контейнер всё равно получает OOM, нужно уменьшать потребление памяти самим приложением.
Для Java-приложений:
Настройте параметры JVM в Dockerfile или команде запуска:
ENV JAVA_OPTS="-Xmx256m -Xms128m"
CMD java $JAVA_OPTS -jar app.jar
Или в docker-compose.yml:
environment:
- JAVA_OPTS=-Xmx256m
Для Python/Node.js:
- Используйте стриминговую обработку больших файлов вместо загрузки в память.
- Уменьшите размер кэшей (например, в Django
CACHES['default']['OPTIONS']['MAX_ENTRIES']). - Обновите библиотеки — иногда утечки памяти исправляются в новых версиях.
Для веб-серверов (Nginx/Apache):
- Уменьшите
worker_processesиworker_connections. - Настройте буферизацию.
Способ 3: Настройка OOM score adjustment
Вы можете влиять на приоритет контейнера при выборе жертвы OOM killer. Параметр --oom-score-adj (от -1000 до 1000) задаёт "вес" контейнера. Чем ниже значение, тем меньше шансов, что контейнер будет убит.
docker run -d \
--name critical-app \
--oom-score-adj=-500 \
my-critical-image
Как выбрать значение:
-1000— максимальная защита (контейнер будет убит в последнюю очередь, но не гарантировано).0— значение по умолчанию.1000— максимальный приоритет на убийство (не рекомендуется).
⚠️ Важно: Это не отменяет OOM killer, а лишь меняет порядок. Если память закончится, какой-то процесс всё равно будет убит.
Способ 4: Увеличение памяти хоста или настройка swap
Если проблема в нехватке ресурсов на уровне хоста:
- Увеличьте RAM на виртуальной машине/сервере (например, в AWS измените тип инстанса).
- Добавьте swap-пространство, если его нет или оно мало:
# Проверьте текущий swap
swapon --show
# Создайте swap-файл 2 ГБ
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Чтобы swap включался автоматически, добавьте в /etc/fstab:
# /swapfile none swap sw 0 0
⚠️ Внимание: Swap на SSD может ускорить износ диска. Используйте только если нет возможности добавить RAM.
Способ 5: Мониторинг и автоматическое реагирование
Настройте мониторинг памяти и автоматические действия:
- Используйте
docker eventsдля отслеживания событий OOM:docker events --filter 'event=die' --filter 'status=OOMKilled' - Интегрируйте с системами мониторинга (Prometheus + cAdvisor, Datadog). Настройте алерты при достижении 80-90% памяти.
- Используйте оркестраторы (Kubernetes, Docker Swarm), которые могут автоматически перезапускать контейнеры и масштабироваться при нехватке ресурсов.
Профилактика
- Всегда задавайте лимиты памяти для продакшн-контейнеров. Используйте
--memoryи--memory-swap. - Регулярно анализируйте потребление памяти через
docker statsили мониторинг. - Тестируйте приложение под нагрузкой с ограниченной памятью (например, через
stress-ngвнутри контейнера). - Настройте health checks в Docker Compose/Kubernetes, чтобы быстро обнаруживать падение из-за OOM.
- Избегайте запуска нескольких memory-intensive контейнеров на одном хосте без должного контроля.
- Обновляйте ядро и Docker — новые версии улучшают управление памятью и OOM killer.
FAQ
Можно ли полностью отключить OOM killer для контейнера?
Нет, OOM killer — это механизм ядра Linux. Вы можете только уменьшить oom_score контейнера или увеличить лимиты памяти, чтобы избежать срабатывания.
Почему контейнер с лимитом памяти всё равно получает OOMKilled?
Если лимит задан, контейнер должен быть остановлен Docker до достижения лимита (с кодом 137). Но если лимит не задан, контейнер может использовать всю RAM хоста, и тогда OOM killer ядра его убьёт. Проверьте, что лимит установлен корректно.
Как диагностировать, какое приложение в контейнере потребляет много памяти?
Войдите в контейнер (docker exec -it <container> bash) и используйте утилиты: top, htop, ps aux --sort=-%mem. Для Java-приложений используйте jcmd <pid> VM.native_memory summary.
Что делать, если приложение не может работать в заданных лимитах памяти?
Оптимизируйте код, увеличьте лимит (если есть запас на хосте) или пересмотрите архитектуру: разделите монолит на микросервисы, вынесите тяжёлые операции в отдельные контейнеры с большими лимитами.
Правильно ли задавать --memory-swap в 2x от --memory?
Не всегда. Если приложение не использует swap (например, базы данных), то задание --memory-swap может привести к неожиданному использованию swap и падению производительности. Для таких приложений лучше отключить swap (--memory-swap=-1) или оставить равным --memory.