RU | EN

Развертывание Twisted-приложения

Вот twisted-приложение написано, отлажено и готово к сдаче заказчику. Как же правильно разворачивать twisted-приложение? Об этом я расскажу сегодня.
У разработчика twisted-приложение можно запускать отдельным runner-ом. Например, как это сделано в TwistedPythy: через twisted.internet.reactor. Для разработчика это удобно: запустил скрипт - сервер работает, логи видно тут же. Однако такой подход не приемлем в "боевой" обстановке: чтобы изменить параметры сервиса (в случае TwistedPythy, допустим, тип клиента, время задержки или номер порта) нужно править исходник. Далее, запуск/останов сервера должен быть неинтерактивным, ну и крайне желательно, чтобы он был стандартным для ОС (например, в случае Linux - через init-скрипт). Twisted предлагает несколько схем развертывания приложений.

Пару слов о сервисе

Небольшое отступление. В примере с TwistedPythy пока и слова не было о собственно "приложении". Формально, это лишь тестовый запуск определенной фабрики (factory). Чтобы говорить о приложении, необходимо понять, что же это такое. Приложение - это некий контейнер сервисов (под сервисом понимается то, что требует отдельного запуска и останова). Приложение может содержать несколько сервисов, сервисы можно объединять в коллекции. Пример с TwistedPythy:

from twisted.application import internet, service from TwistedPythy import proto, clients config = { 'listen-port' : 3000, # порт, который нужно слушать 'delay': 5, # задержка перед ответом, в сек. 'encoding': 'utf-8', # кодировка транспорта } # -- всё как раньше, определяем клиента и тип фабрики client = clients.UnicodeDummyClient() client.pause_time = config['delay'] factory = proto.AsyncUnicodePythyFactory(client, config['encoding']) # создаем приложение application = service.Application("TwistedPythy") # создаем один (в приложении м.б. несколько сервисов) сервис tp_service = internet.TCPServer(config['listen-port'], factory) # добавляем сервис в приложение tp_service.setServiceParent(application)

Однако теперь если просто запустить такой файл как Python-скрипт, ничего не произойдет. А так и должно быть. Приложение управляется только опосредованно. Через twistd. Так что теперь приложение запускается (для достижения аналогичного результата как в случае с простым runner-ом) следующим образом:

twistd -n -y twistedpythy_app.py

TAC

TAC, он же Twisted Application Configuration. Формально - это тот же самый Python-модуль с описанием twisted-приложения, что и в вышеприведенном примере. Однако, код, создающий сервис лучше выделить из TAC в отдельный модуль. Назовем его tap, почему именно так - скажу позже. А в TAC лишь опции и "пристыковка" сервиса к приложению. Пример TAC:

from twisted.application import service from TwistedPythy import tap config = { 'listen-port': 3000, 'delay': 5, 'encoding': 'utf-8', } app = service.Application("TwistedPythy") s = tap.makeService(config) s.setSeriveParent(application)

Запуск TAC-файла также производится при помощи twistd. Вот такой способ запуска уже более подходит для разворачивания приложений (например, можно написать init-скрипт для апуска/останова). Однако мне он не нравится тем, что код и опции всё же смешиваются, TAC ничем не отличается от обыкновенного Py-модуля в этом плане. Для этого случая есть TAP.

TAP


TAP (я называю "тапки"), он же Twisted Application Pickled. Существуют вариации TAS (Twisted Application Source) и TAX (Twisted Application XML). Здесь смысл такой: создаем плагин к mktap, при его помощи создаем приложение с конкретными опциями. И при помощи twistd запускаем это приложение. Теперь по шагам:

Делаем плагин

И вот тут пример finger из HOWTO - неправильный. А правильный документ - The Twisted Plugin System. Т.е. использование plugins.tml - устаревший и не рекомендуемый способ. Нужно использовать twisted.plugin. И здесь придется немного поразбираться.
Чтобы twisted "словил" плагин, нужно три условия:
  • Плагин должен реализовать IPlugin
  • Плагин должен реализовать интерфейс-требование данного приложения, в случае с mktap - это IServiceMaker
  • Он должен лежать в определенном месте. По умолчанию - это подкаталог twisted/plugins любого каталога из PYTHONPATH
Насчет последнего требования - это значит недостаточно сделать в каталоге со своим проектом каталог twisted/plugins. Нужно либо создать это в текущем каталоге (из которого будет запускаться mktap), либо в любом другом из PYTHONPATH.
А для первых двух требований есть "помощник": _tapHelper, который упрощает регистрацию плагина к mktap. Именно из-за этого "помощника" и нужно, чтобы модуль, в котором описано создание сервиса назывался tap.
Небольшое лирическое отступление. Про twisted.python.usage.Options. Это модуль для помощи в разборе опций и аргументов, переданных приложению через командную строку. Так вот, если выполнять определенные соглашения, то таким образом можно контролировать опции, которые будут переданы приложению через mktap. Соглашение очень простое - класс опций должен называться Options. Подробнее об использовании twisted.python.usage см. в docstring-ах к этому модулю
Итак, модуль-плагин к mktap выглядит так:

from twisted.application import internet, service from twisted.python import usage from TwistedPythy import proto, clients class Options(usage.Options): optParameters = ( ('listen-port', 'l', 3000, 'Port listening to'), ('encoding', 'e', 'utf-8', 'Transport encoding'), ('delay', 'd', 5, 'Delay in client before answer') def makeService(config): app = service.Application('TwistedPythy') client = clients.UnicodeDummyClient() client.pause_time = config['delay'] factory = proto.AsyncUnicodePythyFactory(client, config['encoding']) return internet.TCPServer(config['port'], factory)

Теперь создаем регистратор плагина, т.е. каталог twisted/plugins, в который кладем такой файл:

from twisted.scripts.mktap import _tapHelper TwistedPythy = _tapHelper( "Twited Pythy Demo server", # название сервиса "TwistedPythy.tap", # модуль, который и реализует собственно плагин "An Twisted Pythy example service", # описание "tpythy" # имя "тапки", которая в итоге будет получена )

Чтобы проверить, что все сделали правильно, запускаем mktap:

pythy@axcel:~/blog/twisted/twistedpythy_05$ mktap tpythy --help Usage: mktap [options] [command options] tpythy [options] Options: -p, --port= Port listening to [default: 3000] -e, --encoding= Transport encoding [default: utf-8] -d, --delay= Delay in client before answer [default: 5] --version --help Display this help and exit

Если появляется сообщение, что команда tpythy (т.е. имя "тапки") не найдена, это значит mktap "не подхватил" плагин.

Делаем и запускаем "тапки"

Если же всё нормально, то можно приступать к формированию "тапки". mktap понимает два набора опция: опции самого mktap и опции того приложения, чья "тапка" делается, в данном случае, TwistedPythy. Опции mktap передаются до имени "тапки", а опции TwistedPythy - после. Например так:

pythy@axcel:~/blog/twisted/twistedpythy_05$ mktap --uid=1000 --gid=1000 tpythy --delay=20 --listen-port=5000

В этом случае будет сформирована "тапка" формата по умолчанию (т.е. pickled) с именем tpythy.tap и опциями: uid/gid запущенного приложения будет 1000 (если root будет делать запуск/останов), задержка внутри клиента будет 20 секунд, приложение будет слушать 5000 порт. Всё. Эти опции "зашиты" в "тапку". Чтобы их изменить, нужно заново создавать "тапку" с измененными опциями.
Т.е. "тапка" должна создаваться при конфигурировании приложения. Однако скрипт tap2deb заворачивает в пакет только данную конкретную "тапку", что идеологически не верно. Я уж надумал писать правильный инструмент для заворачивания twisted-приложений в deb-пакет, да вовремя спросил разработчиков (на IRC-канале #twisted) о рекомендуемом способе развертывания twisted-приложений. И то, что они сказали внушает оптимизм…
Запускается "тапка" при помощи того же twistd, но с опцией -f вместо -y.

Twisted-2.5: жизнь без "тапок"

А сказали мне разработчики то, что в грядущем Twisted-2.5 идеологически верным способом является создание плагинов к mktap, но без промежуточного шага в виде "тапки". Т.е. если раньше было

mktap tpythy --encoding=utf-8; twistd -f tpythy.tap

То теперь так:

twistd tpythy --encoding=utf-8

И что гораздо лучше, это есть уже сейчас, в svn с ревизии 17992

Итог

  • TAC - не очень красивый, но очень гибкий способ, работает в twisted-2.x
  • TAP - более строгий, но противоречивый (с одной стороны более "правильный" чем TAC, с другой - более "кривой"), работает в twisted-2.x
  • TAPless на плагинах к mktap - строгий и самый "прямой" способ, но работает только в twisted-svn-r17992+
Код, как всегда - на code.google.com

Источники:
gorod-omsk.ru/blog/pythy/

Комментарии: