Процессы и сигналы
Что происходит при остановке контейнера и почему приложение теряет запросы.
Процесс
У каждого процесса есть PID, родитель, владелец и состояние. Смотреть — ps, top, htop:
ps -ef | head
ps -o pid,ppid,stat,cmd -p 1
Колонка STAT важнее, чем кажется:
| Код | Состояние |
|---|---|
R |
выполняется или готов |
S |
спит, ждёт события |
D |
непрерываемый сон — ждёт диск или сеть |
Z |
зомби: завершился, родитель не забрал код возврата |
T |
остановлен |
Процесс в состоянии D не убивается даже SIGKILL. Если процесс «не убивается» — почти всегда он в D и ждёт недоступное хранилище.
Сигналы
Сигнал — способ сказать процессу, что что-то произошло. В работе важны три:
| Сигнал | Номер | Что значит |
|---|---|---|
SIGTERM |
15 | «завершись» — можно перехватить |
SIGKILL |
9 | немедленное убийство ядром, перехватить нельзя |
SIGHUP |
1 | обычно «перечитай конфиг» |
kill 1234 # SIGTERM
kill -9 1234 # SIGKILL
Привычка сразу писать kill -9 — плохая: процесс не успевает закрыть соединения, дописать файлы и снять блокировки.
Как это связано с контейнерами
docker stop и Kubernetes при удалении пода делают одно и то же:
- Посылают
SIGTERMглавному процессу. - Ждут (по умолчанию 10 секунд у Docker, 30 у Kubernetes).
- Если процесс ещё жив —
SIGKILL.
Отсюда правило: приложение должно обрабатывать SIGTERM — перестать принимать новые запросы, дождаться текущих, закрыть соединения с базой. Иначе при каждом деплое часть запросов обрывается.
PID 1 и почему это ловушка
Главный процесс контейнера получает PID 1, а у него особое поведение: ядро не применяет к нему обработчики сигналов по умолчанию. Если процесс не установил свой обработчик SIGTERM явно, сигнал просто игнорируется — и контейнер всегда останавливается через SIGKILL спустя таймаут.
Вторая ловушка — запуск через shell:
CMD npm start # shell-форма: PID 1 это /bin/sh
CMD ["npm", "start"] # exec-форма: PID 1 это сам процесс
В shell-форме сигнал получает sh, который не передаёт его дочернему процессу. Приложение не узнает об остановке вообще. Используйте exec-форму.
Проверить
docker run --rm -d --name t nginx
docker stop t # смотрим, сколько заняло
Быстрая остановка — сигнал обработан. Ровно десять секунд — почти наверняка ждали таймаут и убили принудительно.