Используем Docker и Django

Используем Docker и Django

Подробное руководство по использованию Docker в разработке на языке программирования Python. Расскажу об основных концепциях в Docker и на примере создадим свой образ с веб-приложением на Django и запустим его.

Введение

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

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

Если вы арендуете место у облачного провайдера, например, Amazon Web Services (AWS), они, как правило, не предоставляют вам выделенную часть оборудования. Вместо этого вы используете один физический сервер совместно с другими клиентами. Но поскольку каждый клиент имеет свою виртуальную машину, работающую на сервере, клиенту кажется, что у него есть свой собственный сервер.

Именно эта технология позволяет быстро добавлять или удалять серверы у облачного провайдера. В основном за кулисами стоит программное обеспечение, а не реальное оборудование.

В чем недостаток виртуальной машины? 

Размер и скорость. Типичная гостевая операционная система может легко занять 700 МБ. Поэтому если один физический сервер поддерживает три виртуальные машины, это как минимум 2,1 ГБ дискового пространства, а также отдельные потребности в ресурсах процессора и памяти.

Используем Docker и Django

На помощь приходит Docker. 

Основная идея заключается в том, что большинство компьютеров работают на базе одной и той же операционной системы Linux, так что если бы мы виртуализировали, начиная с уровня Linux?

Разве это не обеспечит легкий и быстрый способ дублирования большей части тех же функций? 

Ответ — да. 

И в последние годы контейнеры Linux стали широко популярны. Для большинства приложений — особенно веб-приложений — виртуальная машина предоставляет гораздо больше ресурсов, чем требуется, и контейнера более чем достаточно. 

По сути, это и есть Docker: способ реализации Linux-контейнеров!

Здесь можно использовать аналогию с домами и квартирами. Виртуальные машины — это как дома: отдельные здания с собственной инфраструктурой, включая водопровод и отопление, а также кухню, ванные комнаты, спальни и так далее. Контейнеры Docker похожи на квартиры: они имеют общую инфраструктуру, такую как водопровод и отопление, но бывают разных размеров, которые соответствуют точным потребностям владельца.

Сравнение виртуальных сред и Docker

Виртуальные среды — это способ изолировать пакеты Python. Благодаря виртуальным средам на одном компьютере можно локально запускать несколько проектов. Например, проект A может использовать Python 3.10 и Django 4.0 среди прочих зависимостей, в то время как проект B использует Python 3.5 и Django 1.11. Создавая новую виртуальную среду для каждого проекта и устанавливая пакеты Python в нее, а не глобально на компьютер, можно поддерживать, управлять и обновлять все необходимые пакеты Python по мере необходимости.

Существует несколько способов реализации виртуальных сред, но, пожалуй, самым простым является использование модуля venv, он уже установленный как часть стандартной библиотеки Python 3.

Важное различие между виртуальными средами и контейнерами Docker заключается в том, что виртуальные среды могут изолировать только пакеты Python. Они не могут изолировать не-Python программное обеспечение, например, базу данных PostgreSQL или MySQL. Кроме того, виртуальные среды все еще полагаются на глобальную установку Python на системном уровне (другими словами, на вашем компьютере). Виртуальная среда указывает на существующую установку Python; она не содержит самого Python.

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

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

Установка Docker

Итак, достаточно теории. Давайте начнем использовать Docker и Python вместе.

Docker — кроссплатформенный. Он есть для всех популярных операционных систем, но установка в разных ос отличается.

Я кратко опишу процесс установки для основных систем, не вдаваясь в детали. Если вдруг у вас возникнут какие-то проблемы и вы не сможете самостоятельно решить их, пишите в комментарии подробности — пофиксим 🙂

P.S. С установкой на Windows мне не приходилось сталкиваться.

Установка Docker для MacOS

Скачайте и запустите приложение

Версия для процессора Apple Silicon https://desktop.docker.com/mac/main/arm64/Docker.dmg

Версия для процессора Intel (только х64) https://desktop.docker.com/mac/main/amd64/Docker.dmg

Процесс установки стандартен, но на всякий случай оставлю ссылку на официальную документацию https://docs.docker.com/desktop/install/mac-install/

Установка Docker для Windows

Скачайте и запустите приложение

Версия только для х64 https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe

Ссылка на документацию если что-то пошло не так https://docs.docker.com/desktop/install/windows-install/

Насколько я понял, Docker может работать в Windows с разными бэкэндами, hyper-v и wsl.

Если у вас не серверная OC, то wsl ваш случай.

Установка Docker для Linux

Docker поддерживается всеми основными дистрибутивами.

В документации есть подробная информация об установке на:

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

Проверка установки Docker

Далее я буду демонстрировать выполнение команд в командной оболочке операционной системы.

Я предполагаю что у вас есть достаточно знаний для запуска терминала и выполнения команд под пользователем root.

Если добавить своего пользователя в группу docker, то можно обойтись без запуска команд под root

Открываем терминал и вводим следующую команду

docker --version  

Вывод команды

Docker version 20.10.17, build 100c701

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

Далее необходимо проверить на тестовом Docker образе запуск контейнеров

docker run hello-world

Вывод команды

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
7050e35b49f5: Pull complete 
Digest: sha256:62af9efd515a25f84961b70f973a798d2eca956b1b2b026d0a4a63a3b0b6a3f2
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Тут мы видим, что Docker не смог найти локальный образ «hello-world:latest» и поэтому скачал из репозитория.

После того как завершилась загрузка Docker запустил образ.

Основные концепции в Docker

Для того чтобы уверенно двигаться дальше, я советую ознакомиться вам с основными концепциями в Docker.

Образ

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

Обычно он содержит объединение слоистых файловых систем, сложенных друг на друга. Однако образ не имеет состояния и всегда остается неизменным (никогда не изменяется).

Другими словами, файл, состоящий из нескольких слоев, который мы используем для выполнения кода в контейнере Docker, — это то, что мы называем образом Docker.

По сути, он создан из инструкций для полной и исполняемой версии приложения, которое опирается на ядро ОС хоста. Когда пользователь Docker запускает образ, он становится одним или несколькими экземплярами этого контейнера.

Слой

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

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

При желании пользователь может создать образ полностью с нуля, даже если большинство образов Docker начинаются с базового образа. В интерфейсе командной строки (CLI) каждый слой образа Docker можно просмотреть в папке /var/lib/docker/aufs/diff или с помощью команды Docker history).

По умолчанию Docker показывает все образы верхнего слоя, например, хранилище, теги и размеры файлов.

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

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

Репозиторий

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

Как мы знаем, Docker Hub — это облачная служба реестра, предлагаемая компанией Docker, которая включает в себя частные и публичные хранилища образов.

Кроме того, в нем есть Docker Trusted Registry, который добавляет функции управления образами и контроля доступа.

В то время как образы сообщества — это образы, созданные пользователями Docker, официальные образы были созданы компанией Docker. Примером официального образа Docker является агент CoScale, который предлагает мониторинг приложений Dockerized.

А datadog/docker-dd-agent — пример общественного Docker-образа, то есть как Docker-контейнер для агентов программы управления журналами Datadog.

Используя команду docker push, пользователь может загрузить свой собственный пользовательский образ в Docker Hub.

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

Контейнер

По сути, экземпляры образов Docker, которые можно запускать с помощью команды docker run, и есть то, что мы называем контейнерами. Однако основное назначение Docker — это запуск контейнеров.

Используя Docker API или CLI, мы можем создать, запустить, остановить, переместить или удалить контейнер. Более того, исходя из текущего состояния контейнера, мы можем подключить его к одной или нескольким сетям, присоединить к нему хранилище или даже создать новый образ.

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

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

Файл Dockerfile

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

Dockerfile создает образы.

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

Например, Dockerfile может содержать инструкции по установке определенных программ или загрузке определенных файлов с других серверов.

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

Файл docker-compose.yml

Если наше Docker-приложение включает более одного контейнера, то создание, запуск, а также подключение контейнеров из отдельных Docker-файлов отнимает много времени. Поэтому в качестве решения этой проблемы Docker Compose позволяет нам использовать YAML-файл для определения многоконтейнерных приложений.

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

Кроме того, Docker-compose может работать в нескольких средах, таких как staging, production, testing, development, а также в рабочих процессах доставки и внедрения кода.

Файл .dockerignore

Файл .dockerignore — это лучший способ указать определенные файлы и каталоги, которые не должны включаться в образ Docker. Это может помочь уменьшить общий размер образа и повысить безопасность, не допуская попадания в Docker того, что должно быть секретным.

Обычно мы можем спокойно игнорировать локальную виртуальную среду (.venv), наш будущий каталог .git и файл .gitignore. Так же часто в macOS создаются файлы .DS_Store при навигации через Finder, они нам тоже не нужны.

Для использования создайте новый файл с именем .dockerignore в базовом каталоге.

Тестовое приложение

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

В качестве примера будем использовать связку Python, Django и Docker.

Наше приложение будет называться hh. Особо думать над названием не стал, планирую написать приложение для head hunter вот и подготовлю для него скелет проекта.

Подготовка окружения

Создаем директорию и переходим в корневой каталог.

mkdir hh
cd hh/

Создаем виртуальное окружение для проекта и активируем его

python3 -m venv .venv
. .venv/bin/activate

Обновляем пакетный менеджер и устанавливаем необходимые библиотеки

python3 -m pip install --upgrade pip
python3 -m pip install Django==4.0.8 gunicorn==20.1.0

Сохраним зависимости в файл requirements.txt

touch requirements.txt
echo "Django==4.0.8" > requirements.txt
echo "gunicorn==20.1.0" >> requirements.txt

Подготовка веб приложения

Создадим проект Django

django-admin startproject -v 3 hh .

Точечка в конце указывает текущую директорию в качестве корневой и не создает дополнительный каталог hh.

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

Приложение для проекта я создавать не буду. Для демонстрации связки Django+Docker это не обязательно. Нам нужно увидеть только стартовую страницу от Django.

Подготовка контейнеризации

Создадим файл .dockerignore и заполним его

touch .dockerignore
echo "db.sqlite" >> .dockerignore
echo ".venv" >> .dockerignore
echo "__pycache__" >> .dockerignore
  • db.sqlite — база данных проекта.
  • .venv — папка с виртуальным окружением
  • __pycache__ — папка с байткодом python модулей/приложений

Переходим к созданию Dockerfile

touch Dockerfile

Вот так он должен выглядеть

FROM python:3.9.12-slim
WORKDIR /app/
COPY . .
RUN python3 -m pip install --no-cache-dir --no-warn-script-location --upgrade pip \
    && python3 -m pip install --no-cache-dir --no-warn-script-location --user -r requirements.txt

Приведу описание каждой строки

  • FROM python:3.9.12-slim — в качестве основного образа мы будем использовать официальный минимальный образ с python 3.9.12 на борту
  • WORKDIR /app/ — рабочая директория (аналогично если бы мне сделали cd /app)
  • COPY . . — копирование файлов из текущего каталога хоста в текущий каталог docker образа
  • RUN — это обычный оператор исполнения команды. тут мы устанавливаем необходимые зависимости

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

touch docker-compose.yml

Содержимое файла

# docker-compose.yml
version: '3'

services:

  migrate:
    build: .
    container_name: 'migrate'
    command: >
      /bin/sh -c "python3 manage.py makemigrations --force-color --no-input -v 3
      && python3 manage.py makemigrations --merge --no-input -v 3
      && python3 manage.py migrate --force-color -v 3
      && python3 manage.py createsuperuser --noinput; exit 0"
    environment:
      - DJANGO_SUPERUSER_USERNAME=admin
      - DJANGO_SUPERUSER_PASSWORD=admin
      - DJANGO_SUPERUSER_EMAIL=admin@example.com
    volumes:
      - .:/app

  gunicorn:
    image: hh_migrate
    container_name: 'gunicorn'
    restart: always
    command: /bin/sh -c "python3 -m gunicorn -b 0.0.0.0:80 hh.wsgi --reload"
    volumes:
      - .:/app
    ports:
      - 80:80
    depends_on:
      - migrate

В этом файле мы создаем два сервиса, migrate и gunicorn.

  • migrate — сервисный сервис (лол), он обслуживает наш фреймворк и вносит фундаментальные изменения.
  • gunicorn — сервер веб приложений.

В переменных окружения мы задаем имя пользователя и пароль администратора, для доступа в админ панель.

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

Запуск приложения

В каталоге с файлом docker-compose.yml выполняем следующую команду

docker-compose up

Удостоверимся что сервер приложений запустился

gunicorn  | [7] [INFO] Starting gunicorn 20.1.0
gunicorn  | [7] [INFO] Listening at: http://0.0.0.0:80 (7)
gunicorn  | [7] [INFO] Using worker: sync
gunicorn  | [8] [INFO] Booting worker with pid: 8

И пробуем перейти по ссылке http://127.0.0.1 должны увидеть следующее

Используем Docker и Django

Поздравляю, у вас все получилось.

Часто используемые команды

Я мало посветил времени командам для управления Docker и хочу отдельно описать команды, которые я использую очень часто.

  • docker-compose build — сборка образа из docker-compose.yml
  • docker-compose down — выключить все запущенные контейнеры из docker-compose.yml
  • docker-compose exec <container> <command> — позволяет выполнить определенную команду внутри контейнера
  • docker images list — отображает локальный список образов
  • docker ps — отображает статистику
  • docker log -f <container> — показывает лог работы контейнера (стандартный вывод)
  • docker-compose stop <container> — останавливает контейнер
  • docker stats — отображает статистику потребления ресурсов контейнерами

Еще небольшой советик — используйте алиасы в вашей оболочке, а то постоянно вводить такие длинные команды адово.

Заключение

Я допустил множество сознательных упрощений. Это сделано специально для легкого усвоения.

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

Поделитесь своими впечатлениями о статье, все ли было понятно? Удалось усвоить материал?

Может каких то разделов не хватает для понимания концепции использования Docker?

Егор Егоров

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

Ссылка на мой github есть в шапке. Залетай.

Оцените автора
Егоров Егор
Добавить комментарий

  1. Иван

    Так откуда hh_migrate? У меня ругается на эту строчку
    ERROR: pull access denied for hh_migrate, repository does not exist or may require ‘docker login’: denied: requested access to the resource is denied

    Может просто migrate?

    Ответить
    1. Егор Егоров автор

      hh — название проекта, migrate — название сервиса, в рамках одного docker-compose файла именование идет именно таким образом. Возможно вы не сделали docker-compose build?

      Ответить
  2. Михаил

    Не совсем понятно в этой строке: «image: hh_migrate»
    Откуда взялся образ hh_migrate?

    Ответить
    1. Егор Егоров автор

      это первый сервис в docker-compose.yml

      Ответить
  3. Читатель

    спасибо за такую большую статью, все по полочкам

    Ответить
  4. Alex

    Благодарю

    Ответить