artykuły

Produkcyjne przygody z serverless

Brak komentarzy

16 kwietnia wspólnie prowadziliśmy webinarium, na którym opowiadaliśmy o naszych produkcyjnych doświadczeniach z technologią serverless w chmurze AWS. Nagranie z tego wydarzenia możesz zobaczyć na YouTube:

Pytania i odpowiedzi z webinarium

Podczas prowadzenia transmisji dostaliśmy kilkanaście pytań, na które odpowiadaliśmy w trakcie trwania wydarzenia. Zebraliśmy większość z nich i przygotowaliśmy dokładne odpowiedzi.

Dlaczego cold start Node.js jest tak krótki, np. w porównaniu do Go? Czemu kompilowane języki działają wolniej niż interpretowane?

Mówimy tutaj tylko o cold starcie, czyli opóźnieniu w obsłudze pierwszego zapytania. W przypadku Javy i .NET-a wynika to z faktu wykorzystywania just-in-time compilation przez te platformy. Kompilacja do kodu maszynowego odbywa się dopiero podczas uruchomienia aplikacji. Jednak nie jesteśmy tutaj na straconej pozycji – w najbliższym czasie opublikujemy artykuł, opisujący możliwości skrócenia cold startów w .NET Core przy wykorzystaniu Lambda Layers i techniki pre-jittingu. Język Go, w przeciwieństwie do wcześniej wymienionych, jest kompilowany od razu do kodu maszynowego i w tym przypadku cold starty są zdecydowanie niższe. Są tylko nieznacznie gorsze od języków interpretowanych. Sytuacja zapewne ulegnie w przyszłości poprawie, ponieważ natywne wsparcie dla Go pojawiło się stosunkowo niedawno. Natomiast jeśli spojrzymy na ogólny performance, to zgodnie z przewidywaniami, języki kompilowane wypadają lepiej od interpretowanych. Więcej na ten temat można poczutać w artykule Comparing AWS Lambda performance of Node.js, Python, Java, C# and Go.

Kiedy mówimy o asynchronicznym, a kiedy o synchronicznym wywołaniu Lambdy? Od czego to zależy?

Lambdę można wywołać w dwóch trybach – request-response oraz event. Pierwszy jest nazywany synchronicznym wywołaniem, a drugi asynchronicznym. W pierwszym czekamy na odpowiedź z Lambdy, a w drugim wysyłamy tylko żądanie i odpowiedź nas nie interesuje – nie czekamy na nią. Każdą Lambdę można ręcznie wywołać w obu trybach, natomiast warto wiedzieć, że jeśli jakieś usługi AWS wywołują nasze Lambdy, to robią to zazwyczaj w sposób asynchroniczny.

“Rozgrzewacie” co X minut, żeby uniknąć cold startów. Czy nie taniej i prościej odejść od serverless do klasycznych serwisów które też można skalować z powodzeniem?

Jak zawsze można powiedzieć “to zależy”.
Zacznę od samego heatingu – jeżeli potrzebujemy rozgrzewać Lambdy, to można założyć, że architektura dobrana do rozwiązania danego problemu jest nieoptymalna. U nas heating wynikał przede wszystkim z używania Lambd w połączeniu z VPC, co powodowało cold-start na poziomie nawet kilkunastu sekund. Tak długie stawianie Lambdy wynikało z tego, że AWS musiał postawić ENI(Elastic Network Interface), czyli pomost do naszego VPC. Problem na szczęście już nie istnieje, a więcej można przeczytać tutaj.

Jeśli chodzi o samo skalowanie, to główna różnica rozwiązania opartego o AWS Lambda w porównaniu do aplikacji serwerowej jest taka, że Lambda pozwala na skalowanie poszczególnych kawałków aplikacji, aż do zera, a do tego robi to bardzo szybko. Wynika z tego, że takie rozwiazanie sprawdzi się przede wszystkim w momencie, gdy obciążenie aplikacji jest nierównomierne w czasie, czyli aplikacja działa tylko w określonych godzinach lub poszczególne elementy aplikacji skalują się w inny sposób. Dodatkowym atutem aplikacji serverless jest całkowity brak zarządzania infrastrukturą serwerową, wrzucamy kod i tyle. Dobrym przykładem użycia serverless’a będzie np. proces fakturowania, ponieważ proces ten uruchamiany jest raz w miesiącu, a do tego poszczególne elementy mogą być wykonywane równocześnie oraz skalować się horyzontalnie.
Jeżeli mamy do czynienia ze stałym i ciągłym obciążeniem całej aplikacji, to zdecydowanie wydajniej i przede wszystkim taniej będzie postawić np. kontener na stałe.

Dlaczego AWS, a nie Google Cloud Platform?

Którą chmurę wybrać? To zdecydowanie nie jest trywialne pytanie.
My swoją przygodę z chmurą zaczęliśmy od Azure, a na AWS przeszliśmy w 2016. Spowodowane było to przede wszystkim przewagą technologiczną AWS’a w tamtym czasie. W tej chwili za AWS’em w mojej opinii przemawia przede wszystkim to, że jest to główna usługa dostarczana przez Amazon, ale także elastyczność usług, ich liczba oraz popularność na rynku. Google Cloud stara się nadgonić i większość usług AWS ma już tam swoje odpowieniki, czasem są nawet tańsze. Niemniej AWS ma wszystko, czego aktualnie potrzebujemy, a jego tempo rozwoju i popularność pozwalają zakładać, że nie będzie potrzeby zmiany dostawcy w najbliższych latach.

Czy w przypadku RDS jest coś takiego jak PoolConteners? Czyli coś podobnego do ThreadPool lub JDBC pool w C3PO?

Sam RDS nie udostępnia możliwości zarządzania pulą połączeń, na tę potrzebę odpowiada nowa usługa, RDS Proxy. Pamiętajcie, że obecnie znajduje się w wersji preview. Więcej na jej temat można znaleźć tutaj.

Czy podejście zgodne z CQRS wspiera generowanie i zwracanie ID z bazy w ramach wyniku działania komendy?

Wszystko oczywiście zależy od implementacji. Środowisko jest podzielone pod tym względem i według “kanonicznego” podejścia nie powinniśmy zwracać niczego z komend. Z drugiej strony, jeżeli przyjrzymy się bliżej celowości tej zasady, to możemy ograniczyć się do reguły, że nie powinniśmy zwracać szeroko rozumianych domenowych danych. Z powodzeniem możemy zwracać informacje takie jak wynik komendy, informacje o ewentualnych błędach czy na przykład numer lub identyfikator nowej wersji modyfikowanego obiektu.

Czy faktycznie łączenie Lambdy + RDS jest aż tak częste w projektach? To nie są usługi, które działają dobrze razem; jeśli potrzebna jest relacyjna baza danych, to czemu jednak nie użyć Fargate?

Nasze doświadczenie pokazuje, że połączenie Lambda + RDS można z powodzeniem zastosować w pewnych przypadkach. Zagraniczne konferencje i prezentacje (również oficjalne prezentacje i materiały AWS) zdają się potwierdzać tę tezę. Ponadto, jedna z najnowszych usług o której była mowa podczas webinaru – RDS Proxy, powstała odpowiadając właśnie na taką konfigurację.

Fargate również bardzo dobrze odpowiada na potrzebę stworzenia aplikacji internetowej, która cechuje się wysoką skalowalnością i dostępnością. Każdy przypadek trzeba zawsze analizować indywidualnie i dopasować do wymagań. W naszych rozwiązaniach często wykorzystujemy Lambdy bez połączenia z API Gateway. W takiej konfiguracji odpada spora część kosztów rozwiązania. Dokładniejszą analizę porównania Fargate vs Lambda można znaleźć tutaj.

Zakładając replikację baz danych do odczytu, monitorujecie jakoś “eventual consistency”? AWS ma jakieś dodatkowe toole/narzędzia które wspierają taką weryfikację?

Opóźnienie replikacji można monitorować na dwa sposoby: bezpośrednio na bazie za pomocą zapytania lub poprzez CloudWatch Metrics. W CloudWatch Metrics dostępnych jest ponad 40 metryk dla klastra. Dokładna metryka, która w tym przypadku jest ważna to ReplicaLag.

Przykładowy wykres opóźnienia replikacji

Jeśli chcemy w momencie przekroczenia określonego progu wykonać akcję, trzeba użyć CloudWatch Alarms. Wystawia on informacje na SNS, którą można skonsumować na kilka sposobów, miedzy innymi wysyłając email. Kolejną opcją jest wywołanie lambdy, w której to już nasza fantazja (i limity Lambdy) są naszymi ograniczeniami.

Co jeżeli Lambdy przez RDS proxy odbiją się o brak spójności na bazie która jest zreplikowana?

Lambdy oczywiście mogą zapisywać i odczytywać dane, dlatego trzeba sobie zdawać sprawę, że eventual consistency jak najbardziej występuje. Jednak synchronizacją baz zajmuje się za nas AWS i chwali się bardzo niskim opóźnieniem, które nie powinno przekraczać 100ms. Sama obsługa takiej sytuacji w kodzie oczywiście pozostaje po stronie programisty.
Patrząc na architekturę RDS’a mamy dwa miejsca, gdzie powodujemy eventual consistency, by uzyskac inne benefity:

  • deployment instancji w różnych Availability Zonach i/lub Regionach – daje wysoką dostępność
  • horyzontalne skalowanie Read Replic – zwiększa wydajność odczytów

Samo RDS Proxy jest tylko usługą wspomagającą zarządzanie pulą połączeń do RDS’a i nie ma żadnego wpływu na spójność danych.

Jeśli przeniesiemy RDSa do publicznego subnetu, to gdzie wtedy stawiamy firewalla?

W takim wypadku za firewall posłuży Security Group’a dla RDS albo NACL dla subnetu. Dodatkowo trzeba pamiętać, że umieszczenie RDS’a w publicznym subnecie nie spowoduje nadania mu publicznego IP z automatu, bez którego RDS nadal nie będzie dostępny z publicznego internetu. By udostępnić bazę należy najpierw ustawić parametr Publicly Accessible na ‘Yes’. Dopiero wtedy reguły dostępu nałożone przez Security Group lub NACL wejdą w życie.

Jakie jest Wasze podejście do testowania aplikacji serverless? Używacie mocków/stubów? Testujecie w chmurze? SAM local? Co byście poradzili, żeby maksymalnie przyspieszyć testowanie, a jednocześnie móc testować działanie wielu usług razem (w tym faktyczne działanie uprawnień)?

Logikę aplikacji można z powodzeniem testować testami jednostkowymi. Oprócz tego wykorzystujemy testy integracyjne oraz komponentu, zgodnie z podziałem zaproponowanym przez Tobiasa Clemsona. Dodatkowe testy, które tworzymy to testy serwisu. Testują one funkcjonalności danego stacka od początku do końca, po wdrożeniu go na środowisko programistyczne lub testowe.

Testowanie uprawnień jest natomiast nieco bardziej złożone. SAM local pomaga w symulacji uruchamiania naszych funkcji, ale ciężko z jego pomocą odtworzyć bardziej złożone konfiguracje. Z naszej perspektywy najlepszym sposobem jest stworzenie własnej wersji danego stacka na programistycznym koncie. Żeby było to możliwe, musimy opisać dany serwis w odpowiedni sposób za pomocą CloudFormation. W przypadku aplikacji serverless koszty takich testów są znikome.

Czy jest jakiś prosty sposób, żeby logować z Lambdy do Elasticsearcha?

AWS dostarcza mechanizm subskrypcji, w którym pod log stream z naszej lambdy podpina się inną Lambdę, Kinesis Stream albo Kinesis Fire Hose. Te dalej filtrują/przetwarzają logi i przesyłają je do np. Elasticsearcha. Dokładny sposób użycia z Lambdą jest opisany na przykład w tym poście na Medium .

Skoro mieliście problemy z jedną globalną kolejką do wszystkich Lambd w przypadku asynchronicznych wywołań, to czy są jakieś możliwości wydzielania N kolejek dla grup poszczególnych Lambd?

Można zapytania zamiast bezpośrednio do Lambdy kierować do SQSa, a serwis Lambdy będzie konsumował dane bezpośrednio z takiej kolejki. Tutaj AWS opisuje jak to połączyć. Separacji można dokonać jeszcze uruchamiając grupy aplikacji na osobnych kontach AWS. Takie rozwiązanie to dosyć duży wysiłek ale daje pełną izolację zasobów. W zarządzaniu takimi kontami pomoże AWS Organizations, o którym pisaliśmy wcześniej.

Tags: , , , , ,

Powiązane artykuły

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Wypełnij to pole
Wypełnij to pole
Proszę wprowadzić prawidłowy adres email.
You need to agree with the terms to proceed

Menu