Локальная разработка на Docker и Django: минимальное пошаговое руководство
Зачем вам запускать Django внутри Docker локально? Как компактно запустить различные программные компоненты для работы одной информационной системы?
Я попытаюсь здесь ответить на эти вопросы. Посмотрите, относится ли это к вашему варианту использования. Этот пост о том, как это сделать.
В результате выполнения этого поста у вас будет:
- Настроенный локально Docker на вашем компьютере разработчика.
- Работающий Django в Docker контейнере в том же окне разработки.
- Поставить точку останова для отлаживания кода!
Зачем вам запускать Django внутри Docker локально? Как компактно запустить различные программные компоненты для работы одной информационной системы?
Я попытаюсь здесь ответить на эти вопросы. Посмотрите, относится ли это к вашему варианту использования. Этот пост о том, как это сделать.
В результате выполнения этого поста у вас будет:
- Настроенный локально Docker на вашем компьютере разработчика.
- Работающий Django в Docker контейнере в том же окне разработки.
- Поставить точку останова для отлаживания кода!
Предварительные требования
Уже установленный локально Docker. В целях проверки концепции я использовал Docker Desktop.
Минимальная настройка Docker
Наша минимальная настройка Docker’а будет содержать:
- запускать реляционную базу данных Postgres
- непосредственный запуск команды runserver, что необходимо для целей отладки
Наша минимальная настройка Docker’а не будет содержать:
- веб-сервер, такой как Nginx
- gunicorn или uwsgi как «клей» между фреймворком (код Django) и веб-сервером
Так как целью является локальная разработка с Docker, ни то, ни другое не нужно.
Минимальное знакомство с Docker
Если некоторые концепции Docker все еще не ясны вам, не волнуйтесь. Мне самому все время приходится искать что-то новое.
Когда я начал, я нашел эту статью действительно полезной: 6 основ Docker, которые вы должны полностью понять, когда приступаете к работе. Эта статья объясняет взаимосвязь и ключевые различия между ними:
- Контейнеры
- Образы
- Dockerfiles
- Тома
- Перенаправление порта
- Docker Compose
Спасательный круг, если вас смущает шквал нового Docker жаргона. Как это было для меня. В этом посте мы будем настраивать:
- один Dockerfile
- один файл Docker compose или docker-compose.yml
Настройка локального проекта Django
Dockerfile
Добавим Dockerfile в корневой каталог вашего проекта Django со следующим содержимым:
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
Давайте подробнее рассмотрим этот Dockerfile.
Строка 1 выбирает образ: FROM python: 3 указывает Docker начать с образа python: 3. Часто встречается Alpine версия образа Python. Alpine Linux намного меньше, чем большинство базовых образов дистрибутива, и в целом приводит к более компактным образам.
Строка 2 устанавливает переменную среды PYTHONUNBUFFERED равной 1. Что это? Обычно, если у вас есть процесс, передающий данные в ваше приложение, терминал может буферизовать данные. Терминал хранит данные в буфере до тех пор, пока не будет достигнут предел размера или определенный символ (обычно новая строка или EOF). В этот момент он полностью сбрасывает весь кусок данных в ваше приложение. То же самое для выходных данных и данных об ошибках (stdout и stderr). Эта опция сообщает терминалу не использовать буферизацию.
Оставшийся набор инструкций в строках 3-7:
- создает каталог /code на корневом уровне
- копирует файл require.txt в него
- устанавливает пакеты Python (в контейнере не требуется virtualenv в начале)
- копирует в него полный каталог проекта
Итак, Dockerfile рассмотренный выше:
- выбирает базовый образ
- настраивает его так, чтобы мы могли запускать что-то поверх него, устанавливая необходимые пакеты и копируя код нашего проекта Django.
Причесали! Вопрос: Итак, как нам запустить этот контейнер?
Ответ: Мы будем использовать docker-compose.
Вопрос: Но контейнер выше работает только с Django. Разве нам не нужен контейнер для Postgres?
Ответ: Нам не нужно настраивать контейнер Postgres, поскольку Docker предоставляет образ Docker для Postgres, который мы можем просто запустить. Затем мы входим в него и настраиваем его так, как если бы он работал локально.
Вопрос: Должны ли мы написать сценарий оболочки и выполнить процесс Docker на нашей локальной машине для обоих контейнеров?
Ответ: Нет. Docker предоставляет docker-compose, а не полагается на сценарии оболочки.
docker-compose
Большим преимуществом файла docker-compose.yml является то, что он очень удобочитаемый.
Добавим этот файл docker-compose.yml в каталог Django проекта:
version: '1'
services:
db:
image: postgres
environment:
- POSTGRES_DB=djangodb
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
container_name: django_web
environment:
- DATABASE_URL
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db
У нас есть две «службы», db и web.
Служба db запускает процесс Postgres внутри контейнера, который использует образ postgres.
POSTGRES_DB, POSTGRES_USER и POSTGRES_PASSWORD жестко закодированы в docker-compose.yml. Однако вы можете настроить их для использования переменных среды, используя файл .env или .envrc. Это ИМХО не стоит усилий, если вы делаете это только для локального тестирования.
web служба запускает процесс runserver manage.py внутри контейнера с именем django_web.
Инструкция сборки: сообщает Docker compose использовать Dockerfile, расположенный в этом же каталоге, для запуска web службы. Служба будет запущена внутри контейнера django_web. Документы по сборке здесь.
Команда запускает команду runserver в Django и выставляет ее на порту контейнера 8000.
Контейнерное имя - это пользовательское имя, которое вы можете добавить для ясности. Мы увидим его эффект, когда будем запускать вещи в следующем разделе.
Среда позволяет вам повторно использовать переменные окружения с хоста. Больше информации об управлении переменными среды для вашего проекта Django. В этом случае переменная окружения DATABASE_URL используется повторно.
Тома используются для «монтирования» путей к хостам. Основное использование в нашем контексте - это «поделиться» кодом на нашей машине с кодом в служебном контейнере django_web. Документы здесь.
В заключение:
- Строки 18-19 сопоставляют порт 8000 хост-машины с портом контейнера 8000.
- Строки 20-21 обеспечивают зависимость web контейнера от контейнера db.
Достаточно объяснений! Давайте начнем!
Заставляем работать python в локальном контейнере
Убедитесь, что ваш Docker Desktop работает.
Запустите docker ps, чтобы вывести список контейнеров. Предполагая, что не запущены другие контейнеры, вы не должны их видеть. Ваш вывод должен быть ниже:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Запустите команду ниже, чтобы запустить два контейнера в соответствии с вашим docker-compose.yml:
docker-compose up
Вывод терминала должен заканчиваться обычным выводом команды runserver Django:
web_1 | Watching for file changes with StatReloader
web_1 | Performing system checks...
web_1 |
web_1 | System check identified no issues (0 silenced).
web_1 | June 06, 2020 - 10:24:43
web_1 | Django version 3.0.6, using settings 'djangotest.settings'
web_1 | Starting development server at http://0.0.0.0:8000/
web_1 | Quit the server with CONTROL-C.
При запуске docker ps теперь должны отображаться два контейнера (прокрутите вправо, чтобы увидеть полный вывод):
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
91f53455ce25 django_web "python manage.py ru…" 59 seconds ago Up 58 seconds 0.0.0.0:8000->8000/tcp django_web
45b3091c0c62 postgres "docker-entrypoint.s…" 59 seconds ago Up 58 seconds 5432/tcp djangotest_db_1
Обратите внимание, что вывод названий NAMES зависит от имени каталога проекта. Например, поскольку у контейнера службы db нет имени, получающееся имя контейнера - djangtest_db1, потому что каталог проекта - djangotest.
В другом терминальном окне/вкладке войдём в контейнер db docker:
docker-compose exec db sh
После входа, откройте psql от имени пользователя postgres:
su - postgres -c psql
Создадим базу данных:
CREATE DATABASE djangodb OWNER postgres;
Выйдем из psql и контейнера db docker.
Войдем в web контейнер:
docker-compose exec web sh
После входа запустим следующую команду, чтобы применить миграцию базы данных и создать суперпользователя:
./manage.py migrate
./manage.py createsuperuser
Обновим сайт по адресу http://localhost:8000/admin/ и войдем в систему с только что созданным суперпользователем.
Вы можете выйти из web контейнера. Прежде чем продолжить, остановим предыдущий web сервис.
Нажмите на команду, чтобы остановить процесс, как если бы вы использовали локальный процесс сервера запуска Django.
После этого при запуске docker ps должен отображаться только запущенный сервис db.
Время отладки!
Обновите код и установите точку останова в одном из ваших представлений. Я использовал отладчик IPython ipdb.
Перестраиваем web контейнер
После изменения кода, чтобы перестроить web контейнер, включая зависимости:
docker-compose up -d --no-deps --build web
Разложим приведенную выше команду docker-compose up:
- -d или --detach означает «Отдельный режим»; запускать контейнеры в фоновом режиме
- --no-deps инструктирует docker-compose не запускать связанные сервисы
- --build инструктирует docker-compose для создания любых необходимых образов перед запуском контейнеров web - это сервис, для которого я использую docker-compose
Отлаживаем
Для отладки с помощью ipdb, используется docker-compose run, документы здесь .
Флаг --service-ports web заставляет сервис web выставлять необходимые порты для возможности отладки:
docker-compose run --service-ports web
Если вы запустите Docker PS, вы должны увидеть, что ваш веб-сервис запущен и работает.
Получить доступ к URL, где это остановит выполнение на вашей точке останова. Так как я установил свою точку останова на домашней странице, я вижу вывод терминала ниже:
System check identified no issues (0 silenced).
June 06, 2020 - 10:51:15
Django version 3.0.6, using settings 'djangotest.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
> /code/items/views.py(15)get_context_data()
13 context = super().get_context_data(**kwargs)
14 import ipdb; ipdb.set_trace()
---> 15 return context
ipdb> self.request
<WSGIRequest: GET '/'>
Обратите внимание, что /code/items/views.py - это расположение модуля в контейнере, а не в вашем локальной среде разработчика.
Что означает … вот и все, вы отлаживаете код, работающий в вашей службе Docker!
Результат
Этот топик направлен на то, чтобы вы начали и поняли основы. Я оставил ссылки по пути, если вы хотите погрузиться глубже.
Он не собирался показывать вам все, что можно сделать с помощью Docker. Отнюдь не всё.
Пейзаж «технических операций» постоянно меняется. И я уверен, что многие команды (или их аргументы) быстро устареют.