Как создать контейнер докер python
Перейти к содержимому

Как создать контейнер докер python

  • автор:

Контейнеризации Python-приложений

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

Шаг 1: Установка базового образа

Первым шагом является выбор базового образа для контейнера. Мы рекомендуем использовать минимальный базовый образ, например python:3.11-slim. Это позволит уменьшить размер контейнера и повысить его безопасность.

Шаг 2: Создание непривилегированного пользователя

Официальный образ контейнера Python не содержит предустановленного непривилегированного пользователя. Поэтому нам необходимо его создать. Мы создадим пользователя с UID 1000 и GID 1000.

RUN groupadd -g 1000 python && \ useradd -r -u 1000 -g python python

Шаг 3: Копирование и установка зависимостей

Если ваше приложение имеет какие-либо зависимости, то их необходимо скопировать в контейнер и установить. Это можно сделать с помощью инструкций COPY и RUN.

COPY requirements.txt . RUN pip install -r requirements.txt

Шаг 4: Настройка рабочего каталога

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

RUN mkdir /app && chown python:python /app WORKDIR /app

Шаг 5: Скопируйте код приложения

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

Шаг 6: Запуск от имени непривилегированного пользователя

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

Шаг 7: Задание команды входа

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

CMD [«python», «app.py»]

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

docker build -t my-python-app .

Для запуска контейнера можно воспользоваться следующей командой:

docker run -p 8080:80 my-python-app

Это приведет к запуску контейнера и открытию порта 8080 на хост-машине. Теперь вы можете получить доступ к своему приложению по адресу http://localhost:8080.

Ниже приведен пример Dockerfile для простого Python-приложения:

FROM python:3.11-slimRUN groupadd -g 1000 python && \ useradd -r -u 1000 -g python python COPY requirements.txt . RUN pip install -r requirements.txt RUN mkdir /app && chown python:python /app WORKDIR /app COPY app.py . USER 1000 CMD [«python», «app.py»]

Для создания образа Docker необходимо выполнить следующую команду:

docker build -t my-python-app .

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

docker run -p 8080:80 my-python-app

Это приведет к запуску контейнера и открытию порта 8080 на хост-машине. После этого вы сможете получить доступ к своему приложению по адресу http://localhost:8080.

Надеюсь, эта статья была полезной!

Вот несколько дополнительных советов по контейнеризации Python-приложений:

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

Используем Docker для python приложения

docker img

IT is Easy

Технология контейнеризации Docker – своеобразная священная корова последних лет в области Информационных Технологий. Именно с помощью Docker и различных систем управления для контейнеров получается реализовать самые высоконагруженные системы в мире. Также заметно упрощается процесс разработки приложений. В свое время уже писал в блоге статью из этой области – Docker в повседневной работе ITшника. Сейчас хотелось бы немного больше осветить практическую часть вопроса. А именно способ запуска python приложения в Docker контейнере на сервере. При этом подразумевается, что Вы не используете инфраструктуру наподобие Kubernetes или его аналогов, а просто хотите по мере необходимости использовать контейнеры от случая к случаю. Частенько бывает ситуация, что сервер для различных задач задействует возможности Python v2. При этом есть существенная необходимость запускать приложения, написанные на Python v3. Использование Docker позволяет элегантно решить эту задачу.

По большому счету для того, чтобы ощутить всю прелесть преимуществ использования Docker нужна хорошая система оркестрации. Именно при полной декомпозиции программных приложений под Docker с использованием систем управления мы получим отличное масштабирование, фишки CI/CD, отказоустойчивость. В данном примере всего этого у нас не будет. Мы будем разворачивать наше контейнеризированное приложение напрямую в операционной системе Linux. Это будет своеобразным примером, который позволит понять с практической точки зрения как же на самом деле работает Docker в реальной жизни.

Что делать

В первую очередь необходимо установить сам Docker, с помощью которого мы будем собирать образ и запускать контейнер с нашим приложением Python. Для разных дистрибутивов есть соотвествующие руководства на сайте docker.com. Так для Ubuntu можно почитать по этой ссылке. Вкратце последовательность команд в консоли Ubuntu Linux будет следующая:

$ sudo apt-get update $ sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - $ sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" $ sudo apt-get update $ sudo apt-get install docker-ce docker-ce-cli containerd.io

После проведенных манипуляций мы получим работающий Docker в системе. Это можно проверить с помощью команды docker version. Пример вывода данной команды представлен ниже.

$ docker version Client: Docker Engine - Community Version: 19.03.5 API version: 1.40 Go version: go1.12.12 Git commit: 633a0ea838 Built: Wed Nov 13 07:50:12 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.5 API version: 1.40 (minimum version 1.12) Go version: go1.12.12 Git commit: 633a0ea838 Built: Wed Nov 13 07:48:43 2019 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.2.10 GitCommit: b34a5c8af56e510852c35414db4c1f4fa6172339 runc: Version: 1.0.0-rc8+dev GitCommit: 3e425f80a8c931f88e6d94a8c831b9d5aa481657 docker-init: Version: 0.18.0 GitCommit: fec3683

Когда мы проверили, что установка Docker прошла успешна, пришло время для создания образа контейнера, в котором мы будем запускать наше приложение. В качестве приложения будем использовать telegram бот, создание которого я описывал в статье – Telegram боты и их создание. Создадим Dockerfile со следующим содержанием, который позволит на базе образа python из Docker Hub получить нужный нам контейнер.

FROM python:3 WORKDIR /usr/src/app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY bot.ini bot.ini COPY bot.py bot.py CMD [ "python", "./bot.py" ]

В файле requirements.txt содержится список модулей Python, которые будут установлены с помощью менджера пакетов pip в контейнер. Этот файл, а также прочие файлы, которые с помощью директивы COPY будут помещены в образ создаваемого контейнера должны находиться в той же директории, что и Dockerfile. Содержание requirements.txt для данного примера приведено ниже.

python-telegram-bot emoji

В файле bot.py у нас содержится непосредственно код telegram бота, который будет запускаться в контейнере. А в файлике bot.ini – первоначальные кофигурационные данные для запуска приложения. После того, как все вышеуказанные файлы будут готовы, можно приступать к сборке образа контейнера. Это делается с помощью следующей команды.

$ docker build -t python-docker-app .

Мы получили нужный образ, который можем запускать для работы в продакшене. Это можно делать через утилиту командной строки docker run, а можно и с помощью скрипта systemd. В случае со скриптом systemd мы получим сервис, которым можно будет полноценно управлять системными средствами. То есть, данное приложение можно будет запускать автоматически при старте сервера, а также следить за его постоянной работоспособностью. Пример такого скрипта приведен ниже.

[Unit] Description=Python Script in Docker Requires=docker.service After=docker.service [Service] Restart=always RestartSec=3 ExecStartPre=/bin/sh -c "/usr/bin/docker rm -f my-running-app 2> /dev/null || /bin/true" ExecStart=/usr/bin/docker run --rm --name my-running-app python-docker-app ExecStop=/usr/bin/docker stop my-running-app [Install] WantedBy=multi-user.target

Данный скрипт размещаем в директории /etc/systemd/system. Назовем его к примеру – docker-python.service. После того, как он будет готов, выполняем команду # systemctl daemon-reload. Теперь наше докеризированное приложение может запускаться, останавливаться и управляться с помощью системной утилиты service.

Резюме

Небольшой пример из данной статьи показывает реальный жизненный кейс о том, как запускать Python приложение в отдельном контейнере на обычном Linux сервере. Если Вы занимаетесь этим на постоянной основе, то разделив различные приложения по отдельным контейнерам, можно отлично использовать один общий виртуальный или физический сервер для целей их хостинга. При этом одно приложение никоим образом не будет влиять на другое. Вопросы синтаксиса при написании Dockerfile не рассматривал в данной статье. Также упор был сделан на запуск приложения, которое использует только один собственный контейнер. О том, как работать с многоконтейнерными приложениями, мы поговорим в следующих статьях этого блога.

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 пришел на смену виртуализации как более легковесный аналог, который можно запускать десятками на одной машине, потому что он переиспользует ядро оси.

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

Dockerize Python: создаём образ Docker из приложения на Python 1

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:$PATH
CMD [ "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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *