Как заставить gunicorn передавать SIGINT в uvicorn при запуске внутри Docker?
У меня есть скрипт, запущенный в Docker (с помощью wsl2) с помощью CMD
, который странно ведет себя в отношении сигналов SIGINT
. Вот этот скрипт:
#!/usr/bin/env bash
python manage.py init_db
exec gunicorn foobar.asgi:application \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--graceful-timeout 5 \
--log-level debug \
-w 4
Проблема в том, что когда я нажимаю Ctrl+C
, gunicorn в итоге вынужден принудительно убить работающих uvicorn. Через 5 секунд я вижу следующие ошибки:
^C[2024-10-29 21:15:35 +0000] [1] [INFO] Handling signal: int
[2024-10-29 21:15:40 +0000] [1] [ERROR] Worker (pid:8) was sent SIGKILL! Perhaps out of memory?
[2024-10-29 21:15:40 +0000] [1] [ERROR] Worker (pid:9) was sent SIGKILL! Perhaps out of memory?
[2024-10-29 21:15:40 +0000] [1] [ERROR] Worker (pid:10) was sent SIGKILL! Perhaps out of memory?
[2024-10-29 21:15:40 +0000] [1] [ERROR] Worker (pid:7) was sent SIGKILL! Perhaps out of memory?
Я нашел три обходных пути, которые могут быть полезны для понимания происходящего.
- Зайдите в контейнер и запустите скрипт изнутри. Теперь
Ctrl+C
, кажется, работает лучше, потому что теперь рабочие uvicorn выходят вовремя, но gunicorn все еще печатает некоторые ошибки:
^C[2024-10-29 21:21:56 +0000] [1] [INFO] Handling signal: int
... worker shutdown cleanup output omitted
[2024-10-29 21:21:56 +0000] [7] [ERROR] Worker (pid:15) was sent SIGINT!
[2024-10-29 21:21:56 +0000] [7] [ERROR] Worker (pid:14) was sent SIGINT!
[2024-10-29 21:21:56 +0000] [7] [ERROR] Worker (pid:13) was sent SIGINT!
[2024-10-29 21:21:56 +0000] [7] [ERROR] Worker (pid:10) was sent SIGINT!
[2024-10-29 21:21:56 +0000] [7] [ERROR] Worker (pid:11) was sent SIGINT!
- Добавьте -it в команду docker run:
docker run -it -p 8000:8000 -v $(pwd):/app foobar:latest
Это приводит к такому же поведению, как и в случае с 1.
- Замените
exec
на ручную переадресацию сигнала:
#!/usr/bin/env bash
python manage.py init_db
# Forward SIGINT signal
trap 'kill -INT $PID' INT
# Start Gunicorn
gunicorn foobar.asgi:application \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--graceful-timeout 5 \
--log-level debug \
-w 4 & \
PID=$!
# Wait for Gunicorn process
wait $PID
Теперь, нажав Ctrl+C
, я вижу только следующее, но я замечаю, что рабочие, похоже, больше не выполняют свой код выключения:
^C[2024-10-29 21:26:37 +0000] [7] [INFO] Handling signal: int
Что здесь происходит, и каков хороший способ убедиться, что uvicorn workers правильно отключается на Ctrl+C
?
EDIT: Вот мой Dockerfile
, поскольку один из комментаторов спросил меня об этом. Обратите внимание, что я получаю такое же поведение Ctrl+C
, если запускаю gunicorn непосредственно из Dockerfile
, а не запускаю start-django
bash-скрипт:
CMD ["gunicorn", "foobar.asgi:application", \
"--worker-class", "uvicorn.workers.UvicornWorker", \
"--bind", "0.0.0.0:8000", \
"--graceful-timeout", "5", \
"--log-level", "debug", \
"-w", "4"]
Dockerfile:
# >>> Build stage <<<
FROM python:3.11-slim AS build
WORKDIR /app
# Install C toolchain and C build-time dependencies.
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive \
apt-get install --no-install-recommends --assume-yes \
# gcc, make, etc.
build-essential \
# psycopg2 client libs and header files for building psycopg2
libpq-dev && \
rm -rf /var/lib/apt/lists/*
# Create virtual environment and add it to PATH.
RUN python -m venv /venv
ENV PATH="/venv/bin:$PATH"
# Copy requirements and install.
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip install --no-cache-dir --no-warn-script-location -r requirements.txt
# >>> Run stage <<<
FROM python:3.11-slim
WORKDIR /app
# Install C runtime dependencies.
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive \
apt-get install --no-install-recommends --assume-yes \
# psycopg2 runtime lib
libpq5 && \
rm -rf /var/lib/apt/lists/*
# Create and switch to appuser.
RUN groupadd -r appuser && \
useradd --no-log-init -r -g appuser appuser && \
chown -R appuser:appuser /app
USER appuser
# Copy virtual environment from the build stage and add it to PATH.
COPY --chown=appuser:appuser --from=build /venv /venv
ENV PATH=/venv/bin:$PATH
# Copy the rest of the application code.
COPY --chown=appuser:appuser . .
# Set Python environment variables.
# Prevent Python from writing .pyc files
ENV PYTHONDONTWRITEBYTECODE=1
# Ensure output is sent to stdout/stderr immediately
ENV PYTHONUNBUFFERED=1
# Start server.
CMD ["/app/deployment/start-django"]
EDIT2: Я наблюдаю точно такое же поведение на совершенно новом проекте Django только с этими тремя зависимостями в моем requirements.txt
, используя тот же Dockerfile и bash-скрипт, что и выше:
django
gunicorn
uvicorn[standard]