Как установить 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 содержатся инструкции по созданию образа. С них, набранных заглавными буквами, начинаются строки этого файла. После инструкций идут их аргументы. Инструкции, при сборке образа, обрабатываются сверху вниз. Слои в итоговом образе создают только инструкции FROM
, RUN
, COPY
, и 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