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