Контейнеризация в Python. Часть 3


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

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


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


    Поделиться статьей:


    Вернуться к статьям

    Комментарии

      Ничего не найдено.