Dockerize Python: создаём образ Docker из приложения на Python
Рассказываем, как создать Docker-контейнер из приложения на языке Python, и как Docker работает с фреймворками Python.
Noveo Developer
Репозиторий с примерами кода для данной статьи: Github.
1. Article
1.0. Используемые технологии
1. Docker и Docker Compose (книга Docker Deep Dive рекомендуется к изучению).
Код-ревью — как сделать правильно
3. Task для локальных pipeline workflow.
4. Python frameworks: Django, FastAPI, Flask. Python ORMs + migrating libraries: Django ORM + SQLALchemy/Alembic.
1.1. Вступление
Docker — технология, заменяющая виртуализацию. В контексте современной web-разработки можно сказать, что Docker является аналогом компиляции приложения в один бинарный файл.
Для таких программных языков как Javascript, Python, PHP, Ruby зачастую это единственный способ заморозить библиотеки зависимостей для получения неизменного артефакта (одного файла), который мы можем поочередно запустить и протестировать на машине разработчика, в облачном тестировочном полигоне и его же запустить на прод.
Docker пришел на смену виртуализации как более легковесный аналог, который можно запускать десятками на одной машине, потому что он переиспользует ядро оси.
Контейнеризация, аналогично виртуализации, служит для более полного использования ресурсов каждой машины, она позволяет нам запустить множество различных приложений на одной машине без конфликтов друг с другом.
Docker является фундаментальным кирпичиком, на основе которого идет дальнейшее построение веб-инфраструктур — Docker Сompose, AWS Elastic Beanstalk, AWS ECS, Kubernetes.
Системы контейнерного оркестрирования запускают множество контейнеров на разных машинах для горизонтального масштабирования нагрузки на сервера (увеличивать количество серверов вместо того, чтобы повышать мощность одного единственного), а также для обеспечения отказоустойчивости инфраструктуры.
Концепция докера хороша тем, что всё приложение в обычном случае не имеет сохраняемого состояния и мы можем заменить одну версию приложения на другую без загрязнения операционной системы, на которой запускаем контейнеры. Контейнер внутри содержит внутрифайловую систему в достаточном количестве для имитации независимой операционной системы.
Использование контейнеризации также приводит к тому, что любые приложения становятся мультитенантными по умолчанию. То есть одно и то же приложение можно запустить множество раз безо всяких конфликтов, даже если они внутри контейнера используют совершенно одинаковые ресурсы файловой системы.
Дополнительным следствием использования контенеризации является то, что все системные зависимости, необходимые для сборки или работы наших приложений, у нас записаны на уровне кода — это лучший вариант документации.
Использование Docker или его аналогов в мире веб-разработки является хорошим шагом даже для самых простых случаев. Для примера, установка WordPress требует конфигурации SQL-базы данных MariaDB / MySQL, Apache Web Server в режиме PHP-сервера, который запустит WordPress. Незнакомый с этими технологиями человек может потратить несколько дней на то, чтобы разобраться, как их запустить все вместе. С использованием Docker Сompose это сводится к трем шагам:
1. Установить Docker.
services: db: # We use a mariadb image which supports both amd64 & arm64 architecture image: mariadb:10.6.4-focal # If you really want to use MySQL, uncomment the following line #image: mysql:8.0.27 command: '--default-authentication-plugin=mysql_native_password' volumes: - db_data:/var/lib/mysql restart: always environment: - MYSQL_ROOT_PASSWORD=somewordpress - MYSQL_DATABASE=wordpress - MYSQL_USER=wordpress - MYSQL_PASSWORD=wordpress expose: - 3306 - 33060 wordpress: image: wordpress:latest ports: - 80:80 restart: always environment: - WORDPRESS_DB_HOST=db - WORDPRESS_DB_USER=wordpress - WORDPRESS_DB_PASSWORD=wordpress - WORDPRESS_DB_NAME=wordpress volumes: db_data:
3. Написать команду docker-compose up -d (-d — флаг запуска в фоновом режиме).
В общей сложности на все это уйдет не более 5 минут.
В рамках работы web-приложений с Python даже в тривиальном случае обычно необходимо множество запущенных процессов к приложению:
- база данных PostgreSQL,
- веб сервер Gunicorn,
- Celery Beat Producer — периодические задачи внутри Python через Message Queue,
- Сelery Worker — обработчик задач,
- Redis — очереди для Celery-задач или же в качестве хранилища кэша быстрого доступа Key-Value, который мы можем использовать из питона.
Часто должны быть установлены какие-нибудь дополнительные Linux-библиотеки по компиляции C-кода или gettext для переводов. Все, что надо установить, и не упомнишь без какого-либо средства автоматизации.
Удобство контейнеризации Докер в том, что… можно смело его использовать даже во время разработки на своей машине. Все равно все наши контейнеры можно остановить и удалить безо всяких загрязнений системы (к примеру, эти команды полностью очистят машину от всех работавших докер-приложений: (docker stop $(docker ps -q), docker system prune -a)
С учетом всех достоинств контейнеризации можно смело заявить, что количество нагрузки, которое она снимает с плеч разработчика, делает ее таким же необходимым инструментом, как файл с зависимостями к любому веб-приложению (package.json / requirements.txt и т.д.).
Зачастую Docker является единственным способом быстро вспомнить и настроить окружение для работы с каким-либо веб-проектом.
Если хотите попробовать контейнеризацию в самом щадящем варианте, изучите Docker Deep Dive и попробуйте Docker и Docker-compose локально на машине.
При работе с прод-проектами можно начать с использования Digital Ocean App Deployment, поддерживающего деплой докер-изображений, а так же AWS Lightsail Container Deployment и AWS Elastic Beanstalk, которые являются самыми простыми вариантами в рамках AWS. Beanstalk тоже из коробки решает горизонтальное масштабирование системы.
В связи с этим рассмотрим более подробно, как контейнизировать веб-приложения питона и подготовить их к минимальному использованию не в режиме дев-окружения.
Подобная инструкция может вам особенно пригодиться, если ваша роль — DevOps Engineer, который отвечает лишь за инфраструктуру системы, но может быть мало знаком с отдельными языками программирования (в частности, с Python) и особенностями их контейнеризации.
Полезным это будет и в случае, если вы просто разработчик Python Backend и хотите настроить свое дев-окружение в более удобном и задокументированном варианте, на уровне выше, чем просто использование virtual venv (ваш DevOps Engineer или тот, кто ответственен за деплой системы, очень обрадуется, увидев Docker-файл к проекту).
1.2. Зависимости
В Python наличествует как минимум 5 способов установки зависимостей к проекту:
- pip-менеджер по умолчанию, установка файлов вида requirements.txt, requirements.dev.text, constraints.txt,
- установка requirements.txt через venv,
- установка зависимостей через Pipenv package manager,
- установка зависимостей через Poetry package manager,
- установка зависимостей через Conda (common for data science/machine learning projects).
4 из них продемонстрированы в Dockerfile для Flask простого проекта.
dep-poetry, dep-pipenv, dep-pip, dep-venv docker stages демонстируруют одноименные установки зависимостей:
FROM python:3.10.5-slim-buster as base # This flag is important to output python logs correctly in docker! ENV PYTHONUNBUFFERED 1 # Flag to optimize container size a bit by removing runtime python cache ENV PYTHONDONTWRITEBYTECODE 1 WORKDIR /code FROM base as dep-poetry ENV POETRY_HOME /opt/poetry RUN python3 -m venv $POETRY_HOME RUN $POETRY_HOME/bin/pip install poetry==1.2.2 ENV POETRY_BIN $POETRY_HOME/bin/poetry COPY pyproject.toml poetry.lock ./ RUN $POETRY_BIN config --local virtualenvs.create false RUN $POETRY_BIN install --no-root COPY src src FROM base as dep-pipenv RUN pip install pipenv COPY Pipfile Pipfile.lock ./ RUN pipenv install --system COPY src src FROM base as dep-pip COPY requirements.txt constraints.txt ./ RUN pip install -r requirements.txt -c constraints.txt COPY src src FROM base as dep-venv ENV VIRTUAL_ENV=/opt/venv RUN python3 -m venv $VIRTUAL_ENV ENV PATH="$VIRTUAL_ENV/bin:$PATH" COPY requirements.txt constraints.txt ./ RUN pip install -r requirements.txt -c constraints.txt COPY src src
Альтернативные варианты установки и настроек можно посмотреть по следующим адресам:
1.3. Веб-серверы
Помимо сказанного нужно учесть и то, что встроенные веб-сервера в Flask/Django/FastAPI, запускаемые через python3 entryfile.py, по умолчанию подразумеваются для использования лишь в разработке.
Python web servers разделяются на две категории:
- WSGI based веб-сервера (вида Gunicorn), использование которых подразумевается для sync Python кода (не содержающего async инструкций),
- и ASGI based веб-сервера (вида Uvicorn), подразумеваемые к использованию для питон-кода, содержащего асинхронный код. Использование Uvicorn для прода с асинхронным кодом подразумевается в паре с Gunicorn, который имеет Uvicorn workers.
Дополнительно нужно учитывать, что питон-веб-сервера не способны возвращать static assets css/js/jpeg and etc. Лучшей рекомендацией сегодня является использовать хотя бы Nginx в режиме reverse proxy к питон-веб-серверу, настроив его возвращать static assets. Ни в коем случае не стоит использовать библиотеки White noise, это решение страшно глючит и медленно работает даже для одного пользователя.
1.3.1. Django (Sync)
Django обычно достаточно настраивать для синхронного кода через WSGI (Django Channels с веб-сокетами для реализации живых чатов, впрочем, существует, и ему нужно ASGI). Вот пример настроенного Django, работающего через Gunicorn-WSGI с Nginx reverse proxy serving static assets. Для такой настройки достаточно установить gunicorn через текущий используемый package manager (pip/poetry/pipenv) и запустить веб-сервер через gunicorn, которому дали путь к WSGI.
В prod-варианте деплоя Python мы как минимум всегда можем указывать количество workers, параллельно обратаывающих запросы gunicorn src.core.wsgi -b 0.0.0.0:8000 —workers 2.
P.S. Django с отключенным settings.Debug = False перестает показывать static assets даже в дев-сервере, работающем через python3 manage.py runserver
1.3.2. FastAPI (Async)
Запуск меняется на gunicorn src.core.main:app —workers 4 —worker-class uvicorn.workers.UvicornWorker —bind 0.0.0.0:8000
Помимо смены веб-сервера с WSGI на ASGI, асинхронные фреймворки питона требуют и асинхронно дружелюбных библиотек, и драйверов для PostgreSQL в том числе. Если для синхронного Django нам достаточно установить psycopg2-binary pip package (обратите внимание, что psycopg2-binary не требует установки C-компилирующих зависимостей, в отличие от библиотеки psycopg2), то для асинхронного фреймворка нам нужно также установить asyncpg для PostgreSQL.
Аналогично в асинхронном фреймворке все используемые библиотеки должны быть async-дружелюбными: aiohttp вместо requests, к примеру, для выполнения сетевых запросов.
1.4. CORS headers
Последняя, самая частая проблема при деплое — CORS headers, которые должны быть часто настроены.
— Для Django это решается через django-cors-headers.
— Для FastAPI — через встроенную библиотеку CORSMiddleware.
Примеры проектов Django с Nginx в режиме CORS, разрешающим все, можно найти здесь.
И пример для FastAPI здесь.
1.5. Помимо прочего
Каждый из питон-фреймворков имеет богатую документацию деплоя с разными решениями:
— Django Deployment (Там есть и полезный checklist для деплоя),
Для Django и FastAPI (и минималистичного варианта под минималистичный Flask) в рамках этой статьи представлены варианты конфигурации контейнеризации с Docker Compose и настроенными выполнениями unit-тестов, создающими объекты в PostgreSQL через Pytest framework.
Обратим внимание, что Django ORM миграции БД, создаваемые через python3 manage.py makemigrations, должны быть закомиттены в репозиторий и могут применяться к БД через python3 manage.py migrate.
Для FastAPI и Flask частым решением используется SQLALchemy для ORM и Alembic для миграции БД. alembic -c src/alembic.ini revision —autogenerate -m «migration_name» для создание миграций БД, alembic -c src/alembic.ini upgrade head — для применения всех до последней миграций к БД.
1.5.1. Static assets
Фреймворку, возможно, еще нужно будет добавить параметр, куда собирать static assets, и во время сборки докер-изображения собрать их туда.
Django: ./manage.py collectstatic —noinput —clear (см. STATIC_URL / STATIC_ROOT в settings.py)
1.5.2 Translations
Если во фреймворке используется translations, понадобится дополнительный шаг во время сборки Docker-изображения для этого.
— Django: python3 manage.py compilemessages.
— Flask: инструкции здесь, если используется Flask-Babel.
— FastAPI: Для FastAPI очевидных решений нет, возможно, используется gettext.
1.5.3. CI pipeline build & test
Ниже предоставлены примеры настройки CI agnostic pipeline workflows через task (которые можно исполнять локально!) и docker-compose:
— для Gitlab CI.dockerize python
2. FAQ
Общее для проектов на Python
Приложение является самостоятельным или это виртуальный хост для веб-сервера?
В общем случае Python веб-приложения самостоятельны и деплоятся через Gunicorn/Uvicorn/WSGI/ASGI и прочие веб-серверы питона (см. полный список в ссылках документации по деплою фреймворков в главе 1.5).
Однако питон-веб-сервера не могут возвращать static assets css/js/jpeg и т.д. В этом случае их возвращают через Nginx, работающий заодно в режиме reverse proxy к питон-серверу.
Nginx и прочие reverse proxy используются в том числе для аугментации вида «регулировать headers», «добавить client side или server side caching».
Питон-веб-сервер можно напрямую сдеплоить через Apache mod_wsgi, например, но это малопопулярное решение. Если очень хочется feature rich возможностей, посмотрите в сторону uWSGI (очень много фич).
И хотя некоторые питон-сервера могут прикрепить TSL-сертификаты, все же проще это сделать через Nginx или иные внешние решения.
Приложение можно запустить в одном контейнере?
Да, можно — при ряде условий:
- если в приложении нет никаких добавок вида Celery (Message Queue),
- если static assets нет необходимости возвращать (для REST API в общем случае не нужно, если только это не Django приложение с используемым Django Admin интерфейсом),
- если для кеширования не нужен Redis или Redis деплоится где-то отдельно.
Иначе даже для одного dev environment используется множество контейнеров: контейнер под PostgreSQL, Redis, основной web server, celery beat (cron like message queue task producer), celery worker (message queue worker), celery flower (message queue monitoring) + nginx (в качестве reverse proxy + serving static assets) — см. пример возможного большого количества контейнеров.
Требуется ли приложению установка зависимостей?
Да, читайте главу 1.2 Зависимости.
Если в проекте применяется вызов вставок C-кода или какого-либо вида Golang, то может как минимум понадобиться установка библиотек компиляции C-кода. Большое количество деталей по разным вариантам описано в книге Python Expert Programming 4th edition в главе C extensions.
Best practices по контейнеризации
- Те же, что и везде: сжимать в один шаг установку и очистку кеша.
- Использовать multi staging… который в основном не нужен, так как компилируют веб-приложения к бинарникам редко.
- Сначала установить зависимости, потом копировать остальной код.
- Нормально иметь Python-код собранным в packages от root folder, чтобы хаков PYTHONPATH с обнаружением модулей и packages не потребовалось (root-папка не должны иметь __init__.py файл, а каждая копируемая папка с Python-кодом должна иметь __init__.py на всех уровнях. И пути импорта прописаны по-человечески — абсолютные от root folder или относительные).
- Использовать ENVIRONMENT variables, а не .env-файлы (их можно разве что для локальной дев-разработки).
- Если настроите logging library вместо print, то совсем молодцы.
- Флажок ENV PYTHONUNBUFFERED 1 нужен, чтобы логи нормально вылезали из контейнера.
- Флажок ENV PYTHONDONTWRITEBYTECODE 1 тоже можно поставить, все равно кэш питон-кода в контейнере будет только занимать лишнее место.
- Не забывать, что assert синтаксис используется лишь в тестировании, а для прода может быть и выключен. Так что лучше его не иметь в рабочем коде.
- Если когда-нибудь будете копировать venv папку с уже установленными зависимостямив контейнер, учтите, что его абсолютный путь не должен меняться, иначе он сломается. Но вообще копировать его — моветон, устанавливайте зависимости в контейнерах во время сборки xD.
- Не копируйте мусор вида __pycache__ в контейнер, настройте .dockerignore.
- Как минимум нужно настроить масштабирование количества процессов workers (для вариантов более feature rich можно посмотреть в сторону uWSGI).
Нужны ли дополнительные скрипты (bash etc) для сборки/запуска приложения?
Для веб-приложений обычно нет, однако c Makefile, или task, или paver жить проще. Либо же просто делать скрипты со встроенной библиотекой argparse. Или через click. Все индивидуально для веб-проектов.
Для приложений, собираемых в бинарники файла вида setup.py от https://pypi.org/project/setuptools/ либо иные, могут наличествовать чисто питон-скрипты/библиотеки для сборки проектов. Для этого случая можно составить список самых частых решений. В рамках статьи сборку бинарников, а также публикацию библиотек на pypi мы не рассматриваем.
Что обычно кэшируется в CI/CD-пайплайне?
При контейнеризации нам, в общем-то, ничего кэшировать и не нужно. Однако если бы этого не было, можно было бы закэшировать устанавливаемые pip packages или используемый venv (под капотом он используется почти для каждого из менеджеров (poetry/pipenv), но в зависимости от Package manager отличается путь, где кэшировать их зависимости).
Gitlab CI template for python
variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" # Pip's cache doesn't store the python packages # https://pip.pypa.io/en/stable/topics/caching/ # # If you want to also cache the installed packages, you have to install # them in a virtualenv and cache it as well. cache: paths: - .cache/pip - venv/
Frameworks
Существуют ли еще какие-либо предварительные процедуры для приложения, кроме установки зависимостей?
Django
Да, в статье большинство из них перечислено:
- Смена dev-сервера на боевой WSGI (для синхронного питона) или ASGI (для асинхронного питона).
- Установка питона нужной версии (или использовании Docker image с нужной питон-основой).
- Установка pip, если отсутствует (python3 -m ensurepip), для установки дальнейшей зависимостей.
- Установка используемого package manager (pipenv, poetry).
- Установка зависимостей.
- Отключение дебага.
- Смена Django-секрета на что-нибудь другое из ENV.
- Настройка env-переменных через os.environ или альтернативные решения.
- Настройка CORS headers.
- Настройка, куда собирать static assets и компилировать их в одну папку (если используются html-возможности Django).
- Компиляции переводов, если используется (python3 manage.py compilemessages), установка OS-зависимостей вида gettext.
- При использовании библиотек питона с компиляцией через C должны быть установлены прочие дев-инструменты компиляции.
- Мигрировать БД потом через python3 manage.py migrate.
FastAPI
В основном повторяет Django-шаги, некоторые вещи повторно не упоминаются:
- Смена сервера на боевой асинхронный ASGI-сервер (для примера Gunicorn с Uvicorn workers) с увеличением количества workers.
- Настройка CORS headers.
- Установка используемого package manager (pipenv, poetry).
- Установка зависимостей.
- Мигрировать БД потом для SQLALchemy через: alembic -c src/alembic.ini upgrade head (или иной используемый ORM).
Flask
В основном повторяет Django-шаги, некоторые вещи повторно не упоминаются:
- Смена dev-сервера на боевой WSGI (для синхронного питона) или ASGI (для асинхронного питона).
- Отключение дебага.
- Установка используемого package manager (pipenv, poetry).
- Установка зависимостей.
- Настройка CORS headers.
- Настройка, куда собирать static assets и компилировать их в одну папку (если используются html-возможности Django).
- Компиляции переводов, если используется (Flask-Babel?), установка OS-зависимостей вида gettext.
- Мигрировать БД потом для SQLALchemy через: alembic -c src/alembic.ini upgrade head (или иной используемый ORM).
Контейнеризация в Python. Часть 1
Разработка в Python в локальных средах может стать нелёгкой задачей, если одновременно работать более чем над одним проектом. Бутстрэппинг (начальная загрузка) проекта может потребовать некоторого времени: нужно согласовать версии, а также настроить зависимости и конфигурацию. Раньше мы устанавливали все требования к проектам напрямую в нашу локальную среду разработки и фокусировались на написании кода. Однако при ведении нескольких проектов в одной среде уже возникают сложности, так как мы можем столкнуться с конфликтами конфигурации или зависимостей. Более того, если мы начинаем работать совместно с коллегами, то наши среды разработки уже приходится координировать. Поэтому нам нужно определить среду проекта так, чтобы её могли с лёгкостью использовать другие.
Для этого можно создать изолированную среду для каждого проекта при помощи контейнеров и Docker Compose — инструмента композиции контейнеров, который будет ими управлять. Эту тему мы раскроем в серии статей, первую часть которой вы сейчас и читаете. Каждая из них будет посвящена отдельному аспекту всего этого процесса.
В текущей части мы рассмотрим, как помещать в контейнер Python-службу или инструмент и познакомимся с наилучшими подходами для осуществления данного процесса.
Требования
Для простоты работы с материалами этой и двух последующих статей нужно установить минимальный набор инструментов, которые позволят управлять помещёнными в контейнеры средами локально:
- Windows или macOS: установите Docker Desktop.
- Linux: установите Docker, а затем Docker Compose.
Контейнеризация сервера Python
В качестве примера мы возьмём простой сервер Flask, который можно запустить автономно, не устанавливая другие компоненты.
server.pyfrom flask import Flask
server = Flask(__name__)@server.route("/")
def hello():
return "Hello World!"if __name__ == "__main__":
server.run()
Перед запуском этой программы сначала нужно убедиться в наличии всех необходимых зависимостей. Один из способов управления ими — применение установщика пакетов, например pip. Для этого нужно создать файл requirements.txt и записать в него зависимости. Ниже приведён пример такого файла для нашего простого server.py :
requirements.txtFlask==1.1.1
app
├─── requirements.txt
└─── src
└─── server.py
Мы создаём выделенную директорию для исходного кода, чтобы изолировать его от других файлов конфигурации. Позже вы увидите, зачем мы это делаем. Для выполнения программы осталось только установить интерпретатор Python и запустить его.
Можно запустить программу локально, но так мы отклонимся от нашей задачи по контейнеризации разработки, которая подразумевает чистую стандартную среду разработки, позволяющую легко переключаться между проектами с разными конфликтующими требованиями. Я расскажу, как легко поместить этот сервис Python в контейнер.
Dockerfile
Чтобы осуществить работу нашего Python-кода в контейнере мы упакуем его как образ Docker, а затем запустим на основе этого образа контейнер:
Для генерации образа Docker нужно создать Dockerfile, содержащий необходимые для сборки образа инструкции. Затем Dockerfile обрабатывается сборщиком Docker, который и сгенерирует нужный образ. После этого с помощью простой команды docker run мы создаём и запускаем контейнер с сервером Python.
Анализ Dockerfile
Ниже приведён пример Dockerfile, содержащего инструкции по сборке образа для нашего сервиса hello world :
Dockerfile# установка базового образа (host OS)
FROM python:3.8# установка рабочей директории в контейнере
WORKDIR /code# копирование файла зависимостей в рабочую директорию
COPY requirements.txt .# установка зависимостей
RUN pip install -r requirements.txt# копирование содержимого локальной директории src в рабочую директорию
COPY src/ .# команда, выполняемая при запуске контейнера
CMD [ "python", "./server.py" ]
Для каждой инструкции или команды из Dockerfile сборщик Docker генерирует слой образа и накладывает его на предыдущие. Таким образом, получающийся в результате образ Docker представляет собой простой только читаемый стэк, состоящий из разных слоёв. В выводе команды сборки мы видим, как по очереди выполняются инструкции Dockerfile:
$ docker build -t myimage .
Sending build context to Docker daemon 6.144kB
Step 1/6 : FROM python:3.8
3.8.3-alpine: Pulling from library/python
…
Status: Downloaded newer image for python:3.8.3-alpine
---> 8ecf5a48c789
Step 2/6 : WORKDIR /code
---> Running in 9313cd5d834d
Removing intermediate container 9313cd5d834d
---> c852f099c2f9
Step 3/6 : COPY requirements.txt .
---> 2c375052ccd6
Step 4/6 : RUN pip install -r requirements.txt
---> Running in 3ee13f767d05
…
Removing intermediate container 3ee13f767d05
---> 8dd7f46dddf0
Step 5/6 : COPY ./src .
---> 6ab2d97e4aa1
Step 6/6 : CMD python server.py
---> Running in fbbbb21349be
Removing intermediate container fbbbb21349be
---> 27084556702b
Successfully built 70a92e92f3b5
Successfully tagged myimage:latest
Затем можно проверить образ в локальном хранилище образов:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myimage latest 70a92e92f3b5 8 seconds ago 991MB
В процессе разработки нам может потребоваться повторно собрать образ для Python-сервиса, на что желательно потратить как можно меньше времени. Далее мы проанализируем некоторые лучшие практики, которые могут нам в этом помочь.
Лучшие практики разработки Dockerfile
Базовый образ
Первая инструкция из Dockerfile определяет базовый образ, поверх которого мы добавляем новые слои для приложения. Выбор базового слоя весьма важен, поскольку поставляемые им возможности могут влиять на качество надстроенных слоёв.
По возможности старайтесь работать с официальными образами, которые, как правило, часто обновляются и имеют меньше проблем с безопасностью.
Выбор базового образа также влияет на размер итогового. Если для вас размер имеет первостепенное значение, то можно выбрать какой-нибудь очень маленький нетребовательный к ресурсам образ. Такие образы обычно основываются на дистрибутиве Alpine и имеют соответствующий тег. Тем не менее для приложений Python в большинстве случаев отлично подходит slim-вариант официального Python-образа Docker (например, python:3.8-slim )
Порядок инструкций влияет на использование кэша сборки
При частой сборке образа мы определённо будем использовать механизм кэширования для ускорения. Как я упоминала ранее, инструкции Dockerfile выполняются в заданном порядке. Для каждой инструкции сборщик сначала проверяет свой кэш на наличие образа для повторного использования. При обнаружении изменения в слое этот и все последующие слои пересобираются. Чтобы кэширование было эффективным, нужно поместить инструкции часто изменяемых слоёв после тех, которые меняются редко.
Посмотрим на пример Dockerfile, чтобы понять, как порядок инструкций влияет на кэширование. Ниже я привела интересующие нас строки:
.
# копирование файла зависимостей в рабочую директорию
COPY requirements.txt .# установка зависимостей
RUN pip install -r requirements.txt# копирование содержимого локальной директории src в рабочую директорию
COPY src/ .
.
В процессе разработки зависимости нашего приложения изменяются не так часто, как Python-код. В связи с этим мы устанавливаем их в слое, предшествующем слою кода. То есть мы копируем файл зависимостей, устанавливаем их, а затем копируем исходный код. Это главная причина изолирования исходного кода в отдельную директорию, о котором было сказано в начале статьи.
Многоэтапные сборки
Хотя это может и не быть существенным в разработке, мы кратко расскажем о подобных сборках, поскольку они интересны в плане итоговой отправки контейнеризованного приложения уже по её завершении.
Многоэтапные сборки используются для очистки итогового образа от ненужных файлов и пакетов ПО, чтобы отправлять только необходимые для выполнения кода файлы. Вот небольшой пример многоэтапного Dockerfile:
# первый этап
FROM python:3.8 AS builder
COPY requirements.txt .# установка зависимостей в локальную директорию user (например, /root/.local)
RUN pip install --user -r requirements.txt# второй этап (без названия)
FROM python:3.8-slim
WORKDIR /code# копирование только установки зависимостей из образа первого этапа
COPY --from=builder /root/.local/bin /root/.local
COPY ./src .# обновление переменной среды PATH
ENV PATH=/root/.local:$PATHCMD [ "python", "./server.py" ]
Обратите внимание, что здесь мы используем двухэтапную сборку, где только первый этап называем builder — сборщик. Название этапу мы задаём, добавляя AS к инструкции FROM и используем это название в инструкции COPY , где хотим скопировать в итоговый образ только необходимые файлы. Результат — облегчённый образ:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myimage latest 70a92e92f3b5 2 hours ago 991MB
multistage latest e598271edefa 6 minutes ago 197MB
…
В этом примере мы установили зависимости в локальную директорию user и скопировали эту директорию в итоговый образ с помощью опции pip -user . Однако для выполнения этих действий есть и другие решения вроде virtualenv или сборки в виде пакетов wheel с последующим их копированием и установкой в итоговый образ.
Запуск контейнера
После написания Dockerfile и сборки образа, мы запускаем контейнер с нашим сервисом:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myimage latest 70a92e92f3b5 2 hours ago 991MB
. $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES$ docker run -d -p 5000:5000 myimage
befb1477c1c7fc31e8e8bb8459fe05bcbdee2df417ae1d7c1d37f371b6fbf77f
Мы и поместили в контейнер сервер hello world и теперь можем запросить порт, сопоставленный с localhost:
$ docker ps
CONTAINER ID IMAGE COMMAND PORTS .
befb1477c1c7 myimage "/bin/sh -c 'python . " 0.0.0.0:5000->5000/tcp . $ curl http://localhost:5000
"Hello World!"
Что дальше?
Мы показали, как помещать в контейнер сервер на Python для облегчения разработки. Контейнеризация позволяет не только добиваться одинаковых результатов на разных платформах, но также избегать конфликтов зависимостей и поддерживать в чистоте стандартную среду разработки. Контейнеризованная среда легко управляется и удобна при совместной работе с другими разработчиками: они смогут без проблем развёртывать её в своих стандартных средах, не внося изменений.
В следующей статье вы узнаете, как настроить основанный на контейнерах многосервисный проект, где Python-компонент соединён с внешними компонентами, а также научитесь управлять жизненным циклом всех компонентов проекта при помощи Docker Compose.
- Пять действительно крутых пакетов Python
- Встроенная база данных Python
- Пространства имен и области видимости в Python
Containerize a Python application
This section walks you through containerizing and running a Python application.
Get the sample application
The sample application uses the popular Flask framework.
Clone the sample application to use with this guide. Open a terminal, change directory to a directory that you want to work in, and run the following command to clone the repository:
content_copy
Initialize Docker assets
Now that you have an application, you can use docker init to create the necessary Docker assets to containerize your application. Inside the python-docker directory, run the docker init command. docker init provides some default configuration, but you’ll need to answer a few questions about your application. For example, this application uses Flask to run. Refer to the following example to answer the prompts from docker init and use the same answers for your prompts.
content_copy
You should now have the following contents in your python-docker directory.
content_copy
To learn more about the files that docker init added, see the following:
Run the application
Inside the python-docker directory, run the following command in a terminal.
content_copy
Open a browser and view the application at http://localhost:5000 . You should see a simple Flask application.
In the terminal, press ctrl + c to stop the application.
Run the application in the background
You can run the application detached from the terminal by adding the -d option. Inside the python-docker directory, run the following command in a terminal.
content_copy
Open a browser and view the application at http://localhost:5000 .
You should see a simple Flask application.
In the terminal, run the following command to stop the application.
content_copy
For more information about Compose commands, see the Compose CLI reference.
Summary
In this section, you learned how you can containerize and run your Python application using Docker.
- Build with Docker guide
- Docker Compose overview
Next steps
In the next section, you’ll learn how you can develop your application using containers.
Установка Python в Docker — от запуска контейнера до создания рабочей среды

Новые версии Python разрабатывают всё время. Но каждый раз компилировать его самому, чтобы попробовать новую версию, довольно обременительно.
Выход из этой ситуации прост — запускать различные версии Python через Docker.
Из этого руководства вы узнаете:
- Какие версии Python доступны.
- Как начать работу с Docker.
- Как запускать определённую версию Python в контейнерах Docker.
- Как использовать контейнеры Docker в качестве рабочих сред Python.
Docker и версии Python
С прекращением поддержки Python 2.7 в 2020 году, долгий переход от Python 2 к Python 3 вошел в финальную фазу. Однако, двигаясь вперёд, важно знать о различных версиях Python и о том, как их опробовать.
Типы версий Python
- Официальные релизы (Released versions). Обычно, в этом случае речь идёт о чём-то вроде Python 3.6, 3.7 или 3.8. В каждой из этих версий добавлены новые возможности. Поэтому, чтобы правильно оценивать функционал, нужно знать, какая именно версия релиза запущена. К примеру, f-строки (метод форматирования f-strings) ввели в Python 3.6. На более ранних версиях он не работает. Подобным образом, выражения присваивания доступны только начиная с Python 3.8.
- Разрабатываемые версии (Development versions). Сообщество Python постоянно работает над новыми версиями. Пользователям доступны разрабатываемые версии, отмеченные как альфа, бета и релиз-кандидат (release candidate).
- Реализации (Implementations). Python — язык, имеющий несколько вариантов дополнительных реализаций на основе вышедших официальных версий. Каждая из них содержит интерпретатор и соответствующие библиотеки. CPython — эталонная реализация Python, ее используют чаще всего. Тем не менее есть и другие реализации, ориентированные под специфические нужды. Среди них PyPy, IronPython, Jython, MicroPython и CircuitPython.
Версию используемого Python можно увидеть при запуске интерпретатора REPL. Можно также получить дополнительную информацию c помощью команды «sys.implementation».
>>> import sys >>> sys.implementation.name 'cpython' >>> sys.implementation.version sys.version_info(major=3, minor=9, micro=0, releaselevel='alpha', serial=1)
Как видно, код выполнен на первой альфа-версии CPython 3.9.
Традиционно для управления различными версиями Python используются инструменты вроде «pyenv» и «conda». В большинстве случаев их может заменить Docker. И он часто оказывается проще в использовании. Ниже мы практически покажем, как это сделать.
Использование Docker
Docker — мощная платформа для запуска контейнеров с предварительно упакованными приложениями. Она популярна в сфере упаковки и развёртывания приложений и микросервисов. В этом разделе мы сделаем краткий обзор основных шагов, которые нужны, чтобы начать работу с Docker.
Установка Docker
Docker доступен для всех основных ОС: Windows, macOS и Linux. Инструкции по установке Docker на конкретную систему можно найти в их официальных руководствах.
Если специфических требований нет, можно использовать ядро приложения — Docker Engine — в свободно распространяемой «общественной» редакции (Community Edition).
Запуск контейнеров
Работа Docker основана на двух базовых элементах — образах и контейнерах.
- Образ — автономный пакет, который может выполнить Docker.
- Контейнер — выполняемый образ с определённым состоянием (как пользовательский процесс).
Есть несколько репозиториев, содержащих предварительно собранные образы для Docker. Самым распространённым является Docker Hub — публичный репозиторий, который будет использоваться в данном руководстве по умолчанию.
Для примера установим образ «hello-world».
$ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:451ce787d12369c5df2a32c85e5a03d52cbcef6eb3586dd03075f3. Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. [ . Результат выполнения команды сокращён . ]
Первые строки показывают, что Docker скачал образ «hello-world» из репозитория Docker Hub. При запуске этого образа итоговый контейнер выводит на терминал сообщение «Hello from Docker!».
Создание собственных образов с помощью Dockerfile
С помощью Dockerfile можно создавать собственные образы. Это обычный текстовый файл, который описывает, как именно будет устанавливаться образ Docker.
Вот пример Dockerfile:
FROM ubuntu RUN apt update && apt install -y cowsay CMD ["/usr/games/cowsay", "Dockerfile это круто!"]
Файл Dockerfile состоит из перечня команд Docker. В приведённом выше фрагменте три шага:
- Строка 1 создаёт на базе существующего образа под названием «ubuntu» новый. Она выполнима вне зависимости от ОС, на которой запущен Docker.
- Строка 2 устанавливает программу под названием «cowsay».
- Строка 3 задаёт команду, которая сразу запускает «cowsay» при выполнении образа.
Создание Dockerfile начинается с правильного сохранения — под именем «Dockerfile» без какого-либо расширения.
Примечание. Создавать и запускать образы Linux можно на любой платформе. Поэтому такие образы, как «ubuntu», прекрасно подходят для разработки кросс-платформенных приложений. В то же время образ Windows запустится только на Windows, а образ macOS — только на macOS.
Далее создаём при помощи Dockerfile образ:
$ docker build -t cowsay
В результате последует множество сообщений, и будет создан образ. Опция «-t cowsay» задаёт «cowsay» в качестве его имени. Такие теги удобно использовать для отслеживания образов. Финальная точка в команде указывает текущий каталог в качестве контекста сборки для вашего образа. Это должен быть каталог, где находится ваш Dockerfile.
Теперь образ Docker можно выполнить:
$ docker run --rm cowsay _______________________ < Dockerfile это круто! >----------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
Опция « —rm » очистит контейнер после использования. Её применение позволяет не засорять систему уже ненужными контейнерами Docker.
Примечание. Вывести список ваших образов и контейнеров можно, используя команду «docker ps -a».
И образ, и контейнер имеют 12-символьный идентификатор «ID», который можно найти в результатах выполнения этих команд. Чтобы удалить образ или контейнер, можно использовать «docker rmi » либо «docker rm », указав их правильный идентификатор.
Возможности команды « docker » очень велики. За расширенной справкой можно обратиться к команде « docker —help » или к официальной документации.
Запуск Python в контейнере Docker
Сообщество Docker выпускает и поддерживает релизы в виде файлов Dockerfile для всех новых версий Python. Опробовать новые функции Python можно, воспользовавшись ими.
Кроме того, ключевые разработчики Python поддерживают образы Docker со всеми доступными на настоящий момент версиями. Далее расскажем, как запустить разные версии Python через Docker.
Запуск REPL
При запуске образов Python из Docker Hub, открывается интерпретатор REPL, в котором будет проходить дальнейшая работа. Чтобы открыть его в контейнере Python, следует выполнить следующую команду:
$ docker run -it --rm python:rc Python 3.8.0rc1 (default, Oct 2 2019, 23:30:03) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>>
Эта команда скачает образ «python:rc» с сайта Docker Hub, сделает на его основе контейнер и уже в нём выполнит команду «python».
Опции «-it» необходимы для интерактивного запуска контейнера. Тег «rc» означает «релиз-кандидат» и указывает на последнюю разрабатываемую бета-версию Python. В нашем случае это последний релиз-кандидат Python 3.8:
>>> import sys >>> f"" "sys.version_info[:] = (3, 8, 0, 'candidate', 1)"
При первом запуске контейнера Python на его скачивание может потребоваться некоторое время. Но дальнейшие вызовы уже будут происходить практически мгновенно. Выйти из интерпретатора REPL можно, введя « exit() ». Одновременно произойдёт выход из контейнера.
Примечание. Образы Python для Docker Hub поддерживаются в достаточно актуальном состоянии. По мере готовности, альфа- и бета-версии становятся доступны под тегом «rc».
Если нужно опробовать самые последние версии Python, то больше подойдёт образ от ключевых разработчиков:
$ docker run -it --rm quay.io/python-devs/ci-image:master
В хранилище Docker Hub можно найти полный список доступных образов Python. Последняя версия Python (стабильная) доступна как « python:latest », а свежая разрабатываемая версия — как « python:rc ».
Также можно запросить и точные версии. Например, «python:3.6.3» или «python:3.8.0b4», т.е. четвёртую бету Python 3.8. Можно даже запустить последнюю реализацию PyPy, используя теги вроде « pypy:latest ».
Настройка рабочей среды Python в Docker
Контейнер Docker представляет собой изолированное окружение. Таким образом, добавлять внутри контейнера виртуальную среду обычно не требуется. Вместо этого, для установки необходимых модулей Python в Docker можно запустить непосредственно систему управления пакетами pip.
Чтобы модифицировать контейнер включением дополнительных пакетов, используется Dockerfile. В приведенном ниже примере в образ Python 3.7.5 добавляются пакеты «parse» и «realpython-reader».
FROM python:3.7.5-slim RUN python -m pip install \ parse \ realpython-reader
Этот файл нужно сохранить под именем «Dockerfile». Тег «-slim» в первой строке показывает, что Dockerfile исходит из минимальной установки дистрибутива Debian. Этот тег задаёт существенно меньший размер образа Docker. Недостаток в том, что может потребоваться устанавливать дополнительные инструменты самому.
Помимо этого, в число тегов входят такие, как «-alpine» и «-windowsservercore». Подробную информацию о таких вариациях образа можно найти на Docker Hub.
Примечание. Если нужно использовать внутри контейнера Docker виртуальную среду, есть одно важное предостережение. Каждая команда «RUN» выполняется как отдельный процесс.
Типичная активация виртуальной среды в Dockerfile работать не будет. Вместо этого необходимо активировать виртуальную среду вручную, задав переменные среды «VIRTUAL_ENV» и «PATH»:
FROM python:3.7.5-slim # Настроить и активировать виртуальную среду ENV VIRTUAL_ENV "/venv" RUN python -m venv $VIRTUAL_ENV ENV PATH "$VIRTUAL_ENV/bin:$PATH" # Команды Python будут выполнены в виртуальной среде RUN python -m pip install \ parse \ realpython-reader
Чтобы собрать и выполнить свой Dockerfile, необходимо использовать следующие команды:
$ docker build -t rp . [ . Результат выполнения команды сокращён . ] $ docker run -it --rm rp
Образу будет задан тег «rp». Затем он будет использоваться для открытия созданного образа из сеанса интерпретатора REPL. Можно убедиться, что в контейнер установлен пакет «parse».
>>> import parse >>> parse.__version__ '1.12.1'
Также можно запускать контейнеры, выполняющие отдельные команды:
$ docker run --rm rp realpython The latest tutorials from Real Python (https://realpython.com/) 0 Run Python Versions in Docker: How to Try the Latest Python Release [ . Результат выполнения команды сокращён . ]
Вместо запуска интерпретатора REPL, внутри контейнера «rp» выполняется команда «realpython». Она выводит список последних руководств, опубликованных на специализированном сайте Real Python.
Запуск Python скрипта в Docker
В этом разделе мы расскажем, как запускать скрипты внутри Docker.
Прежде всего, следует сохранить приводимый пример скрипта у себя на компьютере в файл под названием «headlines.py».
# headlines.py import parse from reader import feed tutorial = feed.get_article(0) headlines = [ r.named["header"] for r in parse.findall("\n## \n", tutorial) ] print("\n".join(headlines))
Сначала этот скрипт скачивает последнее руководство с сайта Real Python. Затем он использует пакет «parse», чтобы найти все заголовки и вывести их на терминал.
Есть два основных способа запускать такие скрипты в контейнере Docker:
- Смонтировать локальный каталог в качестве тома (volume) контейнера Docker.
- Скопировать скрипт в нужный контейнер.
Первый вариант особенно удобен при тестировании, поскольку не нужно собирать образ Docker заново при каждом изменении скрипта. Для монтирования каталога в качестве тома, используйте опцию «-v»:
$ docker run --rm -v /home/realpython/code:/app rp python /app/headlines.py Understanding Python Versions and Docker Using Docker Running Python in a Docker Container Conclusion Further Reading
Если же скрипт будет использован на другом компьютере, нужно скопировать его внутрь контейнера. Это можно сделать, добавив в Dockerfile пару шагов:
FROM python:3.7.5-slim WORKDIR /usr/src/app RUN python -m pip install \ parse \ realpython-reader COPY headlines.py . CMD ["python", "headlines.py"]
Опцией «WORKDIR» внутри контейнера задается рабочий каталог, в котором будут выполняться команды. Можно скопировать файл «headlines.py» в этот каталог внутри контейнера, после чего изменить команду по умолчанию на выполнение «headlines.py» с помощью «python».
Соберите образ, как обычно. Остается лишь запустить контейнер:
$ docker build -t rp . [ . Результат выполнения команды сокращён . ] $ docker run --rm rp Understanding Python Versions and Docker Using Docker Running Python in a Docker Container Conclusion Further Reading
Обратите внимание, поскольку в Dockerfile была указана команда «CMD», при запуске контейнера сразу выполняется нужный скрипт.
Подробности о том, как создавать собственные файлы Dockerfile — в описании образа Python на сайте Docker Hub.
Заключение
Это руководство — краткое введение в работу с различными версиями Python при использовании Docker. Оно поможет быстро протестировать код и убедиться, что он совместим с последними версиями Python.
Изучив эту инструкцию, вы сможете:
- Запускать интерпретатор Python REPL при помощи Docker.
- Настраивать среду Python внутри образа Docker.
- Запускать внутри контейнеров Docker скрипты.
Поместить скрипт Python в Docker контейнер — дело пары минут. Теперь опробовать последнюю альфа-версию языка можно сразу после публикации.
Нужна надёжная база для разработки программных продуктов? Выбирайте виртуальные сервера от Eternalhost с технической поддержкой 24/7 и бесплатной защитой от DDoS!