Bezserwerowe programowanie z AWS Lambda

Bartosz Nowak | Go to Cloud | 24.11.2021

W czasach rosnącej popularności architektury mikroserwisowej oprócz zaprojektowania i napisania aplikacji należy troszczyć się również o zarządzanie tymi aplikacjami, wybrać rozwiązania do orkiestracji, zarządzania warstwą sieciową itd. Wymaga to zazwyczaj zbudowania osobnego, wyspecjalizowanego zespołu. Aby tego uniknąć i ułatwić sobie nieco życie, można napisać aplikację w architekturze serverless. Tutaj z pomocą może przyjść Lambda od Amazon Web Services (AWS).

Czym jest AWS Lambda?

AWS Lambda to bezserwerowa funkcja jako usługa, czyli Function as a service (FaaS), kierowana zdarzeniami (event-driven). Lambda pozwala na tworzenie kodu i zarządzanie aplikacją bez konieczności utrzymywania własnych serwerów. Umożliwia tworzenie aplikacji w chmurze np. za pomocą popularnej metody ZIP deployment.

JPro 2021.11.11 logo - Bezserwerowe programowanie z AWS Lambda

Usługi Function as a Service zyskują na popularności, ponieważ użytkownik płaci tylko za czas przetwarzania danej funkcji. Na przykład w przypadku serwisu REST, który wyzwala Lambdę, opłata pobierana jest tylko za pojedyncze wywołanie naszego endpointu (takie podejście znane jest jako pay-as-you-go) przy zachowaniu dostępności, niezawodności i skalowalności.

AWS Lambda – jak to działa?

JPro 2021.11.11 graphic 1 1 - Bezserwerowe programowanie z AWS Lambda
AWS Lambda – architektura

Trigger

Zdarzenie (event), które wyzwala działanie Lambdy. Może to być 1 lub więcej zdarzeń pochodzących z różnych serwisów AWS, np.:

  • Wywołanie przez API-Gateway (wywołanie REST)
  • Dodanie nowego pliku, jego modyfikacja lub usunięcie na S3
  • Wiadomość z kolejki SQS

lub z systemów zewnętrznych, takich jak Atlassian, Symantec, PagerDuty i wiele innych. Dla każdego z eventów można by pokusić się o napisanie osobnego artykułu, więc chcących zgłębić temat zachęcam do lektury dokumentacji Lambda Amazon Web Services, gdzie wszystko jest szczegółowo opisane.

Warto tutaj wspomnieć o typie wywołania funkcji (InvocationType). Lambdę można wywołać w dwóch trybach:

Synchronicznym ten tryb można porównać do wywołania REST-owego, w którym wysyłane jest żądanie i otrzymywana jest odpowiedź, stąd nazywany jest typem request/response.

Asynchronicznym – w tym trybie żądanie do Lambdy wysyłane jest do kolejki zdarzeń. W przypadku błędu żądanie jest ponawiane maksymalnie dwukrotnie. AWS nie gwarantuje, że wszystkie wiadomości zostaną dostarczone, np. jeżeli aplikacja nie jest w stanie obsłużyć takiej liczby zdarzeń w kolejce. Z racji tego, że nie otrzymujemy tutaj informacji o poprawnym wykonaniu funkcji, przydatne może być zastosowanie DLQ (dead-letter-queue). W takim przypadku z pomocą przychodzi blok Destination. Ten tryb wywołania nosi nazwę event.

AWS Lambda Function

Czyli kod funkcji. Może on być napisany w jednym z języków obsługiwanych przez AWS. Obecnie mamy do wyboru takie języki jak:

  • .NET (C#)
  • Java
  • GO 
  • Node.js
  • Python
  • Ruby

Przy technologii takiej jak np. Node.js można skorzystać z wbudowanego edytora do pisania funkcji lub dostarczyć kod funkcji zbudowanej zewnętrznie w formie paczki w pliku ZIP. Istnieją różne wtyczki do narzędzi budowania, które ułatwią stworzenie paczki z kodem w odpowiedniej strukturze (jak np. Apache Maven Shade tworzący Uber-JAR).

Destination

Blok ten odpowiada za wysyłanie powiadomień o statusie wykonania funkcji. W przypadku poprawnego lub niepoprawnego wykonania funkcji AWS może wysłać powiadomienie na jeden z 3 dostępnych kanałów:

  • Topic SNS
  • Wiadomość na SQS
  • EventBridge

lub uruchomić kolejną funkcję Lambda.

Funkcjonalność ta zadziała jednak tylko wtedy, kiedy będziemy wywoływać funkcję Lambda asynchronicznie. W przypadku testowania Lambdy w UI AWS blok Destination nie zadziała, ponieważ żądanie jest wysyłane w trybie request/response, a nie w trybie event.

Infrastruktura, czyli co skrywa wewnątrz AWS Lambda?

AWS Lambda jest typem FaaS, a więc Amazon Web Services samodzielnie zarządza infrastrukturą. Sam kod funkcji przechowywany jest na AWS S3. Warto pamiętać o limitach, czyli o tym, ile kodu w GB można przechowywać w danym regionie chmurowym. Dla regionu europejskiego jest to 75 GB.

Jak wygląda cykl życia naszej aplikacji?

JPro 2021.11.11 graphic 2 1 - Bezserwerowe programowanie z AWS Lambda

W momencie wysyłania żądania do funkcji Lambda uruchamiana jest ona jako instancja naszej usługi w środowisku wewnętrznym. Jest ono podobne do środowiska EC2 (maszyny wirtualne na AWS). Natomiast użytkownik nie ma dostępu do tego środowiska. Gdy próbujemy uruchomić instancję, mechanizmy AWS sprawdzają, czy taka instancja nie została już uruchomiona, a jeśli jest uruchomiona – czy nie przetwarza żądania. Jeśli instancja jest zajęta, mechanizmy AWS Lambda tworzy nową instancję funkcji. W innym przypadku wykorzystuje obecną instancję lub jeśli instancja nie istnieje – tworzy nową. Jeśli instancja funkcji nie jest wykorzystywana przez dłuższy czas (AWS sam zarządza czasem), instancja funkcji jest usuwana.

Skalowalność – sky is the limit?

Przejdźmy teraz do tematu nierozerwalnie związanego z zagadnieniem chmury, czyli skalowalności. AWS Lambda, jak każda usługa, ma swoje limity. Jednym z nich jest limit aktywnych instancji w danym regionie. Dla regionów europejskich Amazon Web Services jest to 1000 aktywnych instancji, a należy pamiętać, że dotyczy to wszystkich funkcji Lambda, jakie posiadamy na naszym koncie w danym regionie. Aby mieć pewność, że minimalna liczba instancji zawsze w razie potrzeby zostanie uruchomiona, można zarezerwować liczbę instancji per funkcja w konfiguracji Lambdy. Dodatkowo warto wspomnieć, że każda instancja Lambdy ma dostęp do pamięci wewnętrznej (/tmp), która ma pojemność 512 MB i nie jest kasowana do momentu usunięcia instancji.

Parametry i konfiguracja Lambdy

Podczas tworzenia Lambdy należy podać dwa istotne parametry:

Memory – czyli dostępna pamięć RAM przypisana do instancji naszej usługi. W przypadku przekroczenia limitu pamięci instancja zostaje zatrzymana. Obecnie maksymalny limit pamięci to 10 GB.

Timeout – maksymalny czas wykonywania funkcji. Bardzo ważny parametr ograniczający koszty. Jeśli ustawiamy ten parametr np. na 30 sekund, a czas wykonania naszej funkcji będzie dłuższy niż czas odpowiedzi, funkcja ta zostanie zatrzymana. Maksymalny czas, jaki można ustawić, to 15 minut.

AWS Lambda – ważne elementy konfiguracji:

  • Zmienne środowiskowe – tutaj można ustawić zmienne, które będą przekazywane do naszej funkcji (np. namiary na bucket na AWS S3).
  • Permissions – ustawianie dostępów Lambdy, np. do S3, RDS AWS itd.
  • Ustawienia sieciowe – od Security Groups, przez podsieci, do VPC. Ważne, jeśli np. nasza baza danych jest w konkretnym Virtual Private Cloud.

O czym jeszcze warto pamiętać, tworząc Lambdę?

  • Wybór technologii, w której piszemy funkcję – ma to wpływ na wydajność aplikacji. W Lambdzie płacimy za czas trwania usługi i zużytą pamięć. Im „grubsza” będzie aplikacja, tym więcej wykorzysta pamięci, a ponadto dłuższy będzie cold-start. Oznacza to zatem, że przy pierwszym wywołaniu funkcji zapłacimy po prostu więcej. Dodatkowo aplikacja będzie miała większą bazę kodu, przez co zostanie zużyte więcej miejsca na S3. Spośród technologii, z których miałem okazję korzystać, Node.js dawał najlepszą wydajność.
  • Logowanie błędów – sam response z Lambdy nie zwróci nam zbyt użytecznej informacji, a monitorowanie i debugowanie Lambdy jest mocno ograniczone. Należy pamiętać, że logowanie na AWS też kosztuje, jeżeli zależy nam na bardziej zaawansowanym wykorzystaniu niż to, które umożliwia testowa wersja bezpłatna. Zachęcam do zapoznania się z cennikiem Amazon CloudWatch.
  • Tworzenie Lambdy za pomocą AWS CloudFormation – korzystanie z usługi AWS pozwoli zaoszczędzić czas na tworzeniu i konfigurowaniu zasobów i poszukiwaniu zależności, ponadto unikniemy wielu problemów związanych m.in. z dostępami czy VPC (jak konfiguracja VPC, aby dla Lambdy była widoczna baza danych lub inna usługa, której nie udostępniamy publicznie).

Koszty – czyli czy to się opłaca?

Odpowiedź brzmi: to zależy. W AWS Lambda płaci się za czas korzystania z funkcji oraz zużytą pamięć. Jednostką rozliczeniową stosowaną przez AWS jest Gigabajt na sekundę (GB/sec). Płaci się również za liczbę żądań, jednak należy wspomnieć, że w pakiecie startowym dla nowego konta (free-tier) dostajemy 1 milion żądań co miesiąc i 400 000 GB/sec, a więc całkiem sporo jak na bezpłatne testowanie rozwiązań chmury AWS.

A jak to wygląda, gdy trzeba zacząć płacić?

  • 1 milion żądań kosztuje 0,20 $
  • 1 GB/sec kosztuje 0,0000166667 $ (dla architektury x86)

Załóżmy, że mamy aplikację, która obsługuje 500 tys. żądań dziennie i nasza aplikacja potrzebuje 512 MB pamięci. Łączny koszt miesięczny to około 267 $. Jest to jedyny koszt, jaki poniesiemy, ponieważ nie musimy utrzymywać infrastruktury aplikacji (k8s, Service Mesh itd.)

Nie chciałbym tutaj porównywać kosztów do instancji serwerowej, np. EC2, bo to porównanie nie byłoby adekwatne. Zostawiam każdemu ocenę, jakie podejście w jego przypadku jest bardziej opłacalne.

AWS Lambda – zalety i wady

Plusy

  • Przy wykorzystaniu języka skryptowego mamy możliwość napisania szybko zoptymalizowanej funkcji
  • Oszczędność czasu przez brak obowiązku zarządzania infrastrukturą
  • Automatyczna skalowalność

Minusy

  • W zależności od wybranej technologii (np. Java ze Spring Boot) długi czas startu przy pierwszym wywołaniu (cold-start)
  • Ograniczenia w monitoringu i małe możliwości debugowania aplikacji
  • Nie nadaje się do aplikacji z dużym narzutem obliczeniowym ze względu na stosunkowo mały maksymalny czas wykonywania funkcji oraz limit pamięci RAM

Podsumowanie

Warto zastanowić się, czy AWS Lambda odciąży naszą infrastrukturę i pozwoli na zoptymalizowane kosztów. Podczas analizy może się okazać rozwiązaniem szytym na miarę. Oczywiście nie jest to narzędzie idealne, ma swoje limity czy ograniczenia i nie zawsze pozwoli osiągnąć zamierzony cel. Mam nadzieję, że udało mi się przybliżyć zagadnienie AWS Lambda wraz z jego plusami i minusami. Wszystkich zainteresowanych tym tematem zachęcam do dalszego zgłębiania i zapoznania się z rozwiązaniami, które pozwalają na wykorzystanie potencjału AWS Lambda – bibliotekami S3 czy SQS. Warto znać również rozwiązania pozwalające zminimalizować zjawisko „cold-startu”. Jest ich naprawdę dużo – na przykład AWS Serverless Java Container Spring, ale to już temat na osobny artykuł.