Локальная разработка Docker и Django: минимальное пошаговое руководство

Зачем запускать Django внутри Docker локально? Разве у вас уже недостаточно движущихся частей, необходимых для работы?

Я пытаюсь ответить на этот вопрос здесь. Посмотрите, относится ли это к вашему варианту использования. Этот пост посвящен тому, как это сделать.

В конце этого поста у вас будет:

  1. Настройте Docker локально на своем устройстве разработки.
  2. Запустите Django в контейнере Docker на том же компьютере.
  3. Поставили точку останова и отладили код!

Pre-reqs

  • Докер установлен локально. Для проверки концепции я использовал Docker Desktop.

Минимальная установка Docker

Наша минимальная установка docker будет такой:

  • запустить реляционную базу данных: Postgres
  • непосредственно запустить команду runserver, что следует делать в целях отладки

Наша минимальная установка не docker:

  • запустите веб-сервер, например Nginx
  • запустите gunicorn или uwsgi как “клей" между фреймворком (кодом Django) и веб-сервером

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

Минимальное понимание Docker

Если некоторые концепции Docker все еще непонятны вам, не волнуйтесь. Мне самому постоянно приходится искать что-то новое.

Когда я начинал, мне очень помогла эта статья: 6 основ Docker, которые вы должны полностью усвоить, приступая к работе. В этой статье объясняются взаимосвязь и основные различия между:

  • Containers
  • Images
  • Dockerfiles
  • Volumes
  • Port Forwarding
  • Docker compose

Спасение, если вы запутались в шквале нового жаргона Docker. Для меня это было так. В этом посте мы будем настраивать:

  • один Dockerfile,
  • один файл Docker compose, или docker-compose.yml

Установите проект Django локально

Dockerfile

Добавьте Dockerfile в корень вашего проекта Django с таким содержимым:

1
2
3
4
5
6
7
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 намного меньше, чем большинство базовых образов дистрибутива, и в целом приводит к более тонким образам. Подробнее об образах Python и их вариантах можно прочитать здесь.

В строке 2 переменной среды PYTHONUNBUFFERED присваивается значение 1. Что это? Обычно, если у вас есть процесс, передающий данные в ваше приложение, терминал может буферизовать данные. Терминал хранит данные в резервуаре до тех пор, пока не будет достигнут предел размера или определенный символ (обычно новая строка или конец строки). В этот момент он сразу выгружает весь блок данных в ваше приложение. То же самое для выходных данных и данных об ошибках (stdout и stderr). Эта опция предписывает терминалу не использовать буферизацию. Подробнее об этой опции здесь.

Оставшийся набор инструкций в строках 3-7:

  • создает каталог /code на корневом уровне
  • копирует в него requirements.txt
  • устанавливает пакеты python (для начала в контейнере нет необходимости в virtualenv)
  • копирует в него полный каталог проекта

Так что Dockerfile выше:

  1. выбирает для нас базовое изображение,
  2. настраивает его, чтобы мы могли запускать что-то поверх него, устанавливая необходимые пакеты и копируя код нашего проекта Django.

Приятно!

Вопрос: Так как же нам запустить этот контейнер?

В: Мы будем использовать docker-compose.

О: Но контейнер выше работает только с Django. Разве нам не нужен контейнер для Postgres?

О: Нам не нужно настраивать контейнер Postgres, поскольку Docker предоставляет образ Docker для Postgres, который мы можем просто запустить. Затем мы войдем в него и настроим его, как если бы он работал локально.

Вопрос: Должны ли мы написать сценарий оболочки и выполнить процесс docker на нашей локальной машине для обоих контейнеров?

A: Нет. Docker предоставляет docker-compose, а не полагается на сценарии оболочки.

docker-compose

Большим преимуществом файла docker-compose.yml является то, что он очень удобен для чтения.

Добавьте этот файл docker-compose.yml в каталог вашего проекта Django:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 запускает процесс manage.py runserver внутри контейнера с подходящим названием django_web.

Инструкция build: . указывает Docker compose использовать Dockerfile, расположенный в этом же каталоге, для запуска службы web. Он будет запускать службу “внутри” контейнер django_web. Документы по сборке здесь.

Команда command запускает команду Django runserver и предоставляет ее порту контейнера 8000.

container_name - это пользовательское имя, которое вы можете добавить для ясности. Мы увидим его эффект при запуске в следующем разделе.

environment позволяет повторно использовать переменные среды с хост-компьютера. Подробнее об управлении переменными среды для вашего проекта Django. В этом случае переменная среды DATABASE_URL используется повторно.

volumes используется для “монтирования” хостовые пути. Основное употребление в нашем контексте — “поделиться” код на нашем компьютере с кодом в сервис-контейнере django_web. Документы здесь.

В заключение:

  • Строки 18–19 сопоставляют порт хост-компьютера 8000 с портом контейнера 8000.
  • Строки 20–21 обеспечивают зависимость контейнера web от контейнера db.

Достаточно объяснений! Давайте запускать!

Заставьте его работать на вашем локальном Docker-контейнере

Убедитесь, что ваш рабочий стол Docker запущен.

Запустите docker ps, чтобы получить список контейнеров. Если вы не запускали никаких других контейнеров, вы не должны увидеть ни одного. Вывод должен быть следующим:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Выполните следующую команду для запуска двух контейнеров в соответствии с вашим docker-compose.yml:

docker-compose up

Вывод терминала должен заканчиваться обычным выводом команды Django runserver:

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-compose exec web sh

После входа в систему выполните следующие действия для применения миграций базы данных и создания суперпользователя:

./manage.py migrate
./manage.py createsuperuser

Обновите сайт по адресу http://localhost:8000/admin/ и войдите в систему под именем суперпользователя, которого вы только что создали.

Вы можете выйти из контейнера web.

Остановите предыдущую web службу, прежде чем продолжить.

Выполните команду для остановки процесса, как вы бы сделали это с локальным процессом Django runserver.

После этого запуск docker ps должен отображать только запущенную службу db.

Время отладки!

Обновите код и поставьте точку останова в одном из представлений. Я использовал отладчик IPython ipdb.

Создайте контейнер web

После изменения кода выполните следующие действия, чтобы перестроить контейнер web, включая зависимости:

docker-compose up -d --no-deps --build web

Давайте разберем приведенную выше команду docker-compose up:< /p>

  • -d или --detach означает “Отдельный режим”; для запуска контейнеров в фоновом режиме
  • --no-deps указывает docker-compose up не запускать связанные службы
  • --build указывает docker-compose up собрать все необходимые образы перед запуском контейнеров
  • web — это сервис, для которого я запускаю docker-compose up

Отладка!

Для отладки с помощью ipdb используйте docker-compose run, документы здесь.

Флаг --service-ports web заставляет службу web предоставлять необходимые порты для отладки:

docker-compose run --service-ports web

Если вы запустите docker ps, вы должны увидеть, что ваша служба web снова запущена и работает.

Найдите 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 - это расположение модуля на контейнере, а не на вашем локальном dev-боксе.

Это значит... вот оно, вы отлаживаете код, запущенный в вашем сервисе Docker!

Обновление после прочтения "Django для профессионалов"

Чтобы мои знания о Django оставались “актуальными” Читаю книги время от времени. Я узнаю об авторе Уилле Винсенте из подкаста DjangoChat.

Имея опыт работы с Django, я выбрал Django для профессионалов.

Освежает то, что мы начинаем непосредственно с Docker. В первой главе подробно рассказывается о том, что такое Docker. И как сделать так, чтобы ваше приложение Django запускалось на нем с "Hello World" get go.

Я узнал некоторые вещи, которые не знал о Docker, даже после написания этого поста. Например:

ENV PYTHONDONTWRITEBYTECODE 1

предотвращает создание Python файлов pyc.

Единственное предостережение: я не использую pipenv. Я спросил на Reddit о том, чтобы pipenv обрабатывал только производственные требования. Реакция в то время была неутешительной. Мне нравится иметь более точный контроль над тем, какие пакеты запускаются локально в моей родной среде разработки. И какие пакеты запускать в другом месте, например staging/prod.

Но это изменится. Почему? Со временем я хочу перейти на локальный запуск вещей и в Docker. Сейчас я еще не дошел до этого. YMMV.

Django для профессионалов использует pipenv. Его функция детерминированной сборки действительно привлекательна:

Преимущество файла блокировки в том, что это приводит к детерминированной сборке: сколько бы раз вы ни устанавливали пакеты программ, результат будет один и тот же. Без файла блокировки, который "фиксирует" зависимости и их порядок, это не обязательно так. Это означает, что у двух членов команды, устанавливающих один и тот же список программных пакетов, сборки могут немного отличаться.

Но это не должно помешать вам перевести это на любой инструмент управления пакетами, который вы используете.

Еще один урок был усвоен, когда автор попросил, чтобы те части ваших файлов, которые часто меняются, были последними:

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

У меня есть одно замечание по поводу пакета psycopg2-binary по умолчанию. Я хотел бы иметь psycopg2 в производстве. Потому что согласно этому:

Пакет psycopg2-binary предназначен для новичков, чтобы начать работать с Python и PostgreSQL без необходимости соответствовать требованиям сборки.

Если вы являетесь сопровождающим опубликованного пакета, зависящего от psycopg2, вам не следует использовать psycopg2-binary в качестве зависимости от модуля. Для производственного использования вам рекомендуется использовать исходный дистрибутив.

Я не уверен, относится ли это ко всем приложениям или только к издателям пакетов. Я связался с автором по этому поводу. Как только я получу ответ, я сообщу об этом.

Обновление 2020-01-08: Ответ Уилла Винсента по этому поводу:

По поводу psycopg2-binary это скорее причуда Pipenv. Это что-то в текучке. Я думаю, что просто pip не использует двоичный формат - это нормально.

Так что, если в вашей ситуации все просто, просто выберите pyscopg2.

Заключение

Этот учебник призван помочь вам начать и показать основы. Если вы захотите погрузиться глубже, я снабдил вас указателями.

Эта статья не ставит своей целью показать вам все, что можно сделать с помощью Docker. Это далеко не так.

Ландшафт "tech ops" постоянно меняется. И я уверен, что многие команды (или их аргументы) быстро устареют.

Я обратился к этому gist пользователя Github katylava для создания "структуры docker" для этого проекта Django. Это очень помогло.

Вернуться на верх