Ikona strzałka
Powrót do bloga

Bliżej i Szybciej w Globalnej Skali. Prześwietlamy Usługę Lambda@Edge

przemek.malak
przemek.malak
04/02/2019

Serverless. Słowo, o którym w ubiegłym roku słyszał chyba każdy programista i architekt systemów IT. Wszystko wskazuje na to, że rok 2019 będzie dla architektury bezserwerowej jeszcze ciekawszy. Wiele osób sądzi, że właśnie w ciągu najbliższych kilku miesięcy rozwiązania serverless będą rozwijały się szybciej niż jakakolwiek inna dziedzina IT. No, może poza kolejnymi frameworkami Node.Js 😉

Kiedy pada hasło serverless, pierwsze, co przychodzi mi do głowy, to usługa AWS Lambda. Praktycznie każdy o niej słyszał, nie każdy jednak wie, że Lambdę można uruchamiać także w usłudze CloudFront, czyli w oferowanym przez Amazon rozwiązaniu content delivery network. Dzieje się tak dzięki usłudze Lambda@Edge.

Jak uruchomić funkcję Lambda@Edge?

Funkcję Lambda@Edge możemy wywołać za pomocą czterech eventów, które mamy do dyspozycji w CloudFront:

  • Viewer Request. Zachodzi, kiedy CloudFront otrzymuje żądanie od użytkownika przed sprawdzeniem, czy dane są w schowku.
  • Origin Request to event, który wywoływany jest jedynie, kiedy żądanie przekazane jest do źródła. Jeżeli dane są już w schowku, ten event nie jest uruchamiany.
  • Origin Response uruchamiany jest pomiędzy momentem, w którym CloudFront otrzymuje odpowiedź od źródła, a chwilą, gdy odpowiedź ląduje w schowku. Co istotne, Origin Response zachodzi także wtedy, gdy źródło zwróci błąd.
  • Viewer Response uruchamiany jest z kolei przed zwróceniem obiektu do użytkownika. Niezależnie od tego, czy był w schowku, czy nie.
Lambda at Edge events

Kiedy użyć danego eventu?

Wybór metod uruchamiających Lambda@Edge jest całkiem spory. Nasuwa się zatem pytanie, kiedy użyć poszczególnych eventów. W każdym przypadku trzeba do tej kwestii podejść indywidualnie. Przygotowałem jednak kilka wskazówek, które mogą pomóc Ci w wyborze.

  • Jeżeli chcemy uruchomić naszą funkcję dla każdego żądania, skorzystajmy z eventu ViewerRequest lub ViewerResponse. Pierwszy będzie uruchomiony zawsze, drugi prawie za każdym razem.
  • W przypadku gdy chcemy zmodyfikować samo żądanie tak, że zmieni ono odpowiedź źródła, użyjmy eventu OriginRequest.
  • Chcąc zmienić obiekt, który zostanie zapisany w schowku CloudFront, użyjmy eventu OriginRequest lub OriginResponse.

Ograniczenia Lambda@Edge

Lambda@Edge w porównaniu z klasycznymi funkcjami Lambda posiada wiele ograniczeń. Najważniejszym z nich, przynajmniej dla mnie, jest fakt, że funkcje Lambda@Edge możemy pisać tylko w Node.Js. O innych językach możemy jak na razie zapomnieć. Mam jednak nadzieję, że wkrótce to się zmieni.

Drugą kwestią są warstwy i zmienne środowiskowe. Zapomnijcie o nich! Te drugie możemy zastąpić AWS Systems Manager Parameter Store. Ale zawsze wprowadzi to jakieś opóźnienia. A maksymalne timeouty w Lambda@Edge też mamy krótsze. Dla OriginRequest i OriginResponse to 30 sekund. Dla eventów ViewerRequest oraz ViewerResponse to tylko 5 sekund.

Do tego dochodzi jeszcze więcej limitów. Na szczęście sporo z nich to tak zwane ograniczenia miękkie, co oznacza, że możemy poprosić o ich zwiększenie. Także sama wielkość funkcji, rozmiar odpowiedzi oraz maksymalna ilość pamięci, którą możemy przydzielić funkcjom @Edge, są mniejsze od standardowych.

A jak korzystać z Lambda@Edge? Oto przykład

Spróbujemy zaimplementować przykładową funkcję Lambda@Edge. Nasze rozwiązanie pozwoli na generowanie za pomocą Lambdy strony www. Jej treść będziemy zmieniali w zależności od kraju, z którego łączy się nasz użytkownik.

Jeżeli wykorzystamy usługę CloudFront, czy to bezpośrednio, czy też implementując nasze API w usłudze API Gateway jako Edge Optimized, w żądaniu zostaną nam przekazane różne nagłówki. Między innymi nagłówek CloudFront-Viewer-Country.

Na podstawie tego nagłówka funkcja Lambda@Edge będzie generowała kod HTML zwracany do użytkownika.

Nasze rozwiązanie będzie składało się z kilku elementów:

  • statycznej strony www w usłudze S3
  • dystrybucji CloudFront
  • funkcji Lambda podpiętej pod event OriginRequest

Proponuję utworzyć zarówno koszyk S3 jak i funkcję Lambda w regionie N. Virginia. Funkcje Lambda@Edge możemy tworzyć tylko w tym regionie!

1.   Piszemy kod strony WWW

W pierwszym kroku musimy utworzyć bucket S3, wgrać do niego poniższy kod i uruchomić w tym koszyku hosting. Kod strony www, która będzie wyświetlana z S3 jest bardzo prosty.


 
     
        
        

Hello unknown from S3


    

Strona będzie po prostu nas informowała, że nie można rozpoznać kraju, z którego użytkownik łączy się z naszą usługą. Do tej strony „podepniemy” też dystrybucję CloudFront.

Cała magia będzie się działa w funkcji Lambda@Edge umieszczonej w CloudFront. To ona będzie generowała właściwą odpowiedź w zależności od kraju, z którego pochodzi nasz użytkownik.

1.   Tworzymy funkcję Lambda

W kolejnym kroku musimy utworzyć funkcję Lambda. Pamiętajcie o regionie – N. Virginia.

Tworzymy zwykłą Lambdę, jako runtime wybieramy Node.Js 8.10. Wystarczy nam domyślna rola, która nada uprawnienia do zapisu logów w usłudze CloudWatch.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}

Teraz musimy dodać Lambda@Edge do zaufanych (Trust relationships).

Po wprowadzeniu tych zmian nasza polityka powinna wyglądać tak:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": ["lambda.amazonaws.com",    				"edgelambda.amazonaws.com"]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Kod samej funkcji jest prosty. W nagłówkach wyszukuje kraj, z którego łączy się nasz użytkownik i wstawia go do kodu HTML, który jest zwracany do użytkownika. Jeżeli nagłówka nie ma, zwraca po prostu żądanie.

'use strict';

exports.handler = (event, context, callback) => {
  const request = event.Records[0].cf.request;
  const headers = request.headers;
  console.log('request', request);

  if (
    headers['cloudfront-viewer-country'] &&
    headers['cloudfront-viewer-country'][0].value
  ) {
    const cloudFrontCountryCode = headers['cloudfront-viewer-country'][0].value;
    console.log('countryCode', cloudFrontCountryCode);

    const response = {
      status: '200',
      statusDescription: 'OK',
      headers: {
        'cloudfront-viewer-country': [
          {
            key: 'CloudFront-Viewer-Country',
            value: cloudFrontCountryCode,
          },
        ],
        'cache-control': [
          {
            key: 'Cache-Control',
            value: 'max-age=100',
          },
        ],
        'content-type': [
          {
            key: 'Content-Type',
            value: 'text/html',
          },
        ]
      },
      body: `
        
       
       
           
           
        
       
           

Hello ${cloudFrontCountryCode} from Lambda @Edge!


        
        
      `,
    };

    callback(null, response);
  }

  callback(null, request);
};

3.   Ustalamy wersję

O wersjach i aliasach funkcji Lambda pisałem już na naszym blogu. Aby nasza Lambda mogła być wykorzystana jako funkcja Lambda@Edge, musimy opublikować jej wersję. W usłudze CloudFront podepniemy później ARN opublikowanej wersji.

W konsoli wybieramy menu Actions i opcję Publish new version. Możemy dodać też jakiś opis. Po chwili nasza wersja będzie gotowa.

Po utworzeniu wersji w górnej części konsoli wyświetli nam się ARN dla naszej funkcji.

Skopiujmy go sobie, będzie potrzebny za chwilę.

4.   Dystrybucja CloudFront

Mamy nasz bucket, mamy Lambdę, stwórzmy teraz powiązaną z nimi dystrybucję CloudFront.

  1. Jako Origin Domain Name wybieramy nasz bucket. To z niego będą pobierane w razie potrzeby dane.
  2. Przekierowujemy też wywołania HTTP na HTTPS w sekcji Default Cache Behavior Settings.
  1. W kolejnym kroku musimy dodać do białej listy nagłówek, którym będziemy się posługiwali.
  1. Teraz najważniejsze. Musimy podpiąć funkcję Lambda pod dystrybucję. Wybieramy interesujący nas event, czyli OriginRequest i wklejamy ARN do naszej funkcji Lambda. Pamiętajcie, że powinien to być ARN opublikowanej wersji.
  1. I to właściwie wszystko. Klikamy [Create Distribution] i nasza dystrybucja zaczyna się tworzyć.

Po 15-30 minutach dystrybucja powinna być gotowa. Możemy przejść do testowania naszego rozwiązania.

CloudFront – Testy i logi

Każda dystrybucja CloudFront ma swoje DomainName. Skorzystajmy z przeglądarki i przetestujmy jej działanie.

Włączamy narzędzia programisty w Chrome, wklejamy adres naszej dystrybucji w przeglądarce i… voilà!

Dostaliśmy odpowiedź od naszej funkcji Lambda@Edge.

W przeglądarce, w zakładce Network, klikamy w odpowiedź z serwera i sprawdzamy nagłówki.

Sprawdźmy teraz, co mamy w logach. Wchodzimy do usługi CloudWatch w regionie N. Virginia i… nic tam nie ma.

Na początku mnie to także zdziwiło. Lambda@Edge zapisuje bowiem logi w regionie, w którym jest wywoływana (albo najbliższym). Ja łączyłem się z Polski, sprawdźmy więc, co słychać we Frankfurcie.

Jak widać, wszystko jest w porządku. Nasza Lambda zadziałała, a logi zostały zapisane.

Odświeżmy stronę i sprawdźmy, co się stanie:

Tym razem nasza strona została załadowana ze schowka, bez uruchamiania funkcji Lambda@Edge. W logach nie będziemy mieli zatem nic nowego.

Spróbujmy połączyć się teraz z innego kraju. Najlepiej wykorzystać do tego jakieś połączenie VPN. Ja użyję PureVPN ale możecie użyć np. TunnelBear. Na potrzeby naszego testu wystarczy, a jest darmowy. Połączyłem się z serwerem VPN w Holandii.

Kolejna próba w Chrome i przenosimy się do kraju tulipanów.

Tym razem nagłówek x-cache miał wartość Miss from cloudfront. Dlaczego? Było to pierwsze wywołanie z Holandii i CloudFront nie miał jeszcze w schowku właściwej odpowiedzi. Ponownie zadziałała nasza Lambda@Edge:

Usuwamy zasoby

Usuwanie zasobów związanych z Lamda@Edge przysparza nieco problemów. Pamiętajmy, że nasze funkcje replikowane są do poszczególnych miejsc docelowych. W moim przypadku przy kasowaniu Lambda@Edge zadziałał taki scenariusz:

  1. Odłączyłem funkcję od dystrybucji CloudFront. Można to zrobić w ustawieniach dystrybucji w sekcji Behavior.
  2. Dałem CloudFrontowi trochę czasu. Przynajmniej 30 minut. (Raz musiałem czekać prawie 2 godziny!)
  3. Usunąłem funkcję.

Jeżeli używamy CloudFormation, prawie na pewno będzie konieczna dwukrotna próba usunięcia stacka. Za pierwszym razem operacja usunięcia funkcji się nie powiedzie.

Co z tą Lambdą@Edge?

Udostępniona w 2017 roku usługa Lambda@Edge jest potężnym narzędziem o wielu zastosowaniach.

Za jej pomocą możemy na przykład sprawdzać wartości cookies w żądaniach i na tej podstawie przekierowywać użytkowników na inne adresy URL. Korzystając z nagłówka user-agent, możemy obługiwać różne rozmiary zdjęć. Nie będzie także wielkim problemem wykonanie autoryzacji żądań.

Jeśli chcielibyście odkryć jeszcze więcej zastosowań Lambda@Edge, w oficjalnej dokumentacji można znaleźć wiele przykładów na ciekawe wykorzystanie tej usługi. Miłej zabawy!

AKTUALNOŚCI
13/06/20232 min.
AI w średniej firmie: Tworzenie przyszłości przy użyciu LLM.

Już 21 czerwca dowiesz się, jak możesz wykorzystać AI w Twojej firmie. Damian Mazurek i Piotr Kalinowski wprowadzą Cię w świat sztucznej inteligencji i LLM.

Zobacz wpis
AKTUALNOŚCI
14/02/20232 min
Chmurowisko łączy się z Software Mind

Przed nami nowy rozdział! Chmurowisko dokonało połączenia z polskim Software Mind – firmą, która od 20 lat tworzy rozwiązania przyczyniające się do sukcesu organizacji z całego świata…

Zobacz wpis
AKTUALNOŚCI
09/11/20225 min
Migracja systemu Dynamic Precision do Oracle Cloud

Grupa Dynamic Precision podjęła decyzję o unowocześnieniu swojej infrastruktury. Razem z Oracle Polska prowadzimy migrację aplikacji firmy do chmury OCI.

Zobacz wpis
AKTUALNOŚCI
AI w średniej firmie: Tworzenie przyszłości przy użyciu LLM.

Już 21 czerwca dowiesz się, jak możesz wykorzystać AI w Twojej firmie. Damian Mazurek i Piotr Kalinowski wprowadzą Cię w świat sztucznej inteligencji i LLM.

Zobacz wpis
Grafika przedstawiająca chmuręGrafika przedstawiająca chmurę

Zapisz się do naszego newslettera i
bądź z chmurami na bieżąco!

Zostaw nam swój e–mail a co miesiąc dostaniesz spis najważniejszych nowości
z chmur Azure, AWS i GCP, z krótkimi opisami i linkami.