Docker – это широко распространенный и используемый ведущими ИТ-компаниями инструмент для создания, управления и защиты своих приложений.
Контейнеры, такие как Docker, позволяют разработчикам изолировать и запускать несколько приложений в одной операционной системе, а не выделять виртуальную машину для каждого приложения на сервере. Использование этих более легких контейнеров приводит к снижению затрат, лучшему использованию ресурсов и повышению производительности.
В этой статье мы напишем простое веб-приложение в Python с использованием Flask и подготовим его к «докеризации», после чего создадим образ Docker и развернем, его как в тестовой, так и в производственной среде.
Примечание. В этом руководстве предполагается, что на вашем компьютере установлен Docker.
Что такое Docker в Python?
Docker в Python – это инструмент, который позволяет разработчикам отправлять свои приложения (вместе с библиотеками и другими зависимостями), гарантируя, что они могут работать с одной и той же конфигурацией, независимо от среды, в которой они развернуты.
Это достигается путем изоляции приложений в отдельных контейнерах, которые, хотя и разделены контейнерами, совместно используют операционную систему и соответствующие библиотеки.
Docker можно разбить на:
- Docker Engine – инструмент для упаковки программного обеспечения, используемый для контейнеризации приложений.
- Docker Hub – инструмент для управления вашими контейнерными приложениями в облаке.
В чем польза?
Важно понимать важность и полезность контейнеров. Хотя они могут не иметь большого значения для отдельного приложения, развернутого на сервере, или в домашних проектах, контейнеры могут быть спасением, когда дело доходит до надежных и ресурсоемких приложений, особенно если они используют один и тот же сервер или если они развернуты во многих различных средах.
Сначала это было решено с помощью виртуальных машин, таких как VMWare и гипервизоры, хотя они оказались не оптимальными с точки зрения эффективности, скорости и переносимости.
Контейнеры Docker – это легкая альтернатива виртуальным машинам, в отличие от виртуальных машин, нам не нужно заранее выделять для них ОЗУ, ЦП или другие ресурсы, и нам не нужно загружать новую виртуальную машину для каждого приложения, поскольку мы работаем только с одной операционной системой.
Разработчикам не нужно обременять себя доставкой специальных версий программного обеспечения для различных сред, и они могут сосредоточиться на создании основной бизнес-логики, лежащей в основе приложения.
Настройка проекта
Flask – это микроструктура Python, используемая для создания как простых, так и сложных веб-приложений. Из-за простоты использования и настройки мы будем использовать его для нашего демонстрационного приложения.
Если у вас еще не установлен Flask, это легко сделать с помощью одной команды:
$ pip install flask
После установки Flask создайте папку проекта, например, с именем FlaskApp. В этой папке создайте базовый файл с именем вроде app.py.
В app.py импортируйте модуль Flask и создайте веб-приложение, используя следующее:
from flask import Flask app = Flask(__name__)`
Далее давайте определим основной маршрут / и соответствующий обработчик запросов:
@app.route("/")
def index():
return """
<h1>Python Flask in Docker!</h1>
<p>A sample web-app for running Flask inside Docker.</p>
"""
Наконец, запустим приложение, если скрипт вызывается как основная программа:
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0')
$ python3 app.py
Перейдите в браузере к http://localhost:5000/. Вам должно быть предложено сообщение «Dockerzing Python app using Flask»!

Как запустить приложение?
Чтобы запустить приложение с помощью Docker, мы должны создать контейнер со всеми используемыми в нем зависимостями – в нашем случае это только Flask. Для этого мы включим файл requirements.txt, содержащий необходимые зависимости, и создадим Dockerfile, который использует этот файл для создания образа.
Кроме того, когда мы запускаем контейнер, у нас должен быть доступ к портам HTTP, на которых работает приложение.
Подготовка
Включить зависимости в файл requirements.txt очень просто. Нам просто нужно указать имя и версию зависимости:
Flask==1.0.2
Затем нам нужно убедиться, что все файлы Python, необходимые для запуска нашего приложения, находятся внутри папки верхнего уровня, например, с именем app.
Также рекомендуется называть основную точку входа app.py, поскольку рекомендуется называть созданный в скрипте объект Flask как приложение, чтобы упростить развертывание.
docker-flask-tutorial
├── requirements.txt
├── Dockerfile
└── app
└── app.py
└── <other .py files>
Создание Dockerfile
Dockerfile – это, по сути, текстовый файл с четко определенными инструкциями по созданию образа Docker для нашего проекта.
Далее мы создадим образ Docker на основе Ubuntu 16.04 и Python 3.X:
FROM ubuntu:16.04
MAINTAINER Madhuri Koushik "[email protected]"
RUN apt-get update -y \
apt-get install -y python3-pip python3-dev
COPY ./requirements.txt /requirements.txt
WORKDIR /
RUN pip3 install -r requirements.txt
COPY . /
ENTRYPOINT [ "python3" ]
CMD [ "app/app.py" ]
Здесь есть несколько команд, которые заслуживают надлежащего объяснения:
- FROM – каждый Dockerfile начинается с ключевого слова FROM. Он используется для указания базового образа, из которого создается образ. Следующая строка предоставляет метаданные о создателе образа.
- RUN – мы можем добавить дополнительный контент к образу, выполнив задачи установки и сохранив результаты этих команд. Здесь мы просто обновляем информацию о пакете, устанавливаем python3 и pip. Мы используем pip во второй команде RUN, чтобы установить все пакеты в файле requirements.txt.
- COPY – команда COPY используется для копирования файлов и каталогов с хост-машины в контейнер во время процесса сборки. В этом случае мы копируем файлы приложения, включая файл requirements.txt.
- WORKDIR – устанавливает рабочий каталог в контейнере, который используется RUN, COPY и т.д.
- ENTRYPOINT – определяет точку входа в приложение.
- CMD – запускает файл app.py в каталоге приложения.
Как создаются образы?
Образы Docker создаются с помощью команды docker build. При построении образа Docker создает так называемые «слои». Каждый слой записывает изменения, произошедшие в результате выполнения команды в Dockerfile, и состояние изображения после выполнения команды.
Docker внутренне кэширует эти слои, поэтому при повторном построении образов ему нужно воссоздавать только те слои, которые были изменены. Например, как только он загружает базовый образ для ubuntu: 16.04, все последующие сборки одного и того же контейнера могут повторно использовать его, поскольку это не изменится. Однако во время каждой перестройки содержимое каталога приложения, вероятно, будет другим, и поэтому этот уровень будет каждый раз перестраиваться.
Всякий раз, когда какой-либо слой перестраивается, все следующие за ним слои в Dockerfile также должны быть перестроены. Об этом важно помнить при создании файлов Docker. Например, мы сначала КОПИРУЕМ файл requirements.txt и устанавливаем зависимости перед копированием остальной части приложения. В результате получается слой Docker, содержащий все зависимости. Этот уровень не нужно перестраивать, даже если другие файлы в приложении изменяются, пока нет новых зависимостей.
Таким образом, мы оптимизируем процесс сборки для нашего контейнера, отделяя установку pip от развертывания остальной части нашего приложения.
Создание образа
Теперь, когда наш Dockerfile готов и мы понимаем, как работает процесс сборки, давайте продолжим и создадим образ Docker для нашего приложения:
$ docker build -t docker-flask:latest .
Запуск приложения в режиме отладки с автоматическим перезапуском
Из-за преимуществ контейнеризации, описанных ранее, имеет смысл разрабатывать приложения, которые будут разворачиваться внутри самого контейнера. Это гарантирует, что с самого начала среда, в которой создается приложение, является чистой и, таким образом, исключает неожиданности во время доставки.
Однако при разработке приложения важно иметь быстрые циклы повторной сборки и тестирования для проверки каждого промежуточного шага во время разработки. С этой целью разработчики веб-приложений полагаются на средства автоматического перезапуска, предоставляемые такими фреймворками, как Flask. Это также можно использовать из контейнера.
Чтобы включить автоматический перезапуск, мы запускаем контейнер Docker, сопоставляющий наш каталог разработки с каталогом приложения в контейнере. Это означает, что Flask будет отслеживать файлы на хосте (через это сопоставление) на предмет любых изменений и автоматически перезапускать приложение при обнаружении любых изменений.
Кроме того, нам также необходимо перенаправить порты приложений из контейнера на хост. Это необходимо для того, чтобы браузер, работающий на хосте, мог получить доступ к приложению.
Для этого мы запускаем контейнер Docker с параметрами сопоставления томов и переадресации портов:
$ docker run --name flaskapp -v$PWD/app:/app -p5000:5000 docker-flask:latest
Это делает следующее:
- Запускает контейнер на основе образа docker-flask, который мы создали ранее.
- Имя этого контейнера установлено на flaskapp. Без опции —name Docker выбирает произвольное (и очень интересное) имя для контейнера. Явное указание имени поможет нам найти контейнер (для остановки и т.д.).
- Параметр -v подключает папку приложения на хосте к контейнеру.
- Параметр -p сопоставляет порт контейнера с хостом.
Теперь к приложению можно получить доступ по адресу http://localhost: 5000 или http://0.0.0.0:5000/:

Если мы внесем изменения в приложение во время работы контейнера и сохраним файл, Flask обнаружит изменения и перезапустит приложение:

Чтобы остановить контейнер, нажмите Ctrl — C и удалите контейнер, запустив docker rm flaskapp.
Запуск приложения в производственном режиме
Хотя запуск приложения напрямую с Flask достаточно хорош для разработки, нам нужно использовать более надежный метод развертывания для производства.
Обычно производственному веб-приложению Flask может потребоваться обрабатывать несколько параллельных подключений, и поэтому оно обычно развертывается на веб-сервере, совместимом с WSGI.
Популярной альтернативой является nginx + uwsgi, и в этом разделе мы увидим, как настроить наше веб-приложение для производства. Nginx – это веб-сервер с открытым исходным кодом, а uWSGI – это «быстрый самовосстанавливающийся контейнерный сервер приложений».
Во-первых, мы создаем фасад, который будет запускать наше приложение в режиме разработки или производства, и в зависимости от режима он выберет запуск nginx или Python напрямую.
Мы назовем этот файл launch.sh, и это будет простой скрипт команды. Этот файл основан на entry-point.sh:
#!/bin/bash
if [ ! -f /debug0 ]; then
touch /debug0
while getopts 'hd:' flag; do
case "${flag}" in
h)
echo "options:"
echo "-h show brief help"
echo "-d debug mode, no nginx or uwsgi, direct start with 'python3 app/app.py'"
exit 0
;;
d)
touch /debug1
;;
*)
break
;;
esac
done
fi
if [ -e /debug1 ]; then
echo "Running app in debug mode!"
python3 app/app.py
else
echo "Running app in production mode!"
nginx uwsgi --ini /app.ini
fi
Затем мы создаем файл конфигурации uWSGI для нашего приложения и конфигурацию nginx.
По сути, этот файл описывает точку входа нашего приложения в uWSGI и nginx:
[uwsgi] plugins = /usr/lib/uwsgi/plugins/python3 chdir = /app module = app:app uid = nginx gid = nginx socket = /run/uwsgiApp.sock pidfile = /run/.pid processes = 4 threads = 2
Наконец, мы модифицируем наш Dockerfile, чтобы включить в него nginx и uWSGI. Помимо установки nginx, uWSGI и uWSGI Python3 plugin, теперь он также копирует nginx.conf в соответствующее место и настраивает права пользователя, необходимые для запуска nginx.
Также Dockerfile ENTRYPOINT установлен для сценария оболочки, который помогает нам запускать контейнер в режиме отладки или в рабочем режиме:
FROM ubuntu:16.04
MAINTAINER Madhuri Koushik "[email protected]"
RUN apt-get update -y \
apt-get install -y python3-pip python3-dev \
apt-get install -y nginx uwsgi uwsgi-plugin-python3
COPY ./requirements.txt /requirements.txt
COPY ./nginx.conf /etc/nginx/nginx.conf
WORKDIR /
RUN pip3 install -r requirements.txt
COPY . /
RUN adduser --disabled-password --gecos '' nginx\
chown -R nginx:nginx /app \
chmod 777 /run/ -R \
chmod 777 /root/ -R
ENTRYPOINT [ "/bin/bash", "/launcher.sh"]
Теперь мы можем перестроить изображение:
$ docker build -t docker-flask:latest .
И запустите приложение с помощью nginx:
$ docker run -d --name flaskapp --restart=always -p 80:80 docker-flask:latest
Этот образ является самодостаточным, и во время развертывания необходимо указать только сопоставление портов. Это запустит и запустит команду в фоновом режиме. Чтобы остановить и удалить этот контейнер, выполните следующую команду:
$ docker stop flaskapp docker rm flaskapp
Кроме того, если нам нужно отладить или добавить функции, мы можем легко запустить контейнер в режиме отладки, монтируя нашу собственную версию исходного дерева:
$ docker run -it --name flaskapp -p 5000:5000 -v$PWD/app:/app docker-flask:latest -d
Управление внешними зависимостями
При отправке приложений в виде контейнеров необходимо помнить, что ответственность разработчика по управлению зависимостями возрастает. Помимо определения и указания правильных зависимостей и версий, они также несут ответственность за установку и настройку этих зависимостей в среде контейнера.
К счастью, requirements.txt – это простой механизм для определения зависимостей. К нему можно добавить любой пакет, доступный через pip.
Но опять же, каждый раз, когда файл requirements.txt изменяется, образ Docker необходимо перестраивать.
Установка зависимостей при запуске
Иногда может потребоваться установка дополнительных зависимостей во время запуска. Допустим, вы пробуете новый пакет во время разработки и не хотите каждый раз заново собирать образ Docker или хотите использовать последнюю доступную версию на момент запуска. Этого можно добиться, изменив средство запуска для запуска pip при запуске приложения.
Аналогичным образом мы можем установить дополнительные зависимости пакетов на уровне ОС. Давайте изменим launcher.sh:
#!/bin/bash
if [ ! -f /debug0 ]; then
touch /debug0
if [ -e requirements_os.txt ]; then
apt-get install -y $(cat requirements_os.txt)
fi
if [ -e requirements.txt ]; then
pip3 install -r requirements.txt
fi
while getopts 'hd' flag; do
case "${flag}" in
h)
echo "options:"
echo "-h show brief help"
echo "-d debug mode, no nginx or uwsgi, direct start with 'python3 app/app.py'"
exit 0
;;
d)
echo "Debug!"
touch /debug1
;;
esac
done
fi
if [ -e /debug1 ]; then
echo "Running app in debug mode!"
python3 app/app.py
else
echo "Running app in production mode!"
nginx uwsgi --ini /app.ini
fi
Теперь в файле requirements_os.txt мы можем указать список имен пакетов, разделенных пробелами, в одну строку, и они вместе с пакетами в файле requirements.txt будут установлены до запуска приложения.
Хотя это предоставляется для удобства во время разработки, установка зависимостей во время запуска не является хорошей практикой по нескольким причинам:
- Это побеждает одну из целей контейнеризации, которая заключается в исправлении и тестировании зависимостей, которые не изменяются из-за изменения среды развертывания.
- Это добавляет дополнительные накладные расходы при запуске приложения, что увеличивает время запуска контейнера.
- Получение зависимостей каждый раз при запуске приложения означает неэффективное использование сетевых ресурсов.
Заключение
В этой статье мы углубились в Docker, широко используемый инструмент для контейнеризации. Мы создали простое веб-приложение с помощью Flask, настраиваемого образа Docker на основе Ubuntu для запуска нашего веб-приложения в режиме разработки и производства.
Наконец, мы настроили развертывание для нашего веб-приложения с помощью nginx и uWSGI в контейнере Docker и изучили методы установки внешних зависимостей.
Контейнеризация – это мощная технология, которая позволяет быстро разрабатывать и развертывать приложения в облаке, и мы надеемся, что вы сможете применить полученные здесь знания в своих собственных приложениях.