Введение
Корректный запуск процессов внутри контейнера — одна из ключевых тем при разработке Docker-образов. Формально всё описано в документации Docker, однако на практике регулярно возникают неоднозначные ситуации:
контейнер не останавливается корректно;
сигналы не доходят до приложения;
появляются zombie-процессы;
PID 1 ведёт себя неожиданно.
В этой статье разберём:
Разницу между
ENTRYPOINTиCMD.Отличие exec и shell форм.
Почему критически важно, какой процесс имеет PID 1.
Как правильно писать
docker-entrypoint.sh.Когда и зачем использовать
tini.
Материал ориентирован на практическое применение и реальные сценарии.
1. ENTRYPOINT и CMD: фундаментальная разница
В Dockerfile существуют две директивы для запуска процессов:
ENTRYPOINTCMD
Обе участвуют в формировании итоговой команды запуска контейнера, но выполняют разные роли.
Логическая модель
Можно представить их так:
ENTRYPOINT + CMD = финальная команда контейнераРекомендуемая практика
ENTRYPOINT— фиксированная команда (исполняемый файл или скрипт).CMD— аргументы по умолчанию, которые можно переопределить.
Exec-форма и Shell-форма
Docker поддерживает два синтаксиса.
1️⃣ Exec-форма (рекомендуется)
ENTRYPOINT ["/bin/ping"]
CMD ["it-lux.ru"]Особенности:
Не используется shell.
Нет подстановки переменных.
Процесс запускается напрямую.
Корректная обработка сигналов.
После сборки:
docker run pingВнутри контейнера выполнится:
/bin/ping it-lux.ruПереопределение аргументов:
docker run ping google.comТеперь выполнится:
/bin/ping google.comЭто правильная архитектура: один образ — разные параметры запуска.
2️⃣ Shell-форма (менее предпочтительна)
ENTRYPOINT ping it-lux.ruФактически Docker запустит:
/bin/sh -c "ping it-lux.ru"Минусы:
Появляется промежуточный shell.
Сигналы могут не дойти до целевого процесса.
PID 1 становится shell.
Shell-форма допустима, но требует понимания последствий.
2. Проблема PID 1
В Linux процесс с PID 1 — особый.
Особенности:
Он не имеет обработчиков сигналов по умолчанию.
Он ответственен за "усыновление" осиротевших процессов.
Он должен корректно обрабатывать SIGTERM.
Docker при остановке контейнера выполняет:
docker stop → отправляет SIGTERM → PID 1Если PID 1:
не обрабатывает сигнал,
не передаёт его дочерним процессам,
то контейнер завершится некорректно (force kill через SIGKILL спустя timeout).
3. Ошибка с docker-entrypoint.sh
Типичный пример:
FROM centos:7
COPY docker-entrypoint.sh /usr/bin
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]Содержимое:
#!/bin/bash
ping ya.ruЧто происходит?
PID 1 — это:
/bin/bash /usr/bin/docker-entrypoint.shА ping — дочерний процесс.
При docker stop:
SIGTERM получает bash
bash может не передать сигнал дальше
ping зависает
появляются zombie-процессы
Это некорректная архитектура контейнера.
4. Правильное решение — exec
В bash существует встроенная команда exec.
Она:
заменяет текущий процесс
передаёт ему PID
не создаёт дополнительный уровень
Правильный вариант:
#!/bin/bash
exec ping ya.ruТеперь:
PID 1 → pingКонтейнер завершится корректно.
5. Использование CMD внутри entrypoint
Более гибкий вариант:
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
CMD ["ya.ru"]Скрипт:
#!/bin/bash
# подготовительные действия
set -- ping "$@"
exec "$@"Разбор:
$@— все аргументы контейнера.set --— формирует новую команду.exec "$@"— запускает её как PID 1.
Запуск:
docker run ping google.comРезультат:
PID 1 → ping google.comЭто production-подход.
6. Когда одного exec недостаточно
Теперь усложним сценарий.
Допустим, запускается:
Jenkins
Apache
Zabbix server
Такие системы активно создают дочерние процессы.
Примеры:
Jenkins
Zabbix
Apache HTTP Server
Если дочерние процессы:
завершаются некорректно,
остаются "осиротевшими",
то PID 1 должен их "подчищать".
Но большинство приложений:
не реализуют init-поведение,
не умеют корректно reaping zombie-процессов.
7. Решение — tini
Здесь используется tini.
Минималистичный init для контейнеров.
Корректно проксирует сигналы.
Убирает zombie-процессы.
Работает как PID 1.
Название — это "init" наоборот.
Как подключить tini
Пример Dockerfile:
FROM debian:stable
RUN apt-get update && apt-get install -y tini
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["your-app"]Теперь:
PID 1 → tini
PID 7 → your-appЧто делает tini:
Получает SIGTERM.
Передаёт сигнал дочернему процессу.
Reap'ит zombie-процессы.
Корректно завершает контейнер.
Это production best practice.
8. Почему bash ≠ tini
Bash как PID 1 | tini как PID 1 |
|---|---|
Не проксирует сигналы корректно | Проксирует |
Не предназначен как init | Предназначен |
Может терять SIGTERM | Корректно передаёт |
Не чистит zombie | Чистит |
Это принципиально разные роли.
9. Итоговые рекомендации (Best Practices)
Используйте exec-форму всегда, когда возможно.
В
docker-entrypoint.shобязательно применяйтеexec.Разделяйте:
ENTRYPOINT — исполняемый файл
CMD — аргументы по умолчанию
Если приложение создаёт дочерние процессы — используйте
tini.Проверяйте, кто имеет PID 1:
docker exec -it container ps auxЗаключение
На простых примерах всё работает и без этих нюансов. Однако при усложнении логики контейнера:
появляются проблемы с остановкой,
теряются сигналы,
возникают zombie-процессы,
контейнер завершает работу некорректно.
Docker упрощает деплой, но не отменяет фундаментальные принципы работы процессов в Linux.
Понимание:
роли PID 1,
различий exec и shell,
корректного построения entrypoint,
необходимости tini
позволяет создавать production-ready Docker-образы, которые ведут себя предсказуемо и корректно в любой среде.
Create an account or sign in to leave a review
There are no reviews to display.