Lambda Internals - Część 2: Going Deeper

Odkrywanie bibliotek wykonawczych Lambda AWS

Zdjęcie Jim Beaudoin

Programowanie bez użycia serwera jest po prostu najlepsze. Kliknij dwukrotnie, prześlij kod i gotowe, prawda? Większość ludzi jest więcej niż zadowolona z tego powodu. Jeśli nie jesteś większością ludzi, i chcesz zbadać Lambdę, ten artykuł jest właśnie dla Ciebie.

W poprzednim artykule dostaliśmy powłokę do kontenera Lambda, pobraliśmy środowisko wykonawcze Lambda i odkryliśmy jego komponenty:

  • bootstrap.py - kod python owijający nasz moduł obsługi.
  • awslambda / runtime.so - obiekt współdzielony zgodny ze Pythonem bootstrap.py używa go do, cóż, prawie wszystkiego.
  • liblambda * .so - Z kolei runtime.so używa innych wspólnych obiektów. Skoncentrujemy się na liblambdaruntime.so, odpowiedzialnym za ciężkie podnoszenie w zarządzaniu logiką Lambda.

Mieliśmy też trochę zabawy z bootstrap.py. Tym razem zakasamy rękawy i zanurzymy się w bibliotekach binarnych środowiska wykonawczego Lambda. Zbadamy system rozliczeniowy Lambdy i (alert spoilera) bawimy się zabawą z limitami czasu Lambda.

„Och, miejsca, do których pojedziesz! Można się dobrze bawić! Są punkty do zdobycia. Są gry do wygrania. ”- Dr. Seuss. Zdjęcie Joshua Earle

Odkrywanie bibliotek

Biblioteki (liblambda * .so) są kompilowane z symbolami, dzięki czemu można się wiele dowiedzieć o bibliotekach, przeglądając nazwy symboli. Ponadto runtime.so odsłania wiele z tych funkcji, importując je i pakując, więc skrypt Python (w naszym przypadku bootstrap.py) może z nich korzystać. Jak wygodnie!

Lista funkcji częściowych z demontażu liblambdaruntime.so. Bogu dzięki za symbole.

Jedną z rzeczy, które początkowo naprawdę chciałem sprawdzić, były kulisy systemu rozliczeniowego Lambdy, a po prostu patrząc na nazwy funkcji miałem kilka eksperymentów, które chciałem wypróbować. Ale najpierw - porozmawiajmy trochę o rozliczeniach Lambda.

Lambda Billing

Lambda ma model cenowy oparty na czasie i bez zagłębiania się w wszystkie szczegóły, jego sedno jest takie, im dłużej trwa praca z Lambda, tym więcej płacisz. Przywołując Lambdę, możesz łatwo zauważyć jej początek i koniec w Logach CloudWatch, a także czas jej trwania i okres rozliczeniowy.

CloudWatch loguje dla Lambda. Możesz zobaczyć zarówno czas trwania Lambdy, jak i okres rozliczeniowy

Istnieje jednak bardziej skomplikowany scenariusz. Rozważ następującą lambdę:

W typowym przebiegu czas trwania tej sondy lambda powinien być niewielki (naliczany czas trwania powinien prawie zawsze wynosić 100 ms). Ale co dzieje się przy pierwszym wywołaniu? Lub na zimne starty (gdzie moduł jest ponownie importowany)?

Lambda rejestruje, kiedy nastąpił zimny start. Czas trwania jest znacznie dłuższy niż zwykłe wywołanie

Testy empiryczne pokazują, że czas trwania pierwszego wywołania Lambda (lub zimnego startu) zawiera czas inicjalizacji. Chciałem jednak sprawdzić, jak Lambda to implementuje.

Importowanie bibliotek

W bootstrap.py są wywołania następujących funkcji, importowanych z bibliotek binarnych:

  • lambda_runtime.receive_start () lub lambda_runtime.receive_invoke () - po otrzymaniu nowego wyzwalacza.
  • lambda_runtime.report_done () - za każdym razem, gdy wykonywana jest Lambda

Teraz może być dobry moment, aby podać więcej szczegółów na temat krajalnicy, o której mówiłem w poprzednim artykule. Krajalnica jest komponentem Lambda, który odpowiada za przydzielanie środowiska wykonawczego dla różnych użytkowników Lambdas, działających na kontenerze. Funkcje te wysyłają powiadomienie do fragmentatora (i innych komponentów zarządzania Lambda), gdy wykonywane są egzaminy Lambda lub otrzymują informacje o nowo zainicjowanych wykonaniach.

Więc po tym, jak zidentyfikowaliśmy wywołania z lambda_runtime i dowiedzieliśmy się, co to jest fragmentator, musiałem spróbować: zaimportować bibliotekę środowiska uruchomieniowego i dobrze się z tym bawić! (te eksperymenty to sposób, w jaki dowiedziałem się o krajalnicy, głównie czytając dezasemblację oraz trochę prób i błędów). Test, który chcę Ci udostępnić, jest również pierwszym, który próbowałem: wywołanie lambda_runtime.report_done () z wnętrza mojej Lambdy. Oto kod, którego użyłem:

Zaskakującą rzeczą, którą znalazłem, było to, że podczas uruchamiania tego przykładu mój kod przestał działać po wydrukowaniu „Beginning”. Następnie, kiedy ponownie uruchomiłem moją Lambdę, wznowiła ona wykonywanie dokładnie od miejsca, w którym zakończyliśmy - i wydrukowała „Po pierwszym zakończeniu”! (Dodałem ten sen, ponieważ czasami mojej Lambdzie udało się wyciągnąć jeden „odcisk”, zanim krajalnica go zatrzymała). Działo się to wielokrotnie, aż do końca egzekucji z Lambdy.

Dzienniki Cloudwatch do wykonania Lambda. Zauważ, że mamy kilka identyfikatorów żądań dla tej samej Lambdy!

Tak więc dla mnie było to definitywne - krajalnik obciąża nas tak długo, jak długo nasza Lambda dostaje czas procesora. Oznacza to, że nasz okres rozliczeniowy składa się z dwóch części:

  1. Czas inicjalizacji modułu (tylko przy pierwszym wywołaniu / zimnym uruchomieniu)
  2. Nasz faktyczny czas trwania funkcji

Unikanie przekroczenia limitu czasu dla lambda

Poza tym, że jest bardzo fajny, to odkrycie ma praktyczne (cóż… praktyczne jest w oczach patrzącego, ale jest zdecydowanie interesujące): wykorzystanie limitów czasu dla Lambdy! Rozważ następującą lambdę:

Raz uruchomiłem Lambdę, która zatrzymała się na linii 13. Potem odczekałem chwilę i ponownie ją uruchomiłem. W rezultacie pozostały czas, w którym metoda obiektu kontekstowego zwróciła, wynosiła 0, ale Lambda nie przekroczył limitu czasu! Limit czasu dla Lambdy został zresetowany, ponieważ jest to inne wywołanie, a teraz podwoiliśmy limit czasu dla naszej Lambdy (i oczywiście nasz rachunek AWS)! Przydatnym przypadkiem może być na przykład pętla przetwarzająca wiele rekordów, a czasem przekroczona limit czasu. Możemy teraz sprawdzić, czy zbliżamy się do przekroczenia limitu czasu, a jeśli tak, wywołaj lambda_runtime.report_done () i poczekaj, aż następny wyzwalacz odbierze wykonanie dokładnie z miejsca, w którym się zatrzymaliśmy!

Dziennik Cloudwatch z wywołania Lambda. Pozostały czas: 0

Inną rzeczą, która przyszła mi do głowy podczas pracy nad tym problemem, jest to, że AWS może dostarczyć prawdziwą funkcję opartą na tym zachowaniu, w której użytkownik może zawiesić swoją Lambdę i wznowić pracę z tej samej lokalizacji przy następnym wywołaniu. Może to być przydatne nie tylko do przetwarzania znacznych ilości danych i obsługi limitów czasu w środku. Innym przypadkiem użycia może być na przykład zawieszenie Lambdy w oczekiwaniu na drogie IO / inne wyniki zadania, zamiast płacenia za bezczynność Lambdy! Czy oni to zrobią? Nie wiem Czy to jest super fajne? Defo.

Wszystko to ma jednak wadę. Ponieważ jest to hacking sposób, kolejne dwa kolejne wywołania Lambda zakończy się niepowodzeniem z błędem wewnętrznym Amazon. Jestem pewien, że można rozwiązać ten problem również przy odrobinie wysiłku, ale na razie było to dla mnie wystarczająco dobre.

Wniosek

Dowiedzieliśmy się wiele o wewnętrznych elementach AWS Lambda. Zbadaliśmy biblioteki binarne w środowisku wykonawczym i system rozliczeniowy Lambda. Zaimportowaliśmy również bibliotekę wykonawczą Lambda i wykorzystaliśmy ją do obsługi limitów czasu! Jednak wciąż jest wiele do odkrycia, zarówno w AWS, jak i innych sprzedawcach. Czekamy na kolejne wyzwania, jeśli masz jakieś prośby - daj mi znać!

Zaktualizowałem również bibliotekę open source zawierającą różne przeprowadzone przeze mnie eksperymenty. Mam nadzieję, że okaże się przydatna!

Tutaj w Epsagon opracowujemy narzędzie do monitorowania dostosowane do aplikacji bezserwerowych. Używasz serwera bez serwera i chcesz usłyszeć więcej? Odwiedz nasza strone internetowa!