Cat

Docker 8. Разворачивание Django-проекта в Docker compose

Docker - это не просто инструмент для запуска контейнеров с различными проектами и сервисами. Сегодня Docker является одним из основных способов разворачивания (деплоя) приложений на сервере.

Все статьи

Icon Link

Дополнительные материалы

Icon Link

Реклама

Icon Link
Применение Docker proDream 04 Июль 2024 Просмотров: 722

Docker - это не просто инструмент для запуска контейнеров с различными проектами и сервисами. Сегодня Docker является одним из основных способов разворачивания (деплоя) приложений на сервере.

Один из ключевых компонентов Docker – это Docker Compose. Docker Compose позволяет запускать контейнеры в так называемом "композ сервисе", объединяя их в единую локальную сеть. Он решает множество проблем, с которыми сталкиваются разработчики при работе с отдельными контейнерами. Он упрощает управление зависимостями, позволяя описывать все сервисы проекта в одном YAML-файле, что облегчает запуск и конфигурацию. Вместо того, чтобы вручную запускать каждый контейнер, можно использовать одну команду docker-compose up, чтобы поднять все сервисы. Более того, Compose обеспечивает портативность и воспроизводимость конфигурации, что позволяет любому разработчику быстро развернуть проект на любом сервере без сложной настройки окружения. Таким образом, Docker Compose значительно упрощает процесс разработки, тестирования и деплоя многокомпонентных приложений.

В нескольких предыдущих постах мы подготовили всё необходимое для разворачивания, а именно:

Теперь осталось собрать всё воедино и добавить к этому веб-сервер NGINX для обработки входящих запросов.

 

Подготовка.

Да, нам снова нужна подготовка.

На VPS создайте директорию project и поместите в неё файлы Django-проекта и файлы бота в соответствующих директориях, например, pressanybutton для сайта и pressanybutton_bot для бота. Также создайте директорию postgres-db для хранения файлов базы данных.

Убедититесь, что у вас есть достаточные права доступа и место на диске для всех необходимых файлов и контейнеров.

В директории project создайте файл docker-compose.yaml.

Должна получиться следующая структура:

project/
├── pressanybutton/
│   ├── manage.py
│   ├── requirements.txt
│   ├── Dockerfile
│   ├── .env
│   └── <директории проекта>
├── pressanybutton_bot/
│   ├── main.py
│   ├── requirements.txt
│   ├── Dockerfile
│   ├── .env
│   └── <директории проекта>
├── postgres-db/  # пока пустая директория
└── docker-compose.yaml  # файл, в котором будем описывать сервис

 

Предложенная структура проекта является примером и может быть изменена в зависимости от специфики проекта.

 

Файл docker-compose.yaml.

Файл docker-compose.yaml описывает, какие сервисы (контейнеры) будут входить в композ-сервис, как они будут запускаться, какие образы использовать и так далее. Параметров, доступных для определения сервиса, много и они позволяют настроить работу сервисов практически под любой случай.

Обратите внимание! Поскольку это YAML-файл, он придерживается строгих правил форматирования. Учтите это при написании конфигурации.

Откроем файл.

Раньше в самом начале файла было обязательно писать версию используемого Docker Compose-протокола. Обычно все писали version: "3" и этого было достаточно. Разработчики Docker поняли, что это бессмысленный параметр и в последних версиях Docker от него отказались. Поэтому первое, что мы напишем - это ключ services:. После этого ключа начинается описание будущих контейнеров.

services:

 

Сервис Базы Данных.

Первым сервисом, который мы опишем будет PostgreSQL.

Прописываем ключ db со следующим содержимым:

db:  
  image: postgres:15  
  restart: always  
  environment:  
    - POSTGRES_USER=db_user  
    - POSTGRES_PASSWORD=db_password  
    - POSTGRES_DB=db_name  
    - PGDATA=/var/lib/postgresql/data/pgdata  
    - POSTGRES_EXTENSIONS=pg_trgm  
  volumes:  
    - ./postgres-db:/var/lib/postgresql/data

 

Разберём написанное:

  1. image: postgres:15 — Указываем используемый образ. В нашем случае это официальный образ 15-й версии PostgreSQL. Можно указать другую версию или образ из другого источника при необходимости.
  2. restart: always — Указываем, что если в контейнере произойдёт сбой, то он будет перезагружаться автоматически. Это позволяет контейнеру возобновить свою работу при возникших неполадках. Однако, иногда это может привести к проблемам, например, когда в проекте ошибка, из-за которой контейнер не может запуститься. В этом случае он будет перезагружаться пока его не остановят принудительно.
  3. environment: — В этих строках описываются переменные окружения, передаваемые внутрь контейнера. В прошлых постах мы использовали .env-файл для передачи переменных окружения, однако, если нужно передать немного переменных, можно прописать их здесь, чтобы не создавать лишние файлы. Переменные окружения разберём чуть ниже.
  4. volumes: — Указываем подключаемые внутри контейнера внешние директории. Это нужно не для всех контейнеров, однако, для контейнера с базой данных это обязательный пункт, поскольку контейнер ненадёжное место. После перезагрузки он сбрасывается в состояние созданного образа, что может привести к потере важных данных. В данном пункте мы прописываем, что созданная ранее директория postgres-db будет примонтирована внутри контейнера по пути /var/lib/postgresql/data.

 

Переменные окружения:

  • POSTGRES_USER — Указываем имя пользователя базы данных.
  • POSTGRES_PASSWORD — Указываем пароль для пользователя базы данных.
  • POSTGRES_DB — Создаём базу данных, связанную с указанным выше пользователем.
  • PGDATA — Определяем, в какой директории будут храниться файлы базы данных внутри контейнера.
  • POSTGRES_EXTENSIONS=pg_trgm — Определяем подключенные к базе данных расширения, в нашем случае это расширение pg_trgm для поддержки триграммного поиска. Подробнее об этом в посте "Django 29.1 Добавляем поиск на сайт".

 

Подготовка Django

Прежде чем мы приступим к описанию Django-сервиса, необходимо дополнительно подготовиться (снова).

Дело в том, что в посте "Django 42. Запуск Django-проекта на VPS" мы с вами сделали запуск Django на встроенном веб-сервере, который хорош для разработки, но не подходит для полноценной работы.

Для работы с большим объёмом запросов и лучшей производительности необходим WSGI (Web Server Gateway Interface) или ASGI (Asynchronous Server Gateway Interface). Это мост между веб-сервером (например, NGINX) и веб-приложением (например, фреймворк Django).

Поскольку мы работаем с синхронным Django, нам необходим WSGI. Библиотек для работы с ним много, например, Gunicorn или uWSGI. Мы будем использовать uWSGI.

Пропишите его в файле requirements.txt Django-проекта:

uwsgi==2.0.26

 

Обратите внимание! Библиотека uWSGI не работает в Windows. К тому же, при локальной разработке она ни к чему, её можно не устанавливать, а оставить только для VPS.

Далее нам необходимо создать файл конфигурации для uWSGI. В директории pressanybutton, где находится файл manage.py, создадим новый файл uwsgi.ini и откроем его.

Пропишем следующее содержимое:

[uwsgi]  
socket = /code/pab_app.sock  
chdir = /code/pressanybutton  
module = pressanybutton.wsgi:application  
master = true  
chmod-socket = 666  
uid = www-data  
gid = www-data  
vacuum = true

 

Разберём параметры:

  • socket = /code/pab_app.sock — Этот параметр указывает путь к сокету Unix, который будет использоваться для связи между uWSGI и веб-сервером. Сокет позволяет передавать запросы от веб-сервера к приложению и возвращать ответы обратно.
  • chdir = /code/pressanybutton — Параметр chdir изменяет текущий рабочий каталог процесса uWSGI на указанный. В данном случае это /code/pressanybutton. Это полезно, когда приложение или его зависимости находятся в определённом месте и нужно, чтобы процесс uWSGI работал из этого каталога.
  • module = pressanybutton.wsgi:application — Указывает модуль и объект WSGI приложения, которые будут загружены и запущены uWSGI. В нашем случае uWSGI будет искать объект application в модуле pressanybutton.wsgi, который является проектом Django.
  • master = true — Этот параметр включает режим мастера. В режиме мастера uWSGI запускается как главный процесс, который управляет дочерними процессами.
  • chmod-socket = 666 — Параметр chmod-socket задаёт права доступа к сокету Unix. Значение 666 означает, что сокет будет доступен для чтения и записи всем пользователям.
  • uid = www-data и gid = www-data — Эти параметры задают идентификатор пользователя (uid) и группы (gid), под которым будет работать процесс uWSGI. Значение www-data обычно используется для веб-серверов Apache и Nginx, что позволяет избежать проблем с правами доступа к файлам.
  • vacuum = true — Параметр vacuum указывает uWSGI удалять старые версии сокета перед созданием нового. Это помогает предотвратить проблемы с "зависшими" сокетами, если приложение было перезапущено без соответствующей очистки.

Далее следует изменить наш Dockerfile, удалив из него две последние команды:

EXPOSE 8000  
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

 

Больше нет необходимости автоматически запускать проект из Dockerfile, так как запуск будет указан в docker-compose.yaml.

Остался последний момент, который необходимо подготовить, а именно скрипт wait-for-it.sh. Этот скрипт перед запуском Django проверяет доступность базы данных, поскольку может возникнуть ситуация, когда сервис с сайтом запустился и пытается подключиться к БД, но сервис с БД ещё не инициализировался полностью. Это может привести к перезагрузке с ошибкой или остановке сервиса сайта.

Мы не будем писать этот скрипт сами. Воспользуемся готовым скриптом. Скачайте файл wait-for-it.sh из репозитория https://github.com/vishnubob/wait-for-it и поместите его в директорию с Django-проектом.

 

Сервис Django-сайта.

Следующим опишем сервис сайта.

Пропишем ключ web со следующим содержимым:

web:  
  build: ./pressanybutton  
  command: [ "./wait-for-it.sh", "db:5432", "--", "uwsgi", "--ini", "/code/uwsgi.ini" ]  
  restart: always  
  env_file:  
    - ./pressanybutton/.env  
  volumes:  
    - ./pressanybutton:/code  
  depends_on:  
    - db

 

Разберём новые параметры:

  • build: ./pressanybutton — В сервисе для базы данных мы указывали готовый образ, а в посте "Django 42. Запуск Django-проекта на VPS" мы сперва собирали образ, а затем запускали его. Сейчас мы указываем параметр build, означающий, что необходимо взять Dockerfile из указанной директории и автоматически собрать образ для сервиса.
  • command: — Параметр определяет команду, которая будет выполнена при запуске контейнера, аналог CMD из Dockerfile. Команду разберём ниже.
  • env_file: — Так же, как мы ранее указывали .env при запуске контейнера, указываем этот файл для сервиса Django.
  • volumes: — Так же, как и с сервисом базы данных, указываем монтируемую директорию. Тут есть два варианта:
    1. Вместо всей директории указать только монтируемые пути для static и media директорий, чтобы впоследствии дать доступ к ним веб-серверу.
    2. Указать всю директорию с проектом. Таким образом можно изменять файлы проекта "на лету", и для применения изменений достаточно будет перезагрузить контейнер с Django.
  • depends_on: — Параметр, указывающий на зависимость от другого сервиса. В нашем случае это сервис базы данных. Это необходимо для того, чтобы сперва был запущен сервис с БД и только после этого сервис с Django.

Про команду запуска: первым аргументом идёт скрипт wait-for-it.sh. Далее передаём хост и порт базы данных. Обратите внимание, что в качестве хоста мы указываем имя сервиса базы данных. Таким образом, обращения сайта к базе данных будут происходить в пределах сервера. Специальный символ -- (double dash), является разделителем между двумя командами. Сперва выполняется команда до символа, затем команда после. Далее указываем, что запускаем uWSGI с конфигурационным файлом по указанному абсолютному пути внутри контейнера.

 

Сервис Telegram-бота.

Тут совсем просто и коротко.

Пропишем ключ bot со следующим содержимым:

bot:  
  build: ./pressanybutton_bot  
  restart: always

 

Указываем откуда брать Dockerfile и политику перезапуска.

 

Подготовка NGINX.

Перед тем, как приступим к описанию сервиса с NGINX, необходимо подготовить его конфигурационный файл.

В директории project создадим директорию ssl. В эту директорию следует поместить SSL-сертификат и приватный ключ для используемого домена. Как получить сертификат, рассказано в посте "Certbot - бесплатный SSL-сертификат для сайта".

Затем в директории project, рядом с файлом docker-compose.yaml, создадим файл default.conf.template. В этом файле опишем конфигурацию веб-сервера.

У нас будет четыре блока конфигурации:

Первый будет блок upstream:

upstream pressanybutton {  
    server unix:/code/pab_app.sock;  
}

 

В нём указываем группу по имени pressanybutton. По этому имени будем обращаться в другом блоке для проксирования обращений. Внутри блока указываем, что сервером является Unix-сокет, который создаёт uWSGI в Django.

Следующий блок server прослушивает 80-й HTTP-порт и при обращении на домен по HTTP перенаправляет с 301-м HTTP-кодом на HTTPS протокол:

server {  
    listen 80;  
    server_name www.pressanybutton.ru pressanybutton.ru;  
    return 301 https://$host$request_uri;  
}

 

Далее ещё один блок переадресации: на этот раз прослушивается 443-й HTTPS-порт и при обращении на поддомен с www происходит переадресация на домен без www. Поскольку это HTTPS, в этом блоке указываем путь до SSL-сертификата и ключа:

server {  
    listen 443 ssl;  
    ssl_certificate /code/ssl/cert.crt;  
    ssl_certificate_key /code/ssl/privkey.key;  
    server_name www.pressanybutton.ru;  

    return 301 https://pressanybutton.ru$request_uri;  
}

 

Последний, самый большой и основной блок прослушивает 443-й HTTPS порт. Внутри блока также указываем:

  • SSL-сертификат и его ключ.
  • Вывод ошибок в терминал.
  • В параметре client_max_body_size указываем максимально допустимый размер загружаемого файла. В нашем случае это 100МБ.
  • Указываем, что включено gzip-сжатие и настройки этого сжатия.
  • Блок location / — В нём указываем, что используется uWSGI-сокет, на который происходят перенаправление со всех запросов на сайт.
  • location /static/ и location /media/ — В них указываем, что если в пути запроса есть static или media, то необходимо отдавать файлы из соответствующих директорий. Также добавляем кэширование статических и медиа файлов на стороне клиента.
server {  
    listen               443 ssl;  
    ssl_certificate      /code/ssl/cert.pem;  
    ssl_certificate_key  /code/ssl/privkey.pem;  
    server_name  pressanybutton.ru;  
    error_log    stderr warn;  
    access_log   /dev/stdout main;  
    client_max_body_size 100M;  

    gzip on;  
    gzip_comp_level 6;  
    gzip_min_length 1024;  
    gzip_proxied any;  
    gzip_vary on;  
    gzip_types  
        application/javascript  
        application/json  
        application/ld+json  
        application/manifest+json  
        application/x-web-app-manifest+json  
        text/css  
        text/plain  
        text/xml  
        text/x-component  
        text/x-cross-stream;  
    gzip_disable "text/javascript";  

    location / {  
        include      /etc/nginx/uwsgi_params;  
        uwsgi_pass   pressanybutton;  
    }  

    location /static/ {  
        alias /code/static/;  
        add_header Cache-Control "max-age=604800";  
    }  
    location /media/ {  
        alias /code/media/;  
        add_header Cache-Control "max-age=604800";  
    }  
}

 

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

 

Сервис веб-сервера NGINX.

Последним сервисом опишем веб-сервер.

Пропишем ключ nginx со следующим содержимым:

nginx:  
  image: nginx  
  restart: always  
  volumes:  
    - ./default.conf.template:/etc/nginx/templates/default.conf.template  
    - ./pressanybutton:/code/  
    - ./ssl:/code/ssl  
  ports:  
    - "80:80"  
    - "443:443"

 

Разберём параметры:

  • volumes: — Прописываем три монтирования:
    1. Монтируем файл конфигурации внутри контейнера.
    2. Монтируем директорию с Django. Нам это нужно для доступа к статическим и медиа файлам, а также к Unix-сокету.
    3. Монтируем директорию ssl для доступа к сертификатам внутри контейнера.
  • ports: — Данный параметр указывает, какие порты будут доступны вне контейнера и с какими портами они связаны внутри контейнера. В данном случае прослушиваются и вещаются 80-й и 443-й порт — стандартные порты для веб.

 

Перед запуском.

Перед тем, как мы всё запустим, убедитесь, что в .env указаны верные параметры, например, хост базы данных это db, и что все файлы доступны.

 

Запуск Docker compose.

Для запуска Docker Compose достаточно выполнить команду:

sudo docker compose up -d

 

Разберём команду:

  • sudo docker — Указывает, что мы обращаемся к приложению Docker с правами администратора (root).
  • compose — Указывает, что нам необходим плагин Compose.
  • up — Указывает, что мы хотим запустить Docker Compose-сервис из docker-compose.yaml, расположенного в этой директории.
  • -d — Указывает, что мы запускаем сервис в фоновом режиме.

После выполнения команды начнётся скачивание и сборка образов.

 

Миграции и статика.

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

Необходимо применить миграции. Для этого выполним следующие команды:

# Создадим миграции, если они не были созданы ранее.
sudo docker exec -it project-web-1 python /code/manage.py makemigrations

# Применим миграции.
sudo docker exec -it project-web-1 python /code/manage.py migrate

 

Разберём команды:

  • exec — Указывает, что мы хотим выполнить команду внутри контейнера.
  • -it — Указывает, что мы хотим интерактивный режим.
  • project-web-1 — Имя контейнера.
  • python /code/manage.py migrate — Команда, которую хотим выполнить внутри контейнера.

Если сейчас снова открыть сайт, он загрузится, но в нём могут отсутствовать стили и другая статика. Для решения этой проблемы выполним команду:

sudo docker exec -it project-web-1 python /code/manage.py collectstatic

 

Всё это можно сделать в исполняемом при сборке скрипте, но это тема для другого раза.

 

Дополнительно.

Остановка композ-сервиса.

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

sudo docker compose down

 

Логи контейнеров

Для просмотра логов запущенных контейнеров используйте команду:

sudo docker compose logs -f

 

После выполнения команды будут выведены логи всех контейнеров. Если необходимо посмотреть логи конкретного контейнера, сделать это можно, выполнив команду:

sudo docker logs имя_контейнера

 

Обновление образов.

Для обеспечения безопасности и стабильности проекта рекомендуется регулярно обновлять Docker образы и зависимости. Это можно сделать с помощью команды:

sudo docker compose pull

 

Эта команда обновит образы до последних версий, указанных в docker-compose.yaml.

 

Заключение.

Теперь вы знаете, как можно развернуть свой сайт на Django с использованием Docker Compose. На первый взгляд, всё это кажется сложным, и в процессе неизбежно возникнут трудности на разных этапах, но с каждым последующим разом начинает выполняться "на автомате". Вы начинаете понимать, где возникла ошибка и как её решить.

Данный пост – базовая инструкция, которую можно адаптировать и расширять в зависимости от специфики вашего проекта. Не забывайте регулярно обновлять ваши образы и контейнеры до последних версий для обеспечения безопасности и использования новых возможностей. С Docker Compose это делается довольно просто и эффективно.

На этом с Django мы не заканчиваем. Там ещё много всего, что можно сделать.

Автор

    Нет комментариев

    Реклама