Подробное руководство по использованию Docker в разработке на языке программирования Python. Расскажу об основных концепциях в Docker и на примере создадим свой образ с веб-приложением на Django и запустим его.
- Введение
- На помощь приходит Docker.
- Сравнение виртуальных сред и Docker
- Установка Docker
- Установка Docker для MacOS
- Установка Docker для Windows
- Установка Docker для Linux
- Проверка установки Docker
- Основные концепции в Docker
- Образ
- Слой
- Репозиторий
- Контейнер
- Файл Dockerfile
- Файл docker-compose.yml
- Файл .dockerignore
- Тестовое приложение
- Подготовка окружения
- Подготовка веб приложения
- Подготовка контейнеризации
- Запуск приложения
- Часто используемые команды
- Заключение
Введение
Docker — это способ изолировать всю операционную систему с помощью контейнеров Linux, которые являются разновидностью виртуализации. Виртуализация уходит своими корнями в начало компьютерной науки, когда большие и дорогие компьютеры-мейнфреймы были нормой.
Как несколько программистов могли использовать одну и ту же машину? Ответом стала виртуализация и, в частности, виртуальные машины, которые представляют собой полные копии компьютерной системы, начиная с операционной системы и выше.
Если вы арендуете место у облачного провайдера, например, Amazon Web Services (AWS), они, как правило, не предоставляют вам выделенную часть оборудования. Вместо этого вы используете один физический сервер совместно с другими клиентами. Но поскольку каждый клиент имеет свою виртуальную машину, работающую на сервере, клиенту кажется, что у него есть свой собственный сервер.
Именно эта технология позволяет быстро добавлять или удалять серверы у облачного провайдера. В основном за кулисами стоит программное обеспечение, а не реальное оборудование.
В чем недостаток виртуальной машины?
Размер и скорость. Типичная гостевая операционная система может легко занять 700 МБ. Поэтому если один физический сервер поддерживает три виртуальные машины, это как минимум 2,1 ГБ дискового пространства, а также отдельные потребности в ресурсах процессора и памяти.
На помощь приходит 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 и хочу отдельно описать команды, которые я использую очень часто.
- 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?
Так откуда 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?
hh — название проекта, migrate — название сервиса, в рамках одного docker-compose файла именование идет именно таким образом. Возможно вы не сделали docker-compose build?
Не совсем понятно в этой строке: «image: hh_migrate»
Откуда взялся образ hh_migrate?
это первый сервис в docker-compose.yml
спасибо за такую большую статью, все по полочкам
Благодарю