← Назад к вопросам

Как достигается первый процесс в Docker?

1.8 Middle🔥 201 комментариев
#DevOps и инфраструктура

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как достигается первый процесс (PID 1) в Docker

Первый процесс в контейнере Docker имеет специальное значение — он отвечает за управление всеми остальными процессами. Рассмотрим, как он создаётся и почему это важно.

Что такое PID 1

В Linux каждому процессу присвоен PID (Process ID). Первый запущенный процесс при загрузке системы имеет PID = 1. В Docker контейнере также есть свой первый процесс, изолированный от хоста.

Особенности PID 1:

  • Отвечает за переиспользование дочерних процессов (reaping orphaned processes)
  • Получает сигналы SIGTERM, SIGKILL
  • Не может быть "убит" обычными сигналами
  • Если умрёт, контейнер останавливается

Определение первого процесса в Dockerfile

Первый процесс определяется инструкцией ENTRYPOINT или CMD:

Вариант 1: ENTRYPOINT (рекомендуется)

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

# Первый процесс будет запущен напрямую
ENTRYPOINT ["python", "app.py"]

При запуске: docker run myapp → первый процесс это /usr/local/bin/python app.py

Вариант 2: CMD (менее рекомендуется)

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

# Запуск через /bin/sh
CMD ["python", "app.py"]

Проблема: CMD запускается через /bin/sh -c, что создаёт дополнительный shell процесс.

Вариант 3: Shell Form (проблематичен)

# ❌ Избегайте!
ENTRYPOINT python app.py
CMD python app.py

Это создаёт shell процесс, а не прямой запуск.

Проблема зомби-процессов

Если первый процесс неправильно настроен, может произойти утечка дочерних процессов:

# ❌ Проблема: дочерние процессы становятся зомби
import subprocess
import time

def main():
    # Запускаем дочерний процесс, но не ждём его
    subprocess.Popen(["sleep", "1000"])
    
    # Основной процесс продолжает работать
    while True:
        time.sleep(1)

if __name__ == "__main__":
    main()

Эти процессы останутся в памяти даже после завершения.

Решение 1: Ждать дочерних процессов

# ✅ Правильно: ждём дочерних процессов
import subprocess
import time

def main():
    processes = []
    
    for i in range(3):
        p = subprocess.Popen(["sleep", "10"])
        processes.append(p)
    
    # Ждём завершения всех
    for p in processes:
        p.wait()
    
    print("Все процессы завершены")

if __name__ == "__main__":
    main()

Решение 2: Использование init системы

Для контейнеров со сложной логикой используется init система (tini):

FROM python:3.11-slim

# Установка tini
RUN apt-get update && apt-get install -y tini

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

# tini как первый процесс
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["python", "app.py"]

У Docker есть встроенная поддержка:

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

# Встроенная поддержка tini
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["python", "app.py"]

Правильная обработка сигналов

Первый процесс должен корректно обрабатывать сигналы graceful shutdown:

import signal
import sys
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class Application:
    def __init__(self):
        self.running = True
        
        # Регистрируем обработчики сигналов
        signal.signal(signal.SIGTERM, self.handle_shutdown)
        signal.signal(signal.SIGINT, self.handle_shutdown)
    
    def handle_shutdown(self, signum, frame):
        logger.info(f"Received signal {signum}, shutting down...")
        self.running = False
    
    def run(self):
        logger.info("Application started")
        
        try:
            while self.running:
                # Основная логика приложения
                self.process()
        except KeyboardInterrupt:
            logger.info("Interrupted")
        finally:
            self.cleanup()
            logger.info("Application stopped")
    
    def process(self):
        # Ваша бизнес-логика
        import time
        time.sleep(1)
    
    def cleanup(self):
        logger.info("Cleaning up resources")
        # Закрываем соединения, сохраняем состояние

if __name__ == "__main__":
    app = Application()
    app.run()
    sys.exit(0)

FastAPI/uvicorn пример

Для web приложений:

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

# Uvicorn как первый процесс
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Проверка в контейнере

# Какой процесс имеет PID 1?
docker exec container_id ps aux

# Например, вывод:
# UID   PID  PPID  C STIME TTY TIME CMD
# root    1     0  0 12:34 ?   00:00:05 python app.py
# root   42     1  0 12:35 ?   00:00:01 some_child_process

Лучшие практики

  • Используйте ENTRYPOINT в exec форме (с массивом): ["python", "app.py"]
  • Не оборачивайте в shell если не нужен: избегайте python app.py без массива
  • Обрабатывайте SIGTERM для graceful shutdown
  • Используйте tini/dumb-init если запускаете много процессов
  • Логируйте старт/стоп приложения
  • Проверяйте healthcheck в Docker Compose:
services:
  app:
    build: .
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 3

Первый процесс в Docker — это ключ к стабильности и корректному управлению контейнером.

Как достигается первый процесс в Docker? | PrepBro