Часть 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 нам потребуется сделать следующее:

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

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи ANCA IORDACHE: Containerized Python Development — Part 3

Предыдущая статьяПод покровом капустного листа: шаблон Декоратор
Следующая статьяШкола ленивого разработчика: ускоренный курс по созданию фрагментов кода в VS Code