Django + uWSGI + Docker пример настройки

Дата выхода: 1 февраля 2024 г.

Редактировался: 13 апреля 2024 г.

Интернет полнится статьями по настройке Django с Gunicorn, но что если вам нужно больше функциональности от WSGI сервиса, тогда вам больше подойдёт uWSGI возможности которого гораздо шире.

Представим, что у нас уже есть проект (ссылка на подобный проект) простое django приложение отображающее страницу с какими-то данными.

Структура проекта:

Папки:

  1. app - папка с django проектом, а так же с статическими и медиа файлами;
  2. data - хранит базу данных postgres;
  3. nginx - папка с конфигом nginx-а;

Запапускается проект с помощью docker-compose, yaml файл которого имеет следующий вид:

version: '3.9'
services:
  
  db:
    container_name: Database
    image: postgres
    volumes:
      - ./data:/var/lib/pgsql/data
    environment:
      - POSTGRES_DB=post
      - POSTGRES_USER=test
      - POSTGRES_PASSWORD=verystrongpasswd
      - PGDATA=/var/lib/pgsql/data/pgdata

  app:
    container_name: Web-app
    build: ./app
    command: bash -c "python manage.py makemigrations &&
            python manage.py migrate && python manage.py collectstatic --noinput &&
            gunicorn mysite.wsgi:application --bind 0.0.0.0:8001"
    volumes:
      - ./app:/app
    environment:
      - POSTGRES_DB=post
      - POSTGRES_USER=test
      - POSTGRES_PASSWORD=verystrongpasswd
    expose:
      - "8001"
    depends_on:
      - db

  nginx:
    container_name: NGINX
    build: ./nginx
    volumes:
      - ./app/static:/mysite/static
      - ./app/media:/mysite/media
    ports:
      - "8001:80"
    depends_on:
      - db
      - app

Как видно из yaml файла у нас есть контейнер с базой данных, nginx-ом и приложением django, которое работает через gunicorn, от последнего мы сейчас и будем избавляться.

Для начала создадим в папке django приложения (app) файл конфигурации uWSGI (uwsgi.ini) чтобы не писать параметры в консоли, выглядеть он будет сделующим образом:

[uwsgi]
# Путь к django проекту
chdir           = /app
# Путь к wsgi django
module          = mysite.wsgi:application

master          = true
# Количество процессов
processes       = 4

# Путь к сокету
socket          = /socket/pntlv.sock
# Права доступа к файлу сокета в ОС
chmod-socket    = 664
# UID пользователя (web) от имени которого будет запущен процесс
uid             = 1000
# GUID группы (web)
gid             = 1000

# Очистка всех сгенерированных файлов/сокетов по завершению работы uWSGI
vacuum          = true
enable-threads  = true
thunder-lock    = true
single-interpreter = true
need-app        = true
die-on-term     = true
disable-logging = true
py-call-osafterfork = true

# Принцип жизни воркеров
max-requests    = 1000 # Перезапуск workers после указанного количества запросов
max-worker-lifetime = 3600 # Перезапуск workers после указанного времени в сек
reload-on-rss   = 1024 # Перезапуск workers после потребления указанного количества памяти
worker-reload-mercy = 60 # Время ожидания перед уничтожение процессов workers

Большей части параметров я дал краткое описание, но если вам этого недостаточно то воспользуйтесь официальной документацией.

Приложение gunicorn работало с nginx через порт 8001, с uWSGI мы поступим по другому, будем использовать socket, для этого создадим в корне проекта папку socket, её мы будем пробрасывать в контейнеры с помощью volumes, внесём соответствующие изменения в docker-compose.yaml:

version: '3.9'
services:
  
  db:
    container_name: Database
    image: postgres:alpine
    volumes:
      - ./data:/var/lib/pgsql/data
    environment:
      - POSTGRES_DB=post
      - POSTGRES_USER=test
      - POSTGRES_PASSWORD=verystrongpasswd
      - PGDATA=/var/lib/pgsql/data/pgdata

  app:
    container_name: Web-app
    build: ./app
    # 0 Заменяем gunicorn на uWSGI
    command: bash -c "python manage.py makemigrations &&
            python manage.py migrate && python manage.py collectstatic --noinput &&
            uwsgi --ini uwsgi.ini"
    volumes:
      - ./app:/app
      # 1 Прокидываем (расшариваем) папку socket
      - ./socket:/socket
    environment:
      - POSTGRES_DB=post
      - POSTGRES_USER=test
      - POSTGRES_PASSWORD=verystrongpasswd
    expose:
      - "8001"
    depends_on:
      - db

  nginx:
    container_name: NGINX
    build: ./nginx
    volumes:
      - ./app/static:/mysite/static
      - ./app/media:/mysite/media
      # 2 Прокидываем (расшариваем) папку socket
      - ./socket:/socket
    ports:
      - "8001:80"
    depends_on:
      - db
      - app

Не забываем что uWSGI должен быть установлен в контейнере django приложения, для этого добавим его в файл requirements.txt (и удалим от туда gunicorn).

Теперь необходимо изменить Dockerfile нашего приложения следующим образом:

FROM python:slim
# Определяем переменные среды
# Чтобы python не создавал файлы .pyc
ENV PYTHONDONTWRITEBYTECODE=1
# Чтобы видеть выходные данные приложения в реальном времени
ENV PYTHONUNBUFFERED=1
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем в рабочую директорию файл зависимостей
COPY requirements.txt /app/

# Установливаем gcc необходимый для сборки uWSGI
RUN apt-get update && apt-get install gcc -y

# Обновляем pip устанавливаем зависимости
RUN pip install --upgrade pip
# Устанавливаем все зависимости без использования кеша
RUN pip install --no-cache-dir -r requirements.txt
# Копируем содержимое локальной директории web в рабочую директорию
COPY . /app/

# Создаём группу и пользователя UID и GUID которых мы указали в uwsgi.ini
RUN addgroup --gid 1000 web && adduser -ingroup web --uid 1000 web
# Используем только что созданого пользователя
USER web

Далее нам остаётся только изменить файл конфигурации nginx-а:

upstream mysite {
    # Старый способ работы через порты
    # server app:8001;

    # Новый способ работы через сокет
    server unix:///socket/mysite.sock;
}

server {
    # Прослушивается 80 порт
    listen 80;
    
    # Перенаправляем все запросы в django приложение
    location / {
        # Старый способ работы через порты
        # proxy_pass http://mysite;

        # Новый способ работы через сокет
        uwsgi_pass  mysite;
        include uwsgi_params;

        # Устанавливаем заголовки
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        # Отключаем перенаправление
        proxy_redirect off;
    }

    # подключаем статические файлы
    location /static/ {
        alias /app/static/;
    }
    # подключаем медиа файлы
    location /media/ {
        alias /app/media/;
    }

}

Готово, теперь после пересборки образа нашего django приложения (docker-compose up --build) оно будет работать через uWSGI.

Весь код используемый в статье можно найти по ссылке.

python uWSGI разработка