OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacjiPonownie publikujemy zapis wystąpienia z konferencji HighLoad++ 2016 roku, która odbyła się w podmoskiewskim Skolkowie 7–8 listopada zeszłego roku. Władimir Protasow opowiada, jak rozszerzyć funkcjonalność NGINX za pomocą OpenResty i Lua.

Cześć wszystkim, nazywam się Władimir Protasow, pracuję w Parallels. Opowiem trochę o sobie. Trzy czwarte swojego życia poświęciłem na pisanie kodu. Zostałem programistą do szpiku kości: czasami we śnie widzę kod. Jedną czwartą życia spędziłem na komercyjnej rozwój, pisząc kod, który trafia prosto do produkcji. Kod, którego niektórzy z Was używają, ale nie zdają sobie z tego sprawy.

Abyście zrozumieli, jak źle było. Kiedy byłem młodym junior developerem, przyszedłem, a dostano mi takie dwutera, terabajtowe bazy. Teraz wszyscy mówią o highload. Chodziłem na konferencje, pytałem: „Ludzie, opowiedzcie, macie big data, wszystko świetnie? Ile macie tam baz?” Odpowiadali: „Mamy 100 gigabajtów!” Ja mówiłem: „Świetnie, 100 gigabajtów!” A myślałem w sobie, jakby tu zachować pokerową twarz. Myślisz, że tak, ludzie są super, a potem wracasz i zmagasz się z tymi wielotera, terabajtowymi bazami. I to będąc juniorem. Wyobrażacie sobie, jaki to cios?

Znam ponad 20 języków programowania. To coś, co musiałem ogarnąć w trakcie pracy. Daje się kod w Erlangu, w C, w C++, w Luie, w Pythonie, w Rubym, w czymś jeszcze, i musisz cały ten kod robić. W sumie tak to wyszło. Dokładnej liczby nie udało się policzyć, ale gdzieś na 20 w liczbie się zgubiłem.

Ponieważ wszyscy obecni wiedzą, czym jest Parallels i czym się zajmujemy, nie będę mówił o tym, jacy jesteśmy wspaniali i co robimy. Powiem tylko, że mamy 13 biur na całym świecie, ponad 300 pracowników, a rozwój odbywa się w Moskwie, Tallinie i na Malcie. W razie chęci można również przenieść się na Maltę, jeśli zima jest zimna i trzeba się ogrzać.

Nasz dział konkretnie pisze w Pythonie 2. Zajmujemy się biznesem i nie mamy czasu na wdrażanie modnych technologii, dlatego cierpimy. Mamy Django, ponieważ ma wszystko, a nadmiarowe rzecz po prostu usunęliśmy. Używamy również MySQL, Redis i NGINX. Mamy wiele innych fajnych rozwiązań. Mamy MongoDB, mamy króliki biegające, mamy wszystko, co tylko można sobie wyobrazić – ale to nie jest moje, i tym się nie zajmuję.

OpenResty

Opowiedziałem o sobie. Sprawdźmy, o czym będę dziś mówił:

  • Czym jest OpenResty i jak go używać?
  • Po co wynajdywać jeszcze jeden rower, skoro mamy Pythona, NodeJS, PHP, Go i inne fajne technologie, którymi wszyscy są zadowoleni?
  • A teraz kilka przykładów z życia. Musiałem znacznie skrócić moją prezentację, ponieważ miałem aż 3,5 godziny materiału, więc przykładów będzie niewiele.

OpenResty to NGINX. Dzięki niemu mamy pełnoprawny serwer WWW, który jest dobrze napisany i działa szybko. Myślę, że większość z nas używa NGINX w produkcji. Wszyscy wiecie, że jest szybki i świetny. W nim zrobiono doskonałe, synchroniczne wejście/wyjście, więc nie musimy nic wymyślać na nowo, jak w Pythonie, gdzie zrobiono gevent. Gevent jest rewelacyjny, ale jeśli napiszesz kod w C i coś pójdzie nie tak, to z gevent zwariujesz przy debuggowaniu. Miałem taki przypadek: zajęło mi to dwa dni, aby dowiedzieć się, co poszło nie tak. Gdyby nikt przedtem nie pogrzebał przez kilka tygodni, nie znalazł problemu, nie napisał w Internecie i Google tego nie znalazł, to byśmy całkowicie oszaleli.

W NGINX już wdrożono caching oraz treści statyczne. Nie musisz się martwić, jak to zrobić poprawnie, aby nie spowolnić działania, ani nie stracić gdzieś kontrolerów. Nginx jest łatwy w wdrożeniu, nie musisz się zastanawiać, co wybrać — WSGI, PHP-FPM, Gunicorn, Unicorn. Nginx został zainstalowany, administracja przejęła, oni wiedzą, jak z tym pracować. Nginx strukturalnie przetwarza zapytania. Opowiem o tym trochę później. Krótko mówiąc, ma fazę, kiedy tylko przyjmuje zapytanie, kiedy je przetwarza, oraz kiedy zwraca treść użytkownikowi.

Nginx jest świetny, ale ma jeden problem: nie jest wystarczająco elastyczny, mimo wielu świetnych funkcji, które twórcy wprowadzili do konfiguracji. Tej mocy brakuje. Dlatego kilka lat temu, chyba osiem, twórcy z Taobao zintegrowali Lua. Co to daje?

  • Rozmiar. Jest mały. LuaJIT zużywa około 100-200 kilobajtów dodatkowej pamięci i minimalny dodatkowy narzut na wydajność.
  • Szybkość. Interpreter LuaJIT w wielu przypadkach zbliża się do C, w niektórych sytuacjach przegrywa z Javą, a w innych ją wyprzedza. Przez pewien czas uważany był za najnowocześniejszy, najlepszy kompilator JIT. Teraz są bardziej zaawansowane, ale są bardzo ciężkie, na przykład V8. Niektóre interpretery JavaScript i hotspot Javy w niektórych punktach są szybsze, ale w innych miejscach wciąż przegrywają.
  • Łatwość w nauce. Jeśli na przykład masz bazę kodu w Perl, a nie jesteś Bookingiem, nie znajdziesz programistów Perl. Ponieważ ich nie ma, wszyscy zostali zabrani, a nauka trwa długo i jest skomplikowana. Jeśli chcesz programistów w czymś innym, być może także musisz ich przeszkolić lub znaleźć. W przypadku Lua jest to proste. Lua mogą opanować wszyscy juniorzy w trzy dni. Mi zajęło to około dwóch godzin, aby się zorientować. Po dwóch godzinach już pisałem kod w produkcji. Gdzieś po tygodniu trafił prosto do produkcji.

W rezultacie wygląda to tak:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Jest tu wiele rzeczy. W OpenResty zebrano mnóstwo modułów, zarówno lua, jak i z silnika. I masz wszystko gotowe – wdrożone i działa.

Przykłady

Dość sentymentalnych rozmyślań, przechodzimy do kodu. Oto mały Hello World:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Co tu mamy? To lokalizacja z NGINX. Nie przejmujemy się, nie piszemy własnego routingu, nie korzystamy z gotowego — mamy to już w NGINX, żyjemy dobrze i leniwie.

content_by_lua_block – to blok, który mówi, że dostarczamy treść za pomocą skryptu Lua. Bierzemy zmienną z NGINX remote_addr i podstawiamy ją w string.format. To to samo, co sprintf, tylko na Lua, tylko poprawnie. I wysyłamy do klienta.

W rezultacie będzie to wyglądać tak:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Ale wróćmy do rzeczywistości. W produkcji nikt nie wypuszcza Hello World. Nasza aplikacja zazwyczaj łączy się z bazą danych lub gdzie indziej i przez większość czasu czeka na odpowiedź.

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Po prostu siedzi i czeka. To nie jest zbyt dobre. Kiedy przychodzi 100 000 użytkowników, mamy duże problemy. Dlatego stwórzmy prostą aplikację jako przykład. Będziemy szukać obrazków, na przykład kotów. Tylko nie będziemy szukać bez sensu, rozszerzymy słowa kluczowe i jeśli użytkownik poszukał 'kociąt', znajdziemy mu koty, puchatki i inne. Na początek musimy uzyskać dane żądania na backendzie. Wygląda to tak:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Dwie linijki pozwalają na pobranie parametrów GET, bez żadnych trudności. Następnie z bazy danych, przy użyciu tabeli z kluczowymi słowami i rozszerzeniem, pobieramy te informacje za pomocą standardowego zapytania SQL. To proste. Wygląda to tak:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Podłączamy bibliotekę resty.mysql, która już jest w naszym zestawie. Nie musimy nic instalować, wszystko jest gotowe. Określamy, jak się połączyć, i wykonujemy zapytanie SQL:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Tutaj jest trochę strasznie, ale wszystko działa. Liczba 10 to limit. Pobieramy 10 rekordów, jesteśmy leniwi i nie chcemy pokazywać więcej. Zapomniałem o limicie w SQL.

Następnie znajdujemy obrazy na podstawie wszystkich zapytań. Zbieramy zestaw zapytań i wypełniamy tabelę Lua, która nazywa się reqs, i wykonujemy ngx.location.capture_multi.

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Wszystkie te zapytania idą równolegle, a my otrzymujemy odpowiedzi. Czas wykonania równy jest czasowi odpowiedzi najwolniejszego. Jeśli wszystkie odpowiedzi przychodzą w ciągu 50 milisekund i wysłaliśmy sto zapytań, to otrzymamy odpowiedź w ciągu 50 milisekund.

Ponieważ jesteśmy leniwi i nie chcemy pisać obsługi HTTP i cachingu, zmusimy NGINX, aby wszystko robił za nas. Jak widzieliście, tam było zapytanie do url/fetch, oto ono:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Wykonujemy proste proxy_pass, wskazujemy, gdzie zbuforować, jak to zrobić, i wszystko działa.

Ale to nie wystarczy, musimy jeszcze zwrócić dane użytkownikowi. Najprostszy sposób to zserializować wszystko do JSON, łatwo, w dwóch linijkach. Zwracamy Content-Type, zwracamy JSON.

Ale jest jeden problem: użytkownik nie chce czytać JSON. Musimy zaangażować frontendowców. Czasami nie mamy na to ochoty na początku. A SEO-wcy powiedzą, że jeśli szukamy obrazków, to im to obojętne. Ale jeśli dajemy im jakiś content, powiedzą, że nasze treści nie są indeksowane przez wyszukiwarki.

Co z tym zrobić? Oczywiście, będziemy zwracać użytkownikowi HTML. Ręczne generowanie nie jest cool, dlatego chcemy używać szablonów. Do tego jest biblioteka lua-resty-template.

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Na pewno widziałeś trzy straszne litery OPM. OpenResty ma własnego menedżera pakietów, przez którego można zainstalować wiele różnych modułów, w tym lua-resty-template. To prosty silnik szablonów, bliski Django templates. Można tam napisać kod i wstawić zmienne.

W rezultacie wszystko będzie wyglądać mniej więcej tak:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Zebraliśmy dane i ponownie renderowaliśmy szablon w dwóch linijkach. Użytkownik jest zadowolony, otrzymał kotki. Ponieważ rozszerzyliśmy zapytanie, dostaje również morskiego lwiątka. Kto wie, może akurat go szukał, ale nie mógł poprawnie sformułować swojego zapytania.

Wszystko świetnie, ale jesteśmy w fazie dewelopmentu i nie chcemy, by użytkownicy to widzieli. Zróbmy autoryzację. Aby to zrobić, przyjrzyjmy się, jak NGINX przetwarza zapytanie w kontekście OpenResty:

  • Pierwsza faza — access, gdy użytkownik właśnie wszedł, a my go analizujemy po nagłówkach, po adresie IP oraz innych danych. Można go od razu zablokować, jeśli nam się nie podoba. Można to wykorzystać do autoryzacji lub, jeśli przychodzi zbyt wiele zapytań, łatwo je odrzucić na tym etapie.
  • rewrite. Przepisywanie danych zapytania.
  • content. Oferujemy zawartość użytkownikowi.
  • headers filter. Zmieniamy nagłówki odpowiedzi. Jeśli użyliśmy proxy_pass, możemy zmienić niektóre nagłówki przed oddaniem ich użytkownikowi.
  • body filter. Możemy zmienić ciało.
  • log — logowanie. Można zapisywać logi w elasticsearch bez dodatkowej warstwy.

Nasza autoryzacja będzie wyglądać mniej więcej tak:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Dodamy to do lokacji, którą wcześniej opisaliśmy, i umieścimy tam taki kod:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Sprawdzamy, czy mamy token cookie. Jeśli nie, to przekierowujemy na autoryzację. Użytkownicy są sprytni i mogą się domyślić, że muszą ustawić token cookie. Dlatego dodatkowo umieścimy go w Redis:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Kod do pracy z Redis jest bardzo prosty i nie różni się od innych języków. Cała operacja we/wy, zarówno tam, jak i tutaj, jest nieblokująca. Jeśli piszesz kod synchronny, to działa asynchronicznie. Trochę jak z gevent, tylko zrobione dobrze.

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Zróbmy zatem samą autoryzację:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Mówimy, że musimy odczytać ciało zapytania. Odbieramy argumenty POST, sprawdzamy, czy login i hasło są poprawne. Jeśli są błędne, to przekierowujemy na autoryzację. A jeśli poprawne, to zapisujemy token w Redis:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Nie zapominamy ustawić cookie, to także robimy w dwóch linijkach:

OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Przykład prosty, teoretyczny. Oczywiście nie zamierzamy tworzyć usługi, która pokazuje ludziom kotki. Choć kto wie. Dlatego przejdźmy do tego, co można zrobić w produkcji.

  • Minimalistyczny backend. Czasami potrzebujemy wydać w backendzie tylko kilka danych: gdzieś podstawić datę, gdzieś wyświetlić jakąś listę, powiedzieć, ilu użytkowników aktualnie jest na stronie, dodać licznik lub statystyki. Coś takiego niewielkiego. Minimalne fragmenty można bardzo łatwo zrealizować. Dzięki temu wszystko przebiega szybko, łatwo i skutecznie.
  • Przetwarzanie wstępne danych. Czasami chcemy zintegrować na naszej stronie reklamę, a tę reklamę pobieramy za pomocą zapytań API. To można bardzo łatwo zrealizować tutaj. Nie obciążamy naszego backendu, który i tak pracuje ciężko. Można wziąć i skonstruować wszystko tutaj. Możemy łączyć jakieś JS lub, w przeciwnym razie, rozdzielić, przetworzyć coś przed przekazaniem użytkownikowi.
  • Fasada dla mikrousługi. To także bardzo dobry przypadek, który zrealizowałem. Wcześniej pracowałem w firmie Tenzor, która zajmuje się elektronicznym raportowaniem, zapewniając raportowanie dla około połowy podmiotów prawnych w kraju. Stworzyliśmy serwis, w którym za pomocą tego samego mechanizmu zrealizowano wiele rzeczy: routing, autoryzację i inne.
    OpenResty może służyć jako spoiwo dla Twoich mikroserwisów, zapewniając jednolity dostęp do wszystkiego i wspólny interfejs. Ponieważ mikroserwisy mogą być napisane w różnych językach, jak Node.js, PHP, Python, czy też w Erlang, rozumiemy, że nie chcemy powielać tego samego kodu wszędzie. Dlatego OpenResty można zainstalować na froncie.
  • Statystyka i analityka. Zazwyczaj NGINX znajduje się na wejściu, a wszystkie zapytania przechodzą przez niego. W tym miejscu można bardzo wygodnie zbierać dane. Można coś od razu policzyć i wysłać do dowolnego miejsca, na przykład do Elasticsearch, Logstash lub po prostu zapisać w logu i później gdzieś wysłać.
  • Systemy wieloosobowe. Na przykład, świetnie sprawdzają się w przypadku gier online. Dziś w Kapsztadzie Aleksander Hładyś opowie, jak szybko prototypować grę wieloosobową przy użyciu OpenResty.
  • Filtracja zapytań (WAF). Obecnie popularne jest tworzenie różnych zapór aplikacji webowych, a na rynku jest wiele usług, które je oferują. Przy pomocy OpenResty można stworzyć zaporę aplikacji webowej, która w prosty sposób będzie filtrować zapytania zgodnie z Twoimi wymaganiami. Jeśli masz Python, to wiesz, że PHP na pewno nie zostanie zainkturowany, chyba że uruchomisz go z konsoli gdzieś indziej. Wiesz, że masz MySQL i Python. Być może ktoś spróbuje przeprowadzić atak typu directory traversal i coś zainkturować w bazie. Dlatego można szybko i tanio filtrować nietypowe zapytania bezpośrednio na froncie.
  • Społeczność. Ponieważ OpenResty oparty jest na NGINX, ma dodatkowy atut — to społeczność NGINX. Jest bardzo obszerna, a znaczna część pytań, które mogą się pojawić na początku, została już rozwiązana przez społeczność NGINX.

    programiści Lua. Wczoraj rozmawiałem z ludźmi, którzy przyszli na dzień szkoleniowy HighLoad++ i usłyszałem, że jedynie Tarantool jest napisany w Lua. To nieprawda, wiele rzeczy zostało stworzonych w Lua. Przykłady: OpenResty, serwer XMPP Prosody, silnik gier Love2D, skrypty Lua w Warcraft i w innych miejscach. Jest wielu deweloperów Lua, a ich społeczność jest duża i responsywna. Wszystkie moje pytania dotyczące Lua były rozwiązywane w ciągu kilku godzin. Kiedy piszesz na listę dyskusyjną, dosłownie w ciągu kilku minut masz już mnóstwo odpowiedzi z wyjaśnieniami, co i jak, co do czego. To naprawdę wspaniałe. Niestety, nie wszędzie jest taka przyjazna społeczność.
    Dla OpenResty dostępny jest GitHub, gdzie można zgłosić problem, jeśli coś się zepsuło. Jest lista dyskusyjna na Google Groups, gdzie można porozmawiać o ogólnych kwestiach, a także jest lista po chińsku — może nie znasz angielskiego, a znasz chiński.

Podsumowanie

  • Mam nadzieję, że udało mi się przekazać, że OpenResty to bardzo wygodny framework, idealny do aplikacji webowych.
  • Ma niski próg wejścia, ponieważ kod jest podobny do tego, na czym pracujemy, a język jest dość prosty i minimalistyczny.
  • Oferuje asynchroniczne I/O bez callbacków, więc nie będziemy mieć bałaganu, jak to czasami ma miejsce w NodeJS.
  • Jego wdrożenie jest łatwe, ponieważ potrzebujemy tylko NGINX z odpowiednim modułem i naszego kodu, i to wszystko działa od razu.
  • Duża i responsywna społeczność.

Nie opisałem szczegółowo, jak odbywa się routowanie, wyszedłby bardzo długi wykład.

Dziękuję za uwagę!

Odtwórz wideo

Włodzimierz Protasow - OpenResty: przekształcamy NGINX w pełnoprawny serwer aplikacji

Źródło: habr.com
Kup niezawodny hosting dla stron z ochroną przed DDoS, VPS VDS serwery 🔥 Kup niezawodny hosting dla stron z ochroną przed DDoS, VPS VDS serwery | ProHoster