OpenResty: перетворюємо NGINX на повноцінний сервер додатків

OpenResty: перетворюємо NGINX на повноцінний сервер додатківМи знову публікуємо розшифровку доповіді з конференції HighLoad ++ 2016 року, яка проходила у підмосковному Сколковому 7—8 листопада минулого року. Володимир Протасов розповідає, як розширити функціональність NGINX за допомогою OpenResty та Lua.

Всім привіт, мене звуть Володимир Протасов, я працюю у Parallels. Розповім трохи про себе. Три чверті свого життя займаюся тим, що пишу код. Став програмістом до мозку кісток у буквальному значенні: я іноді уві сні бачу код. Чверть життя — промислова технологія, написання коду, що йде прямо в продакшн. Код, який деякі з вас користуються, але не здогадуються про це.

Щоб ви розуміли, наскільки все було погано. Коли я був маленьким джуніором, я прийшов і мені видали такі двотерабайтні бази. Це зараз тут у всіх highload. Я ходив на конференції, питав: «Хлопці, розкажіть, у вас big data, все круто? Скільки у вас там бази? Мені відповідали: "У нас 100 гігабайт!" Я говорив: "Круто, 100 гігабайт!" А про себе думав, як би акуратно зберегти покерфейс. Думаєш, так, хлопці круті, а потім повертаєшся і колупаєшся з цими багатотерабайтними базами. І це — джуніор. Уявляєте, який це удар?

Я знаю більше ніж 20 мов програмування. Це те, що мені довелося розібратися в процесі роботи. Тобі видають код на Erlang, на C, на С++, на Lua, на Python, на Ruby, на чомусь ще, і тобі це треба все пиляти. Загалом довелося. Точну кількість порахувати так і не вдалося, але десь на 20 число загубилося.

Оскільки всі присутні знають, що таке Parallels і чим ми займаємося, говорити про те, які ми круті і що робимо, не буду. Розкажу тільки, що у нас 13 офісів у світі, більше 300 співробітників, розробка у Москві, Таллінні та на Мальті. За бажання можна взяти і переїхати на Мальту, якщо взимку холодно і треба погріти спинку.

Саме наш відділ пише на Python 2. Ми займаємося бізнесом і нам ніколи впроваджувати модні технології, тому ми страждаємо. У нас Django, тому що в ній є все, а зайве ми взяли і викинули. Також MySQL, Redis та NGINX. Ще в нас багато інших крутих штук. У нас є MongoDB, у нас кролики бігають, у нас чогось тільки немає — але це не моє, і я цим не займаюся.

OpenResty

Про себе я розповів. Давайте розберемося, про що я сьогодні говоритиму:

  • Що таке OpenResty та з чим його їдять?
  • Навіщо винаходити ще один велосипед, коли ми маємо Python, NodeJS, PHP, Go та інші круті штуки, якими всі задоволені?
  • І трошки прикладів із життя. Мені довелося сильно урізати доповідь, тому що вона у мене виходила на 3,5 години, тому прикладів буде мало.

OpenResty – це NGINX. Завдяки йому ми маємо повноцінний веб-сервер, який добре написаний, він працює швидко. Я думаю, більшість з нас використовують NGINX у продакшні. Всі ви знаєте, що він швидкий і крутий. У ньому зробили круте синхронне введення/виведення, тому нам не треба нічого велосипедити подібно до того, як у Python навелосипедили gevent. Gevent - крутий, здоровий, але якщо ви напишіть сишний код, і там щось піде не так, то з gevent ви збожеволієте це дебажити. У мене був досвід: знадобилося цілих два дні, щоб розібратися, що ж там пішло не так. Якби хтось до цього не покопався кілька тижнів, не знайшов би проблему, не написав в Інтернеті, і Google не знайшов би цього, то ми взагалі збожеволіли б.

У NGINX вже зроблено кешування та статичний контент. Вам не потрібно паритися, як це зробити по-людськи, щоб у вас десь не загальмувало, щоб ви десь дескриптори не втратили. Nginx дуже зручно деплоїти, вам не потрібно думати, що взяти - WSGI, PHP-FPM, Gunicorn, Unicorn. Nginx поставили, адмінам віддали, вони знають, як із цим працювати. Nginx структуровано опрацьовує запити. Я про це трохи пізніше розповім. Коротко у нього є фаза, коли він тільки прийняв запит, коли він обробив і коли дав контент користувачеві.

Nginx крутий, але є одна проблема: він недостатньо гнучкий навіть при всіх тих крутих фішках, що хлопці впхнули в конфіг, при тому, що можна налаштувати. Цієї сили не вистачає. Тому хлопці з Taobao колись давно, здається, років вісім тому вбудували туди Lua. Що він дає?

  • Розмір. Він маленький. LuaJIT дає десь 100-200 кілобайт оверхеда по пам'яті та мінімальний оверхід за продуктивністю.
  • Швидкість. Інтерпретатор LuaJIT у багатьох ситуаціях близький до C, у деяких ситуаціях він програє Java, у деяких обганяє її. Якийсь час він вважався state of art, найкрутішим JIT-компілятором. Зараз є крутіші, але вони дуже важкі, наприклад, той самий V8. Деякі JS-ні інтерпретатори та джавівський HotSpot у якихось точках швидше, але у якихось місцях все ще програють.
  • Простота в освоєнні. Якщо у вас, скажімо, кодова база на Perl, і ви не Booking, ви не знайдете перлових програмістів. Тому що їх немає, їх усіх забрали, а навчати їх довго та складно. Якщо ви хочете програмістів на чомусь іншому, можливо, їх також їх доведеться переучувати, або знаходити. У випадку Lua все просто. Lua навчається будь-яким джуніором за три дні. Мені знадобилося десь години дві, щоб розібратися. За дві години я вже писав код у продакшн. Десь через тиждень він просто у продакшн і поїхав.

В результаті це виглядає так:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Тут багато всього. В OpenResty зібрали купу модулів, як луашних, так і енджинсівських. І у вас усе готове — задеплоїло і працює.

Приклади

Досить лірики, переходимо до коду. Ось маленький Hello World:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Що тут є? це енджинсовський location. Ми не паримось, не пишемо свій роутинг, не беремо якийсь готовий — у нас вже є в NGINX, ми живемо добре та ліниво.

content_by_lua_block – це блок, який каже, що ми віддаємо контент за допомогою Lua-скрипту. Беремо енджинсівську змінну remote_addr і підсовуємо її в string.format. Це те саме, що й sprintf, Тільки Lua, тільки правильний. І віддаємо клієнтові.

В результаті це виглядатиме ось так:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Але повернемося до реального світу. У продакшн ніхто не деплоїть Hello World. У нас додаток зазвичай ходить в базу або ще кудись і більшу частину часу чекає на відповідь.

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Просто сидить та чекає. Це не дуже добре. Коли приходять 100.000 XNUMX користувачів, нам дуже важко. Тому давайте як приклад накидаємо простенький додаток. Шукатимемо картинки, наприклад, котиків. Тільки ми не будемо просто так шукати, ми розширюватимемо ключові слова і, якщо користувач пошукав «кошенята», ми йому знайдемо котиків, пушистиків та інше. Для початку нам потрібно отримати дані запиту на бекенді. Виглядає це так:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Два рядки дозволяють вам забрати GET-параметри, жодних складнощів. Далі ми, припустимо, з бази даних з табличкою за ключовим словом та розширенням отримуємо звичайним SQL-запитом цю інформацію. Все просто. Виглядає це так:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Підключаємо бібліотечку resty.mysql, яка у нас вже є у комплекті. Нам нічого не потрібно ставити, все готове. Вказуємо, як підключитися, і робимо SQL-запит:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Тут трошки страшно, але все працює. Тут 10 – це ліміт. Ми витягуємо 10 записів, ми ліниві, не хочемо більше показувати. У SQL про ліміт я забув.

Далі ми знаходимо картинки з усіх запитів. Ми збираємо пачку запитів та заповнюємо Lua-табличку, яка називається reqs, і робимо ngx.location.capture_multi.

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Всі ці запити йдуть у паралель і нам повертаються відповіді. Час роботи дорівнює часу відповіді найповільнішого. Якщо ми всі відстрілюються за 50 мілісекунд, і ми відправили сотню запитів, то відповідь у нас прийде за 50 мілісекунд.

Оскільки ми ліниві і не хочемо писати обробку HTTP та кешування, ми змусимо NGINX робити все за нас. Як ви бачили, там був запит на url/fetch, ось він:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Ми робимо простий proxy_pass, Вказуємо, куди закешувати, як це зробити, і у нас все працює.

Але цього недостатньо, нам ще потрібно віддати дані користувачеві. Найпростіша ідея - це все серилізувати в JSON, легко, у два рядки. Віддаємо Content-Type, віддаємо JSON.

Але є одна складність: користувач не хоче читати JSON. Треба залучати фронтендерів. Іноді нам не хочеться цього спочатку робити. Та й сеошники скажуть, що якщо ми картинки шукаємо, то їм все одно. А якщо ми їм якийсь контент видаємо, то вони скажуть, що у нас пошукові системи нічого не індексують.

Що з цим робити? Само собою, ми віддаватимемо користувачеві HTML. Генерувати ручками не комільфо, тому ми хочемо використовувати шаблони. Для цього є бібліотека lua-resty-template.

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Ви, мабуть, побачили три страшні літери OPM. OpenResty йде зі своїм пакетним менеджером, через який можна поставити ще купу різних модулів, зокрема, lua-resty-template. Це простий движок шаблонів, близький до Django templates. Там можна написати код та зробити підстановку змінних.

В результаті все буде виглядати приблизно так:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Ми взяли дані і срендерили шаблон знову ж таки в два рядки. Користувач щасливий, одержав котиків. Оскільки ми розширили запит, він на кошенят отримав ще й морського котика. Чи, може, він шукав саме його, але не міг правильно сформулювати свій запит.

Все круто, але ми ж у девелопменті, і не хочемо поки що користувачам показувати. Давайте зробимо авторизацію. Щоб це зробити, давайте подивимося, як NGINX обробляє запит у термінах OpenResty:

  • Перша фаза - доступ, коли користувач тільки прийшов, і ми на нього подивилися за заголовками, IP-адресою, за іншими даними. Можна одразу відрубати його, якщо він нам не сподобався. Це можна використовувати для авторизації, або якщо нам надходить дуже багато запитів, ми можемо їх легко рубати на цій фазі.
  • переписувати. Переписуємо якісь дані запиту.
  • зміст. Віддаємо контент користувачеві.
  • headers filter. Підмінюємо заголовки відповіді. Якщо ми використовували proxy_pass, ми можемо переписати якісь заголовки, перш ніж віддати користувачеві.
  • body filter. Можемо підмінити тіло.
  • журнал - Логування. Можна писати логі в еластичномувишукуванні без додаткового шару.

Наша авторизація виглядатиме приблизно так:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Ми додамо це у що location, Який ми описали до цього, і засунем туди такий код:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Ми дивимося, чи маємо cookie token. Якщо ні, то кидаємо на авторизацію. Користувачі хитрі та можуть здогадатися, що потрібно поставити cookie token. Тому ми ще покладемо її в Redis:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Код роботи з Redis дуже простий і не відрізняється від інших мов. При цьому все введення/висновок що там, що тут, він не блокуючий. Якщо пишете синхронний код, працює асинхронно. Приблизно як з gevent, тільки добре.

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Давайте зробимо саму авторизацію:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Говоримо, що потрібно читати тіло запиту. Отримуємо POST-аргументи, перевіряємо, що логін та пароль правильні. Якщо неправильні, то кидаємо на авторизацію. А якщо правильні, то записуємо token у Redis:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Не забуваємо поставити cookie, це теж робиться у два рядки:

OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Приклад простий, умоглядний. Ми звичайно ж не робитимемо сервіс, який показує людям котиків. Хоча хтось нас знає. Тому давайте пройдемося тому, що можна зробити в продакшні.

  • Мінімалістичний бекенд. Іноді нам потрібно в бекенд видати зовсім небагато даних: десь потрібно дату підставити, десь якийсь список вивести, сказати, скільки зараз користувачів на сайті, прикрутити лічильник чи статистику. Щось таке невелике. Мінімальні якісь шматочки можна дуже легко зробити. На цьому вийде швидко, легко та здорово.
  • Препроцесинг даних. Іноді нам хочеться вбудувати в нашу сторінку рекламу, причому цю рекламу беремо API-запитами. Таке дуже легко зробити саме тут. Ми не завантажуємо наш бекенд, який і так сидить важко працює. Можна взяти та зібрати тут. Ми можемо зліпити якісь JS або, навпаки, розліпити, щось перепроцесувати, перш ніж віддати користувачеві.
  • Фасад для мікросервісу. Це теж дуже добрий кейс, я його реалізовував. До цього я працював у компанії Tenzor, яка займається електронною звітністю, забезпечує звітність приблизно половини юросіб у країні. Ми зробили сервіс, там за допомогою цього ж механізму зроблено багато речей: маршрутизація, авторизація та інше.
    OpenResty можна використовувати як клей для ваших мікросервісів, який забезпечить єдиний доступ до всього та єдиний інтерфейс. Оскільки мікросервіси можуть бути написані так, що тут у вас Node.js, тут у вас PHP, тут Python, тут стоїть якась штука на Erlang, ми розуміємо, що не хочемо один і той же код скрізь переписувати. Тому OpenResty можна встромити на фронт.

  • Статистика та аналітика. Зазвичай NGINX стоїть на вході і всі запити йдуть через нього. Саме тут дуже зручно зібрати. Можна щось відразу порахувати і кудись закинути, наприклад, той же Elasticsearch, Logstash або просто записати в лог і потім кудись відправити.
  • Розраховані на багато користувачів системи. Наприклад, онлайн-ігри також дуже добре робити. Сьогодні в Кейптауні Олександр Гладиш розповідатиме, як швидко прототипувати розраховану на багато користувачів гру за допомогою OpenResty.
  • Фільтрування запитів (WAF). Зараз модно робити всякі web application firewall, є багато сервісів, що їх надають. За допомогою OpenResty можна зробити собі web application firewall, який просто і легко фільтруватиме запити за вашими вимогами. Якщо у вас Python, то ви розумієте, що PHP вам точно не інжектять, якщо ви, звичайно, з консолі його не спауніть ніде. Ви знаєте, що у вас є MySQL і Python. Напевно, тут можуть спробувати зробити якийсь directory traversal і щось заінджектити в базу. Тому можна відфільтрувати швидкі запити швидко і дешево відразу на фронті.
  • Спільнота. Оскільки OpenResty побудований на базі NGINX, то він має бонус — це NGINX-ком'юніті. Воно дуже велике, і пристойна частина питань, яка у вас виникне спочатку, вже вирішена спільнотою NGINX.

    Lua-розробники. Вчора я спілкувався з хлопцями, які прийшли на навчальний день HighLoad++ і почув, що Lua написаний тільки Tarantool. Це не так, на Lua багато чого написано. Приклади: OpenResty, XMPP-сервер Prosody, ігровий движок Love2D, Lua скриптується в Warcraft та інших місцях. Lua-розробників дуже багато, у них велике та чуйне ком'юніті. Усі мої питання щодо Lua вирішувалися протягом кількох годин. Коли пишеш у список розсилки, буквально за кілька хвилин вже купа відповідей, розписують що і як, що до чого. Це дуже здорово. На жаль, не скрізь таке добре душевне ком'юніті.
    По OpenResty є GitHub, там можна завести issue, якщо щось зламалося. Є список розсилки на Google Groups, де можна обговорити спільні питання, є розсилка китайською — чи, може, англійською ви не володієте, а знання китайської є.

Підсумки

  • Сподіваюся зміг донести, що OpenResty – це дуже зручний фреймворк, заточений під Інтернет.
  • У нього низький поріг входження, оскільки код схожий на те, на чому ми пишемо, мова досить проста і мінімалістична.
  • Він надає асинхронний I/O без коллбеків, ми не матимемо локшини, як ми можемо іноді написати в NodeJS.
  • У нього легкий деплой, оскільки нам потрібен тільки NGINX з потрібним модулем і наш код, і все працює.
  • Велика і чуйна спільнота.

Я не казав докладно як робиться маршрутизація, там виходила дуже довга оповідання.

Дякуємо за увагу!


Володимир Протасов - OpenResty: перетворюємо NGINX на повноцінний сервер додатків

Джерело: habr.com

Додати коментар або відгук