Azure Functions i architektura Serverless

Niedawno wprowadzony model Serverless zyskuje coraz większą popularność. Znajduje on zastosowanie przy realizacji wielu aplikacji biznesowej. Oddelegowanie usługodawcy całej pracy związanej z zarządzaniem serwerem stało się niezmiernie wygodne. Programiści mogą skupić się tylko i wyłącznie na pisaniu kodu. Na tapetę wchodzi Azure Functions.

Zanim przejdę do omówienia modelu Serverless, chciałbym przedstawić Ci fragment przykładowego projektu. Załóżmy, że mamy do zaimplementowania prostą aplikację webową, zajmującą się szyfrowaniem przesłanych informacji. Aplikacja operuje na wiadomościach tekstowych – użytkownik wprowadza treść, generuje klucz szyfrujący, a następnie klika w przycisk rozpoczynający proces szyfrowania. Ciężko oszacować czas trwania tego procesu, ponieważ jest on uzależniony od liczby i długości przesłanych wiadomości. Gdy cała zawartość jest już zaszyfrowana, do użytkownika zostaje wysyłany email wraz z linkiem, za pomocą którego może pobrać skompresowane archiwum z wszystkimi zaszyfrowanymi plikami. Pliki przechowywane są w magazynie aplikacji (blob storage). Ponadto po upływie 7 dniu od zakończenia procesu zaszyfrowania, wszystkie pliki, zapisane w magazynie danych, zostają automatycznie usunięte.

Opisany powyżej scenariusz został przedstawiony na poniższym schemacie:

Przykładowe rozwiązanie serverless w oparciu o Azure Functions
Przykładowe rozwiązanie serverless w oparciu o Azure Functions

Tego typu aplikacja odwiedzana jest przez użytkowników raz na jakiś czas – tylko gdy zachodzi potrzeba, żeby coś zaszyfrować. Załóżmy, że miesięczne użytkownicy przetwarzają około 5 000 plików.

Niestety w znaczącej większości dzisiejszych rozwiązań zmuszeni jesteśmy do ciągłego utrzymywania działającego serwera. Mimo, że nie jest on przez nas wykorzystywany, musimy ponosić koszty związane z jego utrzymaniem.

Wyobraź sobie, że nie ma czegoś takiego jak serwer. Piszesz fragment kodu, odpowiadający za realizację jakiegoś zadania, zamykasz go w funkcję, a następnie wrzucasz w chmurę. Funkcja ta może odpowiadać za: zaszyfrowanie wiadomości wybranym algorytmem, spakowanie wszystkich plików z katalogu do paczki *.zip lub nałożenie wybranego filtru na obraz graficzny. Wszystko może być zintegrowane z usługą OneDrive lub innym magazynem danych, w ramach której przechowywane są pliki.

Z pomocą przychodzi tutaj model Serverless. W tego typu rozwiązaniu płacimy tylko i wyłącznie za zasoby, które wykorzystujemy. Pojęcie serwera w ogóle tutaj nie istnieje. Można zadać sobie pytanie: „No dobrze, ale gdzie i w jaki sposób wykonywany jest mój kod?”

Słuszne pytanie. Serverless wcale nie oznacza, że nie ma fizycznego serwera, aczkolwiek z nazwy można by tak wywnioskować. Serwer cały czas znajduje się fizycznie w jednym z Microsoft’owych data center, jednakże jest on dla nas niedostępny. Wszystko co jest z nim związane zostało tylko i wyłącznie po stronie usługodawcy. Zajmujemy się tylko i wyłącznie pisaniem kodu poszczególnych funkcji w ramach komponentu Azure Functions.

Rozwiązanie to zaczęło powoli przyjmować nazwę FaaS (Function as a Service). Można powiedzieć, że nieco przypomina mikroserwisy. Pozbywamy się jednego, wielkiego monolitu, dekomponując aplikację na funkcje, realizujące kolejno pewien skomplikowany proces. Owe funkcje wyzwalane są za pomocą zdarzeń.

Dwa najważniejsze pojęcia związane z modelem Serverless to funkcja oraz zdarzenie.

W jaki sposób realizowany jest cały proces? Przyjrzyjmy do omówienia fragmentu przedstawionego powyżej scenariusza. Użytkownik uzupełnia formularz znajdujący się na statycznej stronie internetowej podając: adres email, klucz szyfrujący (ewentualnie może zostać on wygenerowany automatycznie), oraz treści do zaszyfrowania. Przykładowy request body:

Następnie użytkownik klika w przycisk rozpoczynający procedurę szyfrującą.

Przechodzimy do stworzenia kilku funkcji realizujących proces przedstawiony na powyższym diagramie. Zaczynamy od stworzenia Logic App w portalu Azure’owym, a następnie dodajemy funkcję wyzwalaną żądaniem HTTP o nazwie SendPlaintextsToEncryptionQueue.

SendPlaintextsToEncryptionQueue

Zadaniem tej funkcji jest wstępne przetworzenie dostarczonej przez request treści, a następnie wygenerowanie wiadomości dla Azure Queue Storage, przechowującą wszystkie informacje, niezbędne do poprawnego procesu szyfrowania. Każda wiadomość zawiera informacje na temat tego jaki klucz szyfrujący ma być wykorzystany, email użytkownika, na który zostanie wysłany link do paczki z szyfrogramami, tekst jawny oraz liczbę wszystkich przesłanych tekstów. Funkcja SendPlaintextsToEncryptionQueue wygląda następująco:

Wszystkie wygenerowane wiadomości odkładane są na kolejkę Encryption Queue. Następnie dodajemy kolejną funkcję o nazwie EncryptPlaintext wyzwalaną, gdy na wspomnianej kolejce zostanie odłożona nowa wiadomość:

EncryptPlaintext

Funkcja ta automatycznie pobiera wiadomości z kolejki, szyfruje otrzymaną treść, a następnie zapisuje szyfrogram do blob storage. Fragment funkcji:

Jeżeli wszystkie wiadomości zostały już zaszyfrowane, na kolejkę Package Queue zostanie wysłana wiadomość zezwalająca na uruchomienie kolejnej funkcji – CreateZipPackage, przenoszącej wszystkie szyfrogramy do jednego skompresowanego pliku *.zip. Na sam koniec wyzwalana jest jeszcze jedna funkcja, wysyłająca email do użytkownika aplikacji.

W tle działa dodatkowa funkcja, która wyzwalana jest każdego dnia o północy, w celu usunięcia z magazynu danych plików starszych niż jeden tydzień.

W ramach udostępnionego interfejsu dodajemy funkcje korzystając z gotowej biblioteki szablonów. Dostępne są wzorce reagujące na różnego typu zdarzenia, takie jak: dodanie nowego pliku na blob storage, wystąpienie się żądania HTTP, dodanie nowej wiadomości na kolejkę Queue Storage. Istnieje również możliwość uruchamiana funkcji zgodnie z zaplanowanym harmonogramem (tak jak w przypadku funkcji usuwającej przestarzałe pliki). Język C# nie jest jedynym narzędziem pozwalającym na implementację, można również skorzystać z wielu innych języków programowania takich jak: F#, JavaScript, PHP czy Python.

W ramach każdej funkcji tworzone są dwa pliki:

  • run.csx – plik z kodem funkcji np. w C#,
  • function.json – plik opisujący wszystkie powiązania funkcji np. definicję wyzwalaczy lub wyjścia. Dla funkcji EncryptPlaintext plik ten wygląda następująco:

Dla każdej funkcji można przypisać większą liczbę wejść oraz wyjść, ale wyzwalacz musi być tylko jeden. W celu przyjemniejszej konfiguracji powiązań należy kliknąć w zakładkę Integrate, znajdującą się po lewej stronie panelu zarządzania funkcjami.

Azure Functions vs WebJobs

Rozwiązanie to przypomina dostępne w ramach App Service Planu – Web Jobs. W czym więc różnica? Web Joby działają na tej samej maszynie wirtualnej, na której znajduje się aplikacja, dzięki czemu mają one dostęp do jej wszystkich zasobów. Zbyt rozbudowane zadania mogą negatywnie wpłynąć na wydajność aplikacji, a najtańszy App Service Plan dostarcza bardzo ograniczone możliwości pod kątem CPU, dysku i pamięci. W przypadku Web Jobów spada na nas dodatkowy nakład pracy związany z konfiguracją, zarządzania instancjami czy skalowaniem. Ponadto tego typu aplikacja musi być aktywna przez cały czas, za co oczywiście jesteśmy rozliczani.

Azure Functions to całkowicie osobny byt, działający z dala od aplikacji. Nie trzeba tutaj nic konfigurować, wystarczy napisać trochę kodu, który zareaguje na pewne zdarzenie, a następnie wykona jakąś pracę, której efekt zostanie przekazany dalej. Kod z funkcji jest o wiele bardziej wyabstrachowany. Piszemy lekkie i proste funkcje, skupiając się tylko na tym, co spełnia z góry narzucone warunki biznesowe, eliminujący przy tym całą masę kodu opakowującego. W przeciwieństwie do Web Jobs nie jest nam potrzebny ciągle działający serwer. Mamy możliwośc wyboru modelu PayAsYouGo, w którym płacimy tylko w momencie, kiedy nasz kod jest uruchamiany. Nasłuchiwanie na zdarzenia, sprawdzanie stanu kolejki, podgląd Azure Storage – z tego nie jesteśmy rozliczani. Azure Functions automatycznie dobierają liczbę serwerów na których funkcja jest uruchamiana tak, aby wynik funkcji był dostępny w akceptowalnym czasie.

Co wybrać?

Na koniec pozostaje pytanie „Co wybrać?” – WebJobs czy Azure Functions? Odpowiem standardowo, jak na informatyka przystało, to zależy! Jeżeli Twoja funkcja w dużej mierze będzie wykorzystywać komponenty i zasoby dostępne w ramach aplikacji webowej, a tym samym nie będą to mocno obciążające funkcje, wybrałbym Web Jobs. Jeżeli zaś jesteś w stanie wydzielić kawałek niezależnego kodu, odpowiadającego za naprawdę złożone obliczenia, a ponadto będą one uruchamiane raz na jakiś czas, wybierz Azure Functions.