Fork me on GitHub
25/9/2006

Развертывание 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> [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

Комментарии

Все статьи