Часть 1, Часть 2, Часть 3
Это последняя статья серии, посвящённой контейнеризации, в которой мы рассмотрели настройку и оптимизацию контейнеризованной среды разработки Python. В Части 1 мы изучили саму контейнеризацию Python-сервиса и наилучшие подходы к ней. В Части 2 было показано, как легко настраивать разные компоненты, необходимые Python-приложению и как без проблем управлять жизненным циклом всего проекта с помощью Docker Compose.
В этой заключительной части мы рассмотрим цикл разработки проекта и поподробнее обсудим обновление кода, а также научимся выполнять отладку сбоев контейнеризованных сервисов. Нашей целью будет проанализировать возможность ускорения этих повторяющихся фаз разработки, чтобы весь этот процесс стал идентичен выполняемому локально.
Помещённый в контейнер цикл разработки состоит из написания/обновления кода, сборки, выполнения и отладки.
Поскольку на стадиях сборки и запуска большую часть времени нам приходится просто ждать, мы заинтересованы, чтобы эти фазы проходили быстрее, чтобы иметь возможность сосредоточиться именно на написании кода и его отладке.
Теперь мы проанализируем, как оптимизировать фазу сборки. Эта фаза соответствует времени сборки образа при изменении исходного кода. Образ должен быть пересобран, чтобы получать обновления кода в контейнере до его запуска.
Тем не менее мы можем применять изменения в коде, не прибегая к повторной сборке образа. Это легко сделать, просто привязав локальную исходную директорию к её пути в контейнере, для чего мы обновляем Compose-файл:
docker-compose.yaml
...
app:
build: app
restart: always
volumes:
- ./app/src:/code
...
Так мы получаем прямой доступ к обновлённому коду и можем тем самым пропустить сборку образа, перезапустив контейнер для повторной загрузки процесса Python.
Более того, мы можем избежать перезапуска контейнера, если внутри него запустим процесс перезагрузки, наблюдающий за изменениями файла и перезапускающий процесс Python при их обнаружении. При этом нужно убедиться, что мы привязали исходный код в Compose-файле, как описывалось ранее.
В нашем примере мы используем фреймворк Flask, который в режиме отладки запускает очень удобный модуль, называемый reloader (перезагрузчик). Перезагрузчик наблюдает за файлами с исходным кодом и автоматически перезапускает сервер при обнаружении изменений. Для активации режима отладки нам нужно просто установить параметр отладки:
server.py
server.run(debug=True, host='0.0.0.0', port=5000)
Если мы проверим логи контейнера приложения, то увидим, что сервер flask работает в режиме отладки.
$ docker-compose logs app
Attaching to project_app_1
app_1 | * Serving Flask app "server" (lazy loading)
app_1 | * Environment: production
app_1 | WARNING: This is a development server. Do not use it in a production deployment.
app_1 | Use a production WSGI server instead.
app_1 | * Debug mode: on
app_1 | * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
app_1 | * Restarting with stat
app_1 | * Debugger is active!
app_1 | * Debugger PIN: 315-974-099
После обновления и сохранения исходного кода мы должны увидеть уведомление в логах и выполнить перезагрузку:
$ docker-compose logs app
Attaching to project_app_1
app_1 | * Serving Flask app "server" (lazy loading)
...
app_1 | * Debugger PIN: 315-974-099
app_1 | * Detected change in '/code/server.py', reloading
app_1 | * Restarting with stat
app_1 | * Debugger is active!
app_1 | * Debugger PIN: 315-974-099
Отладку можно выполнять двумя основными способами.
Первый является более старомодным и подразумевает размещение инструкций print
по всему коду для проверки значений объектов/переменных среды выполнения. Применить к контейнеризованным процессам его достаточно легко, и мы можем без проблем проверять вывод при помощи команды docker-compose logs
.
Второй способ уже более серьёзен и подразумевает применение отладчика. При работе с контейнеризованным процессом нам нужно запускать отладчик внутри контейнера и затем подключать к нему удалённый отладчик, чтобы иметь возможность инспектировать данные экземпляра.
В качестве примера мы возьмём приложение Flask. При выполнении в режиме отладки отдельно от модуля перезагрузки оно также включает интерактивный отладчик. Предположим, что мы обновляем код для вызова исключения. В этом случае сервис Flask вернёт подробный ответ с исключением:
Ещё один интересный случай — это интерактивная отладка, при которой мы размещаем в коде точки останова и инспектируем его в режиме реального времени. Для этого нам понадобится IDE с Python и поддержка удалённой отладки. Если для демонстрации отладки Python-кода, выполняемого в контейнерах, мы предпочтём VS Code, то для подключения удалённого отладчика напрямую из VS Code нам потребуется сделать следующее:
docker-compose.yaml
...
app:
build: app
restart: always
volumes:
- ./app/src:/code
ports:
- 5678:5678
...
2. Далее нужно импортировать модуль отладки в исходный код, чтобы он прослушивал порт, определённый в Compose-файле. Не забудьте также добавить его в файл зависимостей и пересобрать образ для сервисов приложения, чтобы установить пакет отладчика. Для этого примера мы выбрали пакет отладчика ptvsd
, поддерживаемый VS Code.
server.py
...
import ptvsd
ptvsd.enable_attach(address=('0.0.0.0', 5678))
...
requirements.txt
Flask==1.1.1
mysql-connector==2.2.9
ptvsd==4.3.2
3. Нужно также помнить, что для производимых нами в Compose-файле изменений нужно выполнять команду compose down
, чтобы удалить настройку текущих контейнеров, а затем команду docker-compose up
, чтобы произвести повторное развёртывание с новой конфигурацией Compose-файла.
4. В завершении нам нужно создать в VS Code конфигурацию ‘Remote Attach’ для запуска режима отладки.
Файл launch.json нашего проекта должен выглядеть так:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"port": 5678,
"host": "localhost",
"pathMappings": [
{
"localRoot": "${workspaceFolder}/app/src",
"remoteRoot": "/code"
}
]
}
]
}
Нужно убедиться, что мы обновили карту пути локально и в контейнерах.
После этого можно легко разместить точки останова в IDE, активировать режим отладки на основе созданной конфигурации и запустить выполнение кода до точки останова.
В этой серии статей мы рассмотрели, как быстро настраивать контейнеризованную разработку в Python, управлять жизненным циклом проекта, обновлять код, а также производить отладку Python-сервисов. Применение всего пройденного на практике позволит выполнять контейнеризованную разработку так же, как и локальную.
Перевод статьи ANCA IORDACHE: Containerized Python Development — Part 3
Комментарии