Django База [2023]: Докеризация проекта Django 4.1 🐋 #49
Django

Django База [2023]: Докеризация проекта Django 4.1 🐋 #49

Razilator

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

Докер - это платформа для разработки, доставки и запуска приложений в контейнерах. Контейнеры докера запускаются в изолированной среде, где они могут взаимодействовать только с другими контейнерами и ресурсами, которые были специально выделены для них.

Мы рассмотрим создание dev версии проекта, для локальной работы. А в следующем уроке с деплоем мы уже создадим prod версию нашего проекта для сборки на основе dev.

Расположение файлов, папка docker

Создадим папку docker в корне проекта, а внутри папки docker создадим следующие папки: env, logs, nginx. В них мы будем хранить переменные для файлов продакшена и разработки, а в логах логи celery и celery-beat, а в nginx будем хранить конфиги.

Получиться должно вот так
Получиться должно вот так

Создание env файла

Сначала мы создадим .env.dev файл в папке docker/env, для запуска локальной версии переменных с настройками. Дальше перед деплоем мы создадим тоже самое, но с настройками для продакшн версии.

Чтобы корректно работали переменные виртуального окружения, установим в наш проект пакет environ: pip install django-environ

Результат установки:

Терминал
(venv) PS C:\Users\Razilator\Desktop\Base\backend> pip install django-environ
Collecting django-environ
  Downloading django_environ-0.10.0-py2.py3-none-any.whl (19 kB)
Installing collected packages: django-environ
Successfully installed django-environ-0.10.0

[notice] A new release of pip available: 22.3 -> 23.0.1
[notice] To update, run: python.exe -m pip install --upgrade pip

Если мы настроим переменные в терминале VSCODE и запустим Django из него, проект не запустится, потому что Django не сможет их прочитать. Однако, если мы запустим проект через контейнер, то все будет работать, но это не решает проблему запуска проекта без Docker. Бывают ситуации, когда нужно быстро запустить, проверить и исправить проект, не используя Docker. Иногда настройка среды разработки может быть запутанной и сложной, поэтому мы ставим environ и можем работать с проектом.

Настроим виртуальные переменные в env.dev:

docker/env/.env.dev
SECRET_KEY=<djangokey>
DEBUG=1
ALLOWED_HOSTS=127.0.0.1 localhost
CSRF_TRUSTED_ORIGINS=http://127.0.0.1 http://localhost
POSTGRES_DB=<dbName>
POSTGRES_USER=<dbUser>
POSTGRES_PASSWORD=<dbPassword>
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
REDIS_LOCATION=redis://redis:6379/1
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/0
RECAPTCHA_PUBLIC_KEY=<recaptchaPublicKey>
RECAPTCHA_PRIVATE_KEY=<recaptchaPrivateKey>
EMAIL_HOST=<emalHost>
EMAIL_PORT=<emailPort>
EMAIL_USE_TLS=1
EMAIL_HOST_USER=<email>
EMAIL_HOST_PASSWORD=<emailPassword>

Локально .env.dev будет выглядеть так, где <> необходимо заполнить свои данные.

Перейдем в файл конфигурации Django: settings.py и изменим параметры добавив в них переменные из виртуальной среды .env.dev. Сначала добавим импорт envrion в самом верху файла:

backend/settings.py
from pathlib import Path
from celery.schedules import crontab
import environ

# Работа с env.dev
env = environ.Env()

environ.Env.read_env(env_file=Path('./docker/env/.env.dev'))

Далее параметры, меняем следующим образом, и добавим ещё CSRF_TRUSTED_ORIGINS параметр для правильной работы

backend/settings.py
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = int(env('DEBUG', default=1))

ALLOWED_HOSTS = env('ALLOWED_HOSTS').split()

CSRF_TRUSTED_ORIGINS = env('CSRF_TRUSTED_ORIGINS').split()

# Другие параметры

# Cache and Celery settings

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': env('REDIS_LOCATION'),
    }
}

# Celery settings

CELERY_BROKER_URL = env('CELERY_BROKER_URL')
CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND')
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Europe/Moscow'

# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': env('POSTGRES_DB'),
        'USER': env('POSTGRES_USER'),
        'PASSWORD': env('POSTGRES_PASSWORD'),
        'HOST': env('POSTGRES_HOST'),
        'PORT': env('POSTGRES_PORT'),
    }
}

# Другие параметры

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

EMAIL_HOST = env('EMAIL_HOST')
EMAIL_PORT = env('EMAIL_PORT')
EMAIL_USE_TLS = int(env('EMAIL_USE_TLS', default=1))

EMAIL_HOST_USER = env('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')

EMAIL_SERVER = EMAIL_HOST_USER
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
EMAIL_ADMIN = list(EMAIL_HOST_USER)

# Другие параметры

RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY')
RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PRIVATE_KEY')

Таким образом мы заполнили параметры переменными из окружения, которые мы можем держать у себя на компьютере и публиковать проект на github без публичного обозрения наших ключей и паролей.

Создадим файл requirements.txt для получение зависимостей

Перед докеризацией нам необходимо получить все необходимые зависимости используемые в нашем проекте Django, для этого мы можем сгенерировать файл requirements.txt, делается это следующей командой: pip freeze > requirements.txt

Вот что получилось в сгенерированном файле на момент урока:

requirements.txt
amqp==5.1.1
asgiref==3.6.0
async-timeout==4.0.2
billiard==3.6.4.0
celery==5.2.7
click==8.1.3
click-didyoumean==0.3.0
click-plugins==1.1.1
click-repl==0.2.0
colorama==0.4.6
Django==4.1.5
django-ckeditor-5==0.2.4
django-cleanup==7.0.0
django-environ==0.10.0
django-js-asset==2.0.0
django-mptt==0.14.0
django-recaptcha==3.0.0
django-taggit==3.1.0
django_debug_toolbar==3.8.1
kombu==5.2.4
lxml==4.9.2
Pillow==9.4.0
prompt-toolkit==3.0.38
psycopg2==2.9.5
pytils==0.4.1
pytz==2022.7.1
redis==4.5.1
six==1.16.0
sqlparse==0.4.3
tzdata==2022.7
vine==5.0.0
wcwidth==0.2.6

В будущем этот файл понадобится для сборки контейнера с Django.

Конфигурация nginx для Django

Создадим в папке docker/nginx папку dev, в которой создадим конфиг сервера nginx: django.conf со следующим содержимым:

docker/nginx/dev/django.conf
upstream django {
    server django:8000;
}

server {
    listen 80;
    listen [::]:80;

    server_name _;
    server_tokens off;

    location / {
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Url-Scheme $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://django;
    }

    location /static/ {
        alias  /app/static/;
        expires 15d;
    }

     location /media/ {
        alias  /app/media/;
        expires 7d;
    }
}

Это версия без SSL, работающая на 80 порту для локального сервера. На основе нее мы создадим в следующем уроке версию для продакшена.

В целом, данная конфигурация nginx настраивает обратный прокси-сервер для Django-приложения, обслуживает статические и медиа-файлы напрямую и настраивает заголовки для прокси-сервера, чтобы он мог корректно обрабатывать запросы, переданные ему nginx.

Создание Dockerfile

В корне нашего проекта создадим два файла: Dockerfile и docker-compose.dev.yml.

В файле Dockerfile мы определим окружение, в котором будет работать наш проект Django, а также установим необходимые модули.

Dockerfile будет выглядеть следующим образом:

Dockerfile
FROM python:alpine

ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

# Устанавливаем обновления и необходимые модули
RUN apk update && apk add libpq
RUN apk add --virtual .build-deps gcc python3-dev musl-dev postgresql-dev

# Обновление pip python
RUN pip install --upgrade pip

# Установка пакетов для проекта
COPY requirements.txt ./requirements.txt
RUN pip install -r requirements.txt

WORKDIR /app

# Удаляем зависимости билда
RUN apk del .build-deps

# Копирование проекта
COPY . .

# Настройка записи и доступа
RUN chmod -R 777 ./

Данный код содержит инструкции для создания Docker-образа, который будет использоваться для запуска Django-приложения. Разберем по шагам:

FROM python:alpine - указывает, что образ будет основан на образе Alpine Linux с предустановленной Python версии.

ENV PYTHONUNBUFFERED=1 и ENV PYTHONDONTWRITEBYTECODE=1 - задаются значения переменных окружения, чтобы Python не использовал буферизацию ввода-вывода и не создавал скомпилированные файлы для ускорения запуска.

RUN apk update && apk add libpq - обновляет репозитории и устанавливает библиотеку libpq, необходимую для подключения к PostgreSQL.

RUN apk add --virtual .build-deps gcc python3-dev musl-dev postgresql-dev - устанавливает зависимости, необходимые для компиляции некоторых Python-модулей и библиотек.

RUN pip install --upgrade pip - обновляет pip до последней версии.

COPY requirements.txt ./requirements.txt - копирует файл requirements.txt из локальной директории в директорию контейнера.

RUN pip install -r requirements.txt - устанавливает зависимости Python-приложения из файла requirements.txt.

WORKDIR /app - задает рабочую директорию контейнера.

COPY . . - копирует содержимое локальной директории в директорию контейнера.

RUN chmod -R 777 ./ - устанавливает права доступа 777 для всех файлов и директорий внутри контейнера. Это может быть полезно для отладки и тестирования, но не рекомендуется для продакшн среды, так как может привести к уязвимостям безопасности.

Создание docker-compose.dev.yml

Далее нам необходимо создать файл docker-compose.dev.yml, создаем его также в корне и заполняем слудющим содержимым:

docker-compose.yml
version: '3.8'

volumes:
  pgdata:
  static:
  media:

services:

  django:
    build:
      context: .
    ports:
      - '8000:8000'
    container_name: django
    env_file:
      - docker/env/.env.dev
    volumes:
      - ./:/app
      - static:/app/static
      - media:/app/media
    depends_on:
      - postgres
      - redis
    command: sh -c "python manage.py collectstatic --no-input &&
                    python manage.py makemigrations &&
                    python manage.py migrate &&
                    python manage.py runserver 0.0.0.0:8000"

  nginx:
    container_name: nginx
    working_dir: /app
    image: nginx:stable-alpine
    restart: always
    ports:
      - "80:80"
    volumes:
      - static:/app/static
      - media:/app/media
      - ./docker/nginx/dev/:/etc/nginx/conf.d:ro
    links:
      - django
    depends_on:
      - django

  postgres:
    image: postgres:alpine
    container_name: postgres
    restart: always
    env_file:
      - docker/env/.env.dev
    volumes:
      - pgdata:/var/lib/postgresql/data/
  
  redis:
    image: redis:alpine
    container_name: redis
    env_file:
      - docker/env/.env.dev
    expose:
      - 6379
    volumes:
      - ./docker/redis/data:/data
  
  celery-worker:
    build: .
    container_name: celery-worker
    restart: always
    env_file:
      - docker/env/.env.dev
    volumes:
      - ./:/app
      - media:/app/media
    command: celery -A backend worker --loglevel=info --logfile=./docker/logs/celery-worker.log
    depends_on:
      - redis
  
  celery-beat:
      build: .
      container_name: celery-beat
      env_file:
          - docker/env/.env.dev
      depends_on: 
          - redis
      command: celery -A backend beat --loglevel=info --logfile=./docker/logs/celery-beat.log
      volumes:
          - media:/app/media
          - ./:/app

Код представляет собой описание Docker-композиции для развертывания веб-приложения на базе фреймворка Django.

Первый блок определяет именованные тома для хранения данных приложения, а именно pgdata для хранения данных PostgreSQL, а также static и media для хранения статических файлов и медиа-контента.

Далее определены сервисы, используемые в композиции:

  • django - контейнер, который использует Dockerfile в текущей директории для создания образа, на основе которого будет запущен контейнер. В качестве базового образа используется python:alpine. Запускается команда collectstatic для сбора статических файлов, makemigrations и migrate для миграции базы данных, а затем runserver для запуска приложения на порту 8000.
  • nginx - контейнер с веб-сервером nginx, который служит для обработки входящих запросов и передачи их в контейнер с приложением Django. Настроен на прослушивание 80 порта, а также на использование статических файлов и медиа-контента из соответствующих именованных томов.
  • postgres - контейнер с СУБД PostgreSQL, используемый для хранения данных приложения. Запускается с базовым образом postgres:alpine и монтирует именованный том pgdata для хранения данных.
  • redis - контейнер с базой данных Redis, используемый для кеширования данных и брокер для Celery. Монтирует папку для хранения данных Redis и использует env-файл для определения переменных окружения.
  • celery-worker - контейнер, который использует Dockerfile в текущей директории для создания образа, на основе которого будет запущен контейнер. Команда celery запускает worker, который слушает очередь задач, используя Redis в качестве брокера.
  • celery-beat - контейнер, использующий тот же Dockerfile, что и celery-worker. Команда celery запускает beat, который управляет периодическим выполнением задач, также используя Redis в качестве брокера.

Для каждого сервиса определены настройки, такие как контейнерное имя, используемый образ, порты, env-файлы, монтируемые тома и команды, которые будут выполнены при запуске контейнера. С помощью параметра depends_on определены зависимости между сервисами, чтобы Docker-композиция могла правильно запускаться в правильном порядке.

Запуск Django проекта в докере

Так как это у нас локальная dev версия проекта, запущу я его из под Windows. Предварительно у меня уже установлен Docker на пк.

Запустим билд проекта в терминале следующей командой: docker compose -f docker-compose.dev.yml build, создание образа займёт какое-то время. Обычно это 3-5 минут, зависит все от вашего процессора и памяти.

Процесс сборки билда и установки зависимостей
Процесс сборки билда и установки зависимостей

После создания образа с контейнерами билд необходимо запустить, делается это следующей командой: docker compose -f docker-compose.dev.yml up.

Процесс запуска контейнеров
Процесс запуска контейнеров
Процесс запуска контейнеров
Процесс запуска контейнеров
Процесс запуска контейнеров
Процесс запуска контейнеров
Процесс запуска контейнеров
Процесс запуска контейнеров

Я использую флаг -f, так как этот файл содержит имя docker-compose.dev.yml и является dev версией, если бы имя файла было бы docker-compose.yml, можно было бы просто запустить его через команду: docker compose build --up.

Что получилось в самом Docker при билдинге Django

Контейнеры
Контейнеры
Тома
Тома
Образы, всего на 1.14 гб, так как мы используем alpine образы, мы добились такого сжатого размера
Образы, всего на 1.14 гб, так как мы используем alpine образы, мы добились такого сжатого размера

Проверка работы Django

Отлично. Давайте перейдём на наш сайт, который запущен в Docker. Мы можем перейти на адрес: localhost, либо 127.0.0.1, без использования порта.

Сайт открывается
Сайт открывается
Nginx в контейнере отрабатывает
Nginx в контейнере отрабатывает

Как создать учетную запись администратора из консоли

Сайт пустой, но для его заполнения необходимо создать учетную запись, для этого можно ввести следующую команду, чтобы попасть в терминал контейнера с django: docker exec -it django sh, где django имя любого контейнера, в какой терминал вам необходимо зайти, и уже дальше мы создаем также как обычно суперпользователя: python manage.py createsuperuser, заполняем необходимые данные.

Далее войдём в админку с учетными данными:

Панель администратора
Панель администратора

И давайте для теста добавим тестовую статью:

Статья успешно добавлена и отображается
Статья успешно добавлена и отображается

Попробуем пройти регистрацию и проверить работу Celery:

Прохожу регистрацию
Прохожу регистрацию
Письмо отправлено
Письмо отправлено

Celery в контейнере отработал:

docker/logs/celery-worker.log
[2023-04-05 14:34:01,120: INFO/MainProcess] celery@7c3b2a28b0f6 ready.[2023-04-05 14:44:08,645: INFO/MainProcess] Task modules.services.tasks.send_activate_email_message_task[7e5e42d1-3910-4e44-83ab-56f92f23bed5] received
[2023-04-05 14:44:11,334: INFO/ForkPoolWorker-15] Task modules.services.tasks.send_activate_email_message_task[7e5e42d1-3910-4e44-83ab-56f92f23bed5] succeeded in 2.685380100000202s: None
Письмо активации, не забываем в админке изменять example.com на домен своего сайта
Письмо активации, не забываем в админке изменять example.com на домен своего сайта
Подтверждаем аккаунт, снова всё работает
Подтверждаем аккаунт, снова всё работает
Структура папок на конец урока
Структура папок на конец урока

Таким образом мы запустили Django проект в Docker со всеми необходимыми инструментами. На основе этой версии в следующем уроке мы создадим продакшен версию, в которой рассмотрим добавление SSL, работу с .env.prod, другим конфигом nginx под SSL и Gunicorn.

;