Docker 8. Разворачивание Django-проекта в Docker compose
Docker - это не просто инструмент для запуска контейнеров с различными проектами и сервисами. Сегодня Docker является одним из основных способов разворачивания (деплоя) приложений на сервере.
Дополнительные материалы
Для скачивания материалов необходимо войти или зарегистрироваться
Файлы также можно получить в Telegram-боте по коду: 274821
Реклама
Docker - это не просто инструмент для запуска контейнеров с различными проектами и сервисами. Сегодня Docker является одним из основных способов разворачивания (деплоя) приложений на сервере.
Один из ключевых компонентов Docker – это Docker Compose. Docker Compose позволяет запускать контейнеры в так называемом "композ сервисе", объединяя их в единую локальную сеть. Он решает множество проблем, с которыми сталкиваются разработчики при работе с отдельными контейнерами. Он упрощает управление зависимостями, позволяя описывать все сервисы проекта в одном YAML-файле, что облегчает запуск и конфигурацию. Вместо того, чтобы вручную запускать каждый контейнер, можно использовать одну команду docker-compose up
, чтобы поднять все сервисы. Более того, Compose обеспечивает портативность и воспроизводимость конфигурации, что позволяет любому разработчику быстро развернуть проект на любом сервере без сложной настройки окружения. Таким образом, Docker Compose значительно упрощает процесс разработки, тестирования и деплоя многокомпонентных приложений.
В нескольких предыдущих постах мы подготовили всё необходимое для разворачивания, а именно:
- В посте "Django 42. Запуск Django-проекта на VPS" подготовили наш Django-проект.
- В постах "AIOgram3 17. Подготовка к разворачиванию на сервере" и "Docker 7. Разворачивание Telegram-бота в Docker на VPS" подготовили бота.
- В посте "Docker 3. Контейнер с PostgreSQL" - мы показали, как запускать контейнер с PostgreSQL.
Теперь осталось собрать всё воедино и добавить к этому веб-сервер 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
Разберём написанное:
image: postgres:15
— Указываем используемый образ. В нашем случае это официальный образ 15-й версии PostgreSQL. Можно указать другую версию или образ из другого источника при необходимости.restart: always
— Указываем, что если в контейнере произойдёт сбой, то он будет перезагружаться автоматически. Это позволяет контейнеру возобновить свою работу при возникших неполадках. Однако, иногда это может привести к проблемам, например, когда в проекте ошибка, из-за которой контейнер не может запуститься. В этом случае он будет перезагружаться пока его не остановят принудительно.environment:
— В этих строках описываются переменные окружения, передаваемые внутрь контейнера. В прошлых постах мы использовали.env
-файл для передачи переменных окружения, однако, если нужно передать немного переменных, можно прописать их здесь, чтобы не создавать лишние файлы. Переменные окружения разберём чуть ниже.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:
— Так же, как и с сервисом базы данных, указываем монтируемую директорию. Тут есть два варианта:- Вместо всей директории указать только монтируемые пути для
static
иmedia
директорий, чтобы впоследствии дать доступ к ним веб-серверу. - Указать всю директорию с проектом. Таким образом можно изменять файлы проекта "на лету", и для применения изменений достаточно будет перезагрузить контейнер с 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:
— Прописываем три монтирования:- Монтируем файл конфигурации внутри контейнера.
- Монтируем директорию с Django. Нам это нужно для доступа к статическим и медиа файлам, а также к Unix-сокету.
- Монтируем директорию
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 мы не заканчиваем. Там ещё много всего, что можно сделать.
Все статьи