Пишем Dockerfile. Создаем и запускаем образ Docker с Flask приложением.

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

Пусть приложение состоит из трех файлов:

  • app.py
  • requirements.txt
  • Dockerfile

Для начала наполним логикой файл приложения и добавим нужные зависимости в файл окружения.

import os

from flask import Flask, jsonify, make_response


APP = Flask(__name__)
HOST = '0.0.0.0'
PORT = 5000
SERVICE_NAME = os.environ.get('SERVICE_NAME', 'application')


@APP.route('/hello/<user>')
def hello_user(user: str):
    return make_response(
        jsonify(
            {'message': f'Hello from {SERVICE_NAME}, {user}!'}
        ),
        200
    )


if __name__ == '__main__':
    APP.run(host=HOST, port=PORT)
flask==2.2.2

А теперь будем разбираться с Dockerfile. В файлах Dockerfile содержатся инструкции по созданию образа. С них, набранных заглавными буквами, начинаются строки этого файла. После инструкций идут их аргументы. Инструкции, при сборке образа, обрабатываются сверху вниз. Слои в итоговом образе создают только инструкции FROMRUNCOPY, и ADD. Другие инструкции что-то настраивают, описывают метаданные, или сообщают Docker о том, что во время выполнения контейнера нужно что-то сделать, например — открыть какой-то порт или выполнить какую-то команду.

Шпаргалка по инструкциям:

FROM — задаёт базовый (родительский) образ.
LABEL — описывает метаданные. Например — сведения о том, кто создал и поддерживает образ.
ENV — устанавливает постоянные переменные среды.
RUN — выполняет команду и создаёт слой образа. Используется для установки в контейнер пакетов.
COPY — копирует в контейнер файлы и папки.
ADD — копирует файлы и папки в контейнер, может распаковывать локальные .tar-файлы.
CMD — описывает команду с аргументами, которую нужно выполнить когда контейнер будет запущен. Аргументы могут быть переопределены при запуске контейнера. В файле может присутствовать лишь одна инструкция CMD.
WORKDIR — задаёт рабочую директорию для следующей инструкции.
ARG — задаёт переменные для передачи Docker во время сборки образа.
ENTRYPOINT — предоставляет команду с аргументами для вызова во время выполнения контейнера. Аргументы не переопределяются.
EXPOSE — указывает на необходимость открыть порт.
VOLUME — создаёт точку монтирования для работы с постоянным хранилищем.

В первую очередь с помощью инструкции FROM определим базовый образ python, указав имя ‘python’ и тэг образа после двоеточия. В нем просто легковесный python с минимумом библиотек на борту.

Теперь необходимо, чтобы Docker выполнил установку зависимостей. С помощью инструкции RUN мы создаем новый каталог /app внутри файловой системы контейнера. Копируем файл с зависимостями инструкцией COPY. Ей же копируем файл приложения. И инструкцией RUN устанавливаем зависимости. Далее укажем докеру рабочую директорию, в которой мы хотим находиться в момент запуска контейнера инструкцией WORKDIR, и пропишем команду запуска приложения инструкцией ENTRYPOINT.

В итоге наш Dockerfile выглядит так:

FROM python:3.7.9-slim-stretch

RUN mkdir /app

COPY requirements.txt /app/

COPY app.py /app/

RUN python -m pip install -r /app/requirements.txt

WORKDIR /app

ENTRYPOINT ["python", "app.py"]

Собираем образ

В терминале переходим в директорию с нашим приложением и Dockerfile’ом и вызываем команду:

docker build -t test_app .

Командой docker build мы собираем образ Docker с тэгом, указанным после флага -t.

После успешной сборки образа можно запустить контейнер, используя команду docker run. Укажите порт, на котором будет запущено приложение с помощью флага -p. Пример команды:

docker run -d -p 5000:5000 test_app

После этого Flask приложение будет доступно по адресу http://localhost:5000. Проверьте работу вашего приложения, открыв указанный URL в браузере. Не забудьте использовать эндпойнт «/hello/name», чтобы получить результат работы функции.

Чтобы остановить работу контейнера, узнайте его имя командой docker ps -a:

docker ps -a

И остановите командой docker stop:

docker stop practical_kare

По поводу расстановки команд в Dockerfile. При сборке образа Docker использует кэш, поэтому команды, которые используют часто изменяемые файлы, например мы постоянно вносим правки в файл app.py, лучше опускать ниже. А еще в нашем случае вместо инструкции «RUN mkdir /app» можно сразу поставить «WORKDIR /app» и директория будет создана автоматически. Но сделаем так в другой раз. А пока наш файл должен выглядеть так:

FROM python:3.7.9-slim-stretch

RUN mkdir /app

COPY requirements.txt /app/

RUN python -m pip install -r /app/requirements.txt

COPY app.py /app/

WORKDIR /app

ENTRYPOINT ["python", "app.py"]

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

Ну и последнее. Наше приложение способно получать данные из переменной окружения, т.е. снаружи, с помощью модуля os и команды «os.environ.get». Поэтому мы можем указать имя переменной SERVICE_NAME непосредственно при запуске контейнера с помощью флага -e:

docker run -d -p 5000:5000 -e "SERVICE_NAME=Vasya" test_app

Проверить можно без браузера, с помощью консольной утилиты curl:

curl http://localhost:5000/hello/artem
#{"message":"Hello from Vasya, artem!"}

А если добавить к команде запуска ключ «—restart=always», то образ будет сам запускаться при перезапуске системы:

docker run -d -p 5000:5000 -e --restart=always "SERVICE_NAME=Vasya" test_app