Поведение среды исполнения (Runtime Behavior)
TL;DR: Контейнер = процесс с PID 1.
docker stop→ SIGTERM → 10 сек → SIGKILL. Exit code 137 = OOM Kill. Без ротации логов Docker заполнит весь диск.
Запуск контейнера — это лишь начало. Для обеспечения стабильной работы приложений в продакшене необходимо понимать, как Docker управляет жизненным циклом процесса, как обрабатывает сигналы остановки, куда направляет логи и что делает при сбоях.
1. Жизненный цикл процесса и Сигналы
В мире Linux управление процессами осуществляется через сигналы (Signals). Docker транслирует команды CLI в системные сигналы для процесса с PID 1 внутри контейнера.
Graceful Shutdown (Изящное завершение)
Когда вы выполняете docker stop, происходит следующая последовательность:
- SIGTERM (Signal 15): Docker отправляет этот сигнал главному процессу. Это вежливая просьба: “Пожалуйста, заверши работу”.
- Ожидание приложения: Приложение должно перехватить этот сигнал, перестать принимать новые запросы, дописать данные на диск и закрыть соединения.
- Timeout: По умолчанию Docker ждет 10 секунд.
- SIGKILL (Signal 9): Если процесс не завершился за отведенное время, ядро убивает его принудительно. Это “выдергивание шнура из розетки” — возможна потеря данных или повреждение файлов.
Проблема PID 1: Если ваше приложение запускается через shell-скрипт (
CMD ["/bin/sh", "-c", "npm start"]), то PID 1 получаетsh. Shell обычно не пересылает сигналы дочерним процессам. В итогеnpm startне получает SIGTERM, и Docker всегда убивает его через SIGKILL после 10 секунд ожидания. Решение: Используйтеexecв entrypoint-скриптах или запускайте приложение напрямую (CMD ["npm", "start"]).
2. Коды выхода (Exit Codes)
Когда контейнер останавливается, он возвращает код выхода. Понимание этих кодов критично для отладки.
- 0: Успешное завершение (Intentional stop).
- 1: Ошибка приложения (Application error).
- 137 (128 + 9): Процесс убит сигналом
SIGKILL. Чаще всего это OOM Killer (нехватка памяти) или результатdocker kill. - 139 (128 + 11): Segmentation Fault (ошибка доступа к памяти). Обычно указывает на баг в низкоуровневом коде (C/C++ библиотеки).
- 143 (128 + 15): Процесс корректно завершился после получения
SIGTERM.
3. Политики перезапуска (Restart Policies)
Docker имеет встроенный механизм “самоисцеления” (self-healing) для одиночных контейнеров.
no: Не перезапускать никогда (по умолчанию).on-failure: Перезапускать, только если exit code != 0. Полезно для скриптов, которые должны упасть при ошибке, но остановиться при успехе.always: Всегда перезапускать (даже если вы остановили его вручную, он встанет после рестарта демона).unless-stopped: То же, чтоalways, но если вы явно сказалиdocker stop, контейнер не запустится сам после перезагрузки демона.
4. Логирование (Logging Drivers)
По умолчанию Docker перехватывает потоки STDOUT и STDERR контейнера и пишет их в файлы на хосте (JSON-File driver).
Проблема ротации логов
Стандартная настройка Docker не ограничивает размер логов. Если ваше приложение пишет много логов, файл /var/lib/docker/containers/.../*.log может забить всё дисковое пространство хоста.
Решение:
- Настройка ротации в
daemon.json:"log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } - Использование других драйверов:
syslog,journald,gelf(для Graylog),awslogs,fluentd. При использовании этих драйверов командаdocker logsможет перестать работать, так как логи уходят сразу во внешнюю систему.
5. Управление ресурсами (OOM Killer)
Что происходит, когда контейнеру не хватает памяти?
- Если лимиты не заданы: Контейнер может съесть всю RAM хоста, что приведет к зависанию всей системы или срабатыванию системного OOM Killer, который может убить даже
dockerdилиsshd. - Если лимит задан (
--memory="512m"):- При попытке выделить память сверх лимита, ядро Linux убивает процесс внутри контейнера.
- Контейнер падает с кодом 137.
- В
docker inspectполеOOMKilledбудет иметь значениеtrue.
Best Practice: Всегда устанавливайте лимиты памяти (Memory Limit) для контейнеров в продакшене. Java и Node.js приложениям также нужно явно указывать их внутренние лимиты Heap Size, чтобы они соответствовали лимитам контейнера.
Подводные камни
| Заблуждение | Реальность |
|---|---|
| «docker stop мгновенно останавливает» | Сначала SIGTERM, потом 10 сек ожидания, потом SIGKILL. Shell как PID 1 не пробрасывает сигналы |
| «Логи Docker безопасны» | Без ротации (max-size/max-file) логи заполнят диск. Нет лимита по умолчанию |
| «always restart — хорошая идея» | Контейнер перезапустится даже после docker stop + рестарт демона. Используй unless-stopped |
| «Контейнер упал — что-то сломалось» | Exit code 0 = нормальное завершение, 137 = OOM или kill, 143 = graceful shutdown |