Ни для кого не секрет, что уже пару лет назад появился такой сервер приложений как nginx unit, который умеет практически во все возможные бэкэнды: perl, pyhon, php, ruby, java, go, nodejs. Но, поскольку проект пока всё-таки молодой умеет она во всё это с некоторыми оговорками и нюансами. Зато он умеет запускать без танцев с бубнами разные приложения в одном сервере приложений и под разными пользователями. Ну то есть надо тебе запустить что-то перловое, питонное и рубёвое - пожалуйста. Надо тебе по разным путям разроутить запросы и повесить всё это на один порт - тоже пожалуйста. Надо тебе динамически менять количество воркеров под разные приложения - и это тоже из коробки работает. Даже статику из каталога на диске он умеет раздавать. А ещё он умеет перезапускать конкретное приложения не рестартуясь полностью. Добавлять и убавлять листенеры и переконфигурироваться на лету, просто засосав конфиг на json-е. И всё это прямо из коробки и by design.

Обычно, для своей скриптоты, я всё-таки пользую uwsgi. На каждое приложение по своему uwsgi. Это более-менее стабильный вариант, который способен работать надёжно даже в странных и сложных условиях. Так почему же nginx unit на этот раз?

А всё просто: alpine linux содержит в себе сконпиленный uwsgi только для python3. Вот такая засада. Зато nginx unit скомпилен как надо.

Итак, начнём. Пару приложений в нём удалось завести на раз-два, правда с некоторыми шероховатостями.

Одно из приложений на самом деле это даже не веб-приложение, у него появится api, но это в некотором неопределённом будущем, поэтому для него листенер я даже не запускал и в роутере не прописывал, какие запросы в него роутить. Unit и так тоже умеет.

Далее, был такой интересный глюк, связанный со start-stop-daemon и openrc - unit не хотел останавливаться. Правильным конфигом я это победил (видимо, он залипал на моменте останова, а супервизор его не добивал). Кроме того, если по какой-то причине остаётся контрольный сокет на диске, то unit не взлетает с фразой, что address already in use со ссылкой на сетевой адрес-порт для листенера.

Что касается конфига. Тут вообще интересная история - формально unit принимает исключительно корректно сформированный json. Он может быть "красивым", то есть с отступами, сносами строк итд, но должен быть валидным и без расширений, то есть комментарии, пустые строки и тп делать нельзя, хотя это и предусмотрено расширениями к стандарту json. У себя в /var/lib/unit/conf.json он хранит загруженный json в компактном виде (собственно, для разворачивания его в читабельный вид, я наваял перловый скрипт, который его разворачивает). Однако на этом приключение не заканчивается. Полностью готовый конфиг у меня скормить unit-у не получилось. Однако его можно скармливать посекционно. То есть можно отдельно описать вначале приложения и скормить их, потом листенеры, потом роуты, потом настройки юнита, потом статику. Дело в том, что конфиг ему заливается с помощью curl-а.

Ну, то есть мы берём описываем наши приложения

{
 "app1" : {
  "processes" : 3,
  "script" : "/var/lib/myapi/app1.psgi",
  "type" : "perl",
  "working_directory" : "/var/lib/myapi/"
 },
 "app2" : {
  "processes" : 2,
  "script" : "/var/lib/myapi/app2.psgi",
  "type" : "perl",
  "working_directory" : "/var/lib/myapi/"
 }
}

и засовываем их в определённый локейшен конфига:

curl -X PUT --data-binary @apps.json --unix-socket /run/unit.control.sock http://localhost/config/applications

ну и весь остальной конфиг:

{
 "access_log" : "/var/log/joyproxy.access.log",
 "applications" :{
  "app1" : {
   "processes" : 3,
   "script" : "/var/lib/myapi/app1.psgi",
   "type" : "perl",
   "working_directory" : "/var/lib/myapi/"
  },
  "app2" : {
   "processes" : 2,
   "script" : "/var/lib/myapi/app2.psgi",
   "type" : "perl",
   "working_directory" : "/var/lib/myapi/"
  }
 },
 "listeners" : {
  "0.0.0.0:80" : {
   "pass" : "applications/japp1"
  }
 },
 "settings" : {
  "http" : {
   "body_read_timeout" : 30,
   "header_read_timeout" : 30,
   "idle_timeout" : 120,
   "max_body_size" : 6291456,
   "send_timeout" : 30
  }
 }
}

собственно, в таком красивом виде его уже можно подсунуть nginx unit-у в /var/lib/unit/conf.json

Далее, какая есть неприятная особенность? Если uwsgi можно сконфигурить так, чтобы он заваливался, если приложение упало или не запустилось, то листенеры unit-а будут благополучно продолжать работать, даже если приложения больше нет. Это плохо, так как не будет возможности поднять упавшее приложение, и такой настройки, как у uwsgi, в unit-е я не заметил.

Кроме того, логгирование. Если uwsgi можно заставить писать какой тред приложения написал сообщение в лог, то как настроить такой же финт в unit-е я не знаю. Так, на большом количестве запросов, когда приложение логгирует свою активность через какой-нибудь warn(), в случае с uwsgi не надо даже напрягаться - всё естественным образом попадает в лог активности сервера приложений. А вот с unit-ом непонятно как это организовать.

Далее, исходя из документации, access_log можно настроить только в одном месте и для всего сервера приложений, но никак нельзя настроить для каждого приложения в отдельности, что странно. Логичней было бы, чтобы в секции роутинга настраивался бы и акцесс лог.

Вобщем, получается, что nginx unit-у есть куда расти. Для применения в проде, думаю, его ещё рановато применять, надо чтобы он оброс жирком - костыликами подо всякие нестандартные применения и случаи, полезными функциями и тд. То есть этот unit подойдёт для чего-то совсем некритичного, например, для экспериментов на localhost-е или вот для таких условных пет-проектов, доморощенного апи со странной функциональностью.

Да, чуть не забыл. А что же там с PSGI и перлом?

А всё вообще шикарно: есть perl-app-cpanm, который для работы хочет perl-local-lib, тогда можно будет вендорить перлятину в указанный каталог (и логично, что указанным будет какой-нибудь vendor_perl в каталоге с приложением и всё будет хорошо). Если perl-local-lib нету, то для обычного пользователя мерловые модули собираются в ~/perl5, а для рута... прямо в систему. Ну и конечно же мэйнтэйнеры alpine linux про этот пакет "забыли".

Собственно, по этой причине приходится подкладывать этот perl-local-lib в систему и всё начинает колоситься. Правда, помимо этого придётся поставить метапакет build-base и всякие zlib-dev, openssl-dev, sqlite-dev, libidn-dev и тд, от чего зависят перловые модули, но здесь уже всё очевидно из билд-лога cpanm.

Next Post