Рубрики
Без рубрики

Базовый учебник: Использование Docker и Python

Как я пишу Dockerfiles для приложений python и почему.

Автор оригинала: Adam Mertz.

Это не предназначено для того, чтобы быть углубленным учебником по Docker или Flask. Оба инструмента имеют отличную документацию, и я настоятельно рекомендую вам ее прочитать. Краткое описание Docker таково: Docker позволяет вам объединить все зависимости вашего приложения в портативный контейнер, который может быть запущен на любой машине с контейнерной средой выполнения. Это позволяет упростить вашу инфраструктуру, установив только необходимые компоненты docker, и не беспокоиться об установке конкретной версии python/node/java. Они устанавливаются в образ контейнера. Образ контейнера определяется рядом директив в файле Dockerfile . Это Dockerfile то, что мы будем писать в этом посте. Я постараюсь объяснить, почему я пишу свой Dockerfile определенным образом, и если у вас есть какие-либо вопросы, не стесняйтесь их задавать.

Файл Dockerfile будет выглядеть следующим образом.

FROM python:3.9-slim-buster

RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir /code
WORKDIR /code

COPY requirements.txt .
RUN python3.9 -m pip install --no-cache-dir --upgrade \
    pip \
    setuptools \
    wheel
RUN python3.9 -m pip install --no-cache-dir \
    -r requirements.txt
COPY . .

EXPOSE 5000

CMD ["python3.9", "app.py"]

Первый like ИЗ python:3.9-slim-buster определяет, от какого образа мы наследуем. Я пошел с 3.9-slim-buster вместо 3.9-alpine . Хотя Alpine начинается с меньшего изображения (44,7 МБ против 114 МБ), иногда бывает трудно найти скомпилированные двоичные файлы. Это может привести к тому, что образу придется создавать сами двоичные файлы. Возможно, вам придется установить git и другие инструменты для достижения этой цели, что увеличит размер изображения. Кроме того, компиляция из исходного кода может занять некоторое время. Изображение slim-buster является хорошим промежуточным звеном. Я редко получаю больше 1 гигабайта размера изображения.

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

RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

build-essential даст нам компилятор C и другие вещи для установки пакетов Python с расширениями C. В нашем случае psycopg2 . Мы также установим libpq-dev . && rm -rf ... очистит apt-get для нас, чтобы минимизировать размер изображения. Это должно быть в той же директиве. Если бы вы написали это следующим образом.

RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev
RUN rm -rf /var/lib/apt/lists/*

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

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

RUN mkdir /code
WORKDIR /code

Затем мы вводим наш код и устанавливаем различные зависимости pip .

COPY requirements.txt .
RUN python3.9 -m pip install --no-cache-dir --upgrade \
    pip \
    setuptools \
    wheel
RUN python3.9 -m pip install --no-cache-dir \
    -r requirements.txt
COPY . .

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

COPY . .
RUN python3.9 -m pip install --no-cache-dir --upgrade \
    pip \
    setuptools \
    wheel
RUN python3.9 -m pip install --no-cache-dir \
    -r requirements.txt

Оба последующих слоя из директив RUN всегда будут перестраиваться и, таким образом, замедлять сборку. Реальность такова, что вам нужно обновить pip , setuptools и wheel только тогда, когда у вас есть новые зависимости для установки. То же самое с фактической установкой requirements.txt файл. Только копируя поверх requirements.txt файл мы будем делать только те трудоемкие шаги, когда наш requirements.txt файл действительно меняется. Оттуда мы просто копируем наш новый код, открываем порт и определяем команду, которая будет выполняться при запуске контейнера. Наконец, при установке зависимостей pip вы должны использовать флаг --no-cache-dir , так как он запрещает pip загружать пакеты в кэш в том случае, если вы хотите быстро установить их снова. Это не нужно в образе docker и, таким образом, занимает много места.

Способ записи файла только последние 3 слоя будут перестроены при последующих сборках изображения. Таким образом, восстановление изображения происходит довольно быстро. Продолжайте создавать образ docker обоими способами и посмотрите на разницу (возможно, вам придется внести изменения в код docker, чтобы получить изменение контрольной суммы). Кроме того, кэширование определяет, какие слои должны быть перемещены в хранилище образов Docker, а также какие слои должны быть перенесены на хост-машину, на которой выполняется приложение при развертывании. То, как мы написали этот Dockerfile , делает его таким, что снова только последние 3 слоя, которые являются крошечными, выталкиваются в репо и тянутся вниз к хосту приложения, таким образом быстро развертываясь. Это наилучший сценарий. Очевидно, что если вы пытаетесь протолкнуть изменения, которые также изменяют requirements.txt эти слои также нужно будет сдвинуть.

Наш app.py будет выглядеть так

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    # the /etc/hosts in docker containers doesn't like 127.0.0.1
    #  so use 0.0.0.0 instead.
    app.run(host="0.0.0.0")

И requirements.txt будет выглядеть так.

flask
psycopg2
sqlalchemy

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

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

$ docker build -t flask-docker .
$ docker run -it -p 5000:5000 flask-docker

Я надеюсь, что этот пост был полезен для понимания некоторых способов написания Dockerfiles для вашего приложения Python. В этом примере я использовал flask , но для Django он не особенно отличается . В принципе, изменится только директива CMD . Кроме того, для обоих вариантов вы, скорее всего, захотите использовать uwsgi или gunicorn для фактического запуска веб-сервера после развертывания с помощью NGinx или Apache в качестве обратного прокси-сервера.

Я нахожу раздел Dockerfiles best practices документации Docker действительно полезным.