Nasza strona używa cookies. Dowiedz się więcej o celu ich używania i zmianie ustawień w przeglądarce. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Pierwsze kroki z AWS Lambda i Application Load Balancer

Gavin Lewis Cloud Architect / Rapid Circle
Sprawdź, jak i w jakich sytuacjach możesz korzystać z Lambdy i ALB dla swoich potrzeb.
Pierwsze kroki z AWS Lambda i Application Load Balancer

Uwielbiam Lambdę za jej wszechstronność i elastyczność. Lista usług, które można wywołać, cały czas rośnie, dzięki czemu może przyjmować obciążenia, do których tradycyjnie potrzebowałbyś EC2 lub ostatnimi czasy kontenerów. Jednym z najnowszych dodatków do tej listy jest load balancer aplikacji (Application Load Balancer, w skrócie ALB).

Kiedy ogłoszono go w listopadzie 2018 r., chciałem się dowiedzieć się więcej o przypadkach użycia tych dwóch usług pracujących razem. Muszę przyznać, że walczyłem o to, aby zobaczyć solidne uzasadnienie użycia tego w świecie rzeczywistym i znaleźć odpowiedzi na pytania, które sam zadawałem online. Po pewnych przemyśleniach i dalszych badaniach, różne kwestie zaczęły mi się rozjaśniać. Nowe opcje koncentrują się wokół możliwości zastępowania komponentów aplikacji serwerowej bez konieczności wdrażania chaotycznych i złożonych rozwiązań.

Zanim przejdę dalej, musisz zrozumieć, jak działa ALB względem tradycyjnych load balancerów (ELB Classic). ALB ma dwa dodatkowe składniki:

  • Grupy docelowe: Grupa docelowa służy do łączenia ze sobą grup serwerów, które wykonują podobne informacje.
  • Słuchacze: odbierają żądania połączenia dla konkretnego portu i protokołu (np. 80 / HTTP lub 443 / HTTPS) i składają się z wielu reguł. Reguły tradycyjnie oparte są na ścieżkach i mogą kierować ruch do określonych grup docelowych.


Aby przyjąć realną sytuację, załóżmy, że mamy witrynę sprzedaży biletów online, na którą kieruje się dużo ruchu i musimy mieć pewność, że klienci, którzy kupują bilety, nie odczują wpływu innych osób odwiedzających tę witrynę, aby zobaczyć dostępne miejsca. Stworzylibyśmy dwie grupy docelowe - grupę A i B. Wykorzystując reguły ruch dla/ checkout, może zostać przypisany do grupy B, a całą resztę skierować do grupy A. Koncepcja jest stosunkowo prosta.

Oto niektóre z moich przemyśleń i scenariuszy wykorzystania funkcji Lambdy za ALB:

  • Masz tradycyjną aplikację serwerową, która ma również web API. To API jest wbudowane w monolityczną aplikację i chciałbyś wydzielić je by usprawnić proces wypuszczania software'u i skalowalność.
  • Masz tradycyjną aplikację opartą na serwerze, która renderuje niektóre strony HTML. Te strony są ważne, ale niekoniecznie muszą znajdować się na serwerach lub w monolitycznej aplikacji, więc chcesz je usunąć.
  • Masz tradycyjną aplikację serwerową z wieloma statycznymi zasobami. Chcesz je przechowywać na S3, by zaoszczędzić na EBS, ale nie możesz łatwo przedesignować aplikacji by to zrobić.


Już znasz teorię, więc sprawdźmy, jak to wygląda w praktyce.


Zabierzmy się do roboty

W tym celu użyję Serverless Framework i .NET - powinien być równie łatwy do zrozumienia dla każdego, kto zrozumie CloudFormation lub szablon SAM, z C# jako środowiskiem wykonawczym. Kroki są łatwe do przeniesienia do konsoli. Zakładamy także, utworzenie publicznego VPC.

Zacznijmy od utworzenia nowego serwisu:

sls create --template aws-csharp --path LambdaALB


Lubię wgrywać/deployować komponenty stopniowo, kiedy pracuję nad nowym PoC, aby zobaczyć, co działa, a co nie. Tak więc pierwsze komponenty, które mam utworzyć w sekcji resources pliku serverless.yml, to:

  • A Security Group which allows traffic on port 80
  • An Application Load Balancer
  • A Target Group (bez targetu)
  • A Listener

 

service: LambdaALB

provider:
  name: aws
  runtime: dotnetcore2.1
  region: ap-southeast-2

custom:
  vpcConfig:
    vpcId: vpc-xxxxxxxx
    subnets:
      - subnet-xxxxxxxx
      - subnet-xxxxxxxx
      - subnet-xxxxxxxx

resources:
  Resources:
    SecurityGroup:
      Type: AWS::EC2::SecurityGroup
      Properties: 
        GroupDescription: Generic HTTP Security Group
        SecurityGroupIngress: 
          -
            CidrIp: 0.0.0.0/0
            FromPort: 80
            IpProtocol: tcp
            ToPort: 80
        VpcId: ${self:custom.vpcConfig.vpcId}

    LoadBalancer:
      Type: AWS::ElasticLoadBalancingV2::LoadBalancer
      Properties: 
        SecurityGroups: 
          - Ref: SecurityGroup
        Subnets: 
          ${self:custom.vpcConfig.subnets}

    TargetGroup:
      Type: AWS::ElasticLoadBalancingV2::TargetGroup
      Properties: 
        TargetType: lambda

    Listener:
      Type: AWS::ElasticLoadBalancingV2::Listener
      Properties: 
        DefaultActions: 
          - 
            TargetGroupArn:
              Ref: TargetGroup
            Type: forward
        LoadBalancerArn:
          Ref: LoadBalancer
        Port: 80
        Protocol: HTTP


Mamy teraz load balancera, który nigdzie nie kieruje ruchu. W przeszłości można było uruchomić kilka instancji EC2 i przypisać je do grupy docelowej, ale to, co zamierzam zrobić, to utworzenie trzech funkcje Lambda, dla wspomnianych wcześniej scenariuszy.

Definicja każdej funkcji jest standardowa, jednak zamiast tradycyjnego zdarzenia HTTP, zostanie użyte zdarzenie ALB. Słuchać musi zostać zdefiniowany (odwołując się do zdefiniowanego wcześniej) wraz z priorytetem i ścieżką, na którą zostanie wysłana odpowiedź na żądanie.

functions:
  hello:
    handler: CsharpHandlers::Namespace.Page::FunctionHandler
    events:
      - alb:
          listenerArn:
            Ref: Listener
          priority: 100
          conditions:
            path: /

  webapi:
    handler: CsharpHandlers::Namespace.API::FunctionHandler
    events:
      - alb:
          listenerArn:
            Ref: Listener
          priority: 20
          conditions:
            path: /api/*

  s3:
    handler: CsharpHandlers::Namespace.S3::FunctionHandler
    environment:
      assetbucket:
        Ref: AssetsBucket
    events:
      - alb:
          listenerArn:
            Ref: Listener
          priority: 10
          conditions:
            path: /assets/*


Moje repozytorium GitHub zawiera nieco bardziej szczegółowy kod, ale tu wklejam przykład bardzo podstawowego kodu, który odpowiada na żądanie ALB. Kluczem jest odwołanie się do pakietu ApplicationLoadBalancerEvents i zwrócenie ApplicationLoadBalancerResponse, w przeciwnym razie otrzymasz błąd 502 Gateway od ALB.

using Amazon.Lambda.Core;
using System;
using System.Collections.Generic;

using Amazon.Lambda.ApplicationLoadBalancerEvents;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Namespace
{
    public class Page
    {
      public ApplicationLoadBalancerResponse FunctionHandler(JObject input)
      {
        string responseString = @"hello world";

        Dictionary<string, string> Headers = new Dictionary<string, string>();
        Headers.Add("Content-Type", "text/html;");

        ApplicationLoadBalancerResponse response = new ApplicationLoadBalancerResponse() {
          IsBase64Encoded = false,
          StatusCode = 200,
          StatusDescription = "200 OK",
          Headers = Headers,
          Body = responseString
        };

        return response;
      }
    }
}


Teraz dysponujemy podstawowym kodem. Uruchommy to, co do tej pory zrobiliśmy, i spójrzmy na wynik - sukces!

Zawsze intryguje mnie struktura zdarzeń, którą wywołują funkcje Lambdy. Jest to szczególnie przydatne, jeśli używasz ścieżki, metody, parametrów lub innych informacji ze zdarzenia do wykonywania określonych działań z poziomu funkcji Lambda.

{
  "requestContext": {
    "elb": {
      "targetGroupArn": "arn:aws:elasticloadbalancing:ap-southeast-2:<ACCOUNTID>:targetgroup/s3-dev/02afa2eda54ca3bf"
    }
  },
  "httpMethod": "GET",
  "path": "/assets/230.jpg",
  "queryStringParameters": {},
  "headers": {
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
    "accept-encoding": "gzip, deflate",
    "accept-language": "en-AU,en;q=0.9",
    "cache-control": "max-age=0",
    "connection": "keep-alive",
    "host": "lambd-loadb-mzay8zovkhl7-1612334130.ap-southeast-2.elb.amazonaws.com",
    "upgrade-insecure-requests": "1",
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
    "x-amzn-trace-id": "Root=1-5d11ce4c-093017c16c1b02df25bba5bd",
    "x-forwarded-for": "0.0.0.0",
    "x-forwarded-port": "80",
    "x-forwarded-proto": "http"
  },
  "body": "",
  "isBase64Encoded": false
}


OK - teraz powinniśmy mieć trzy adresy URL, używając jako przykładu mojego load balancera (zastąp mój adres URL swoim):

  • strona HTML: http://lambd-loadb-mzay8zovkhl7-1612334130.ap-southeast-2.elb.amazonaws.com/


  • API: http://lambd-loadb-mzay8zovkhl7-1612334130.ap-southeast-2.elb.amazonaws.com/api/


  • S3: http://lambd-loadb-mzay8zovkhl7-1612334130.ap-southeast-2.elb.amazonaws.com/assets/200.jpg


Szczególnie podoba mi się ostatnie rozwiązanie które przesuwa statyczne zasoby na S3. Najlepsze w tym rozwiązaniu jest to, że nie musisz zmieniać aplikacji! Kod, choć nie tak lekki i możliwy do udoskonalenia, jest stosunkowo prosty, ale jest sprytny. Pobiera ścieżkę z żądania i pobiera obiekt z bucketu S3 za pomocą tego samego klucza. Pamiętaj, że ta metoda publicznie udostępnia cały bucket, więc nie przechowuj w nim niczego prywatnego!

private static AmazonS3Client client = new AmazonS3Client();
public ApplicationLoadBalancerResponse FunctionHandler(JObject input)
{
  LambdaLogger.Log(input.ToString());

  try {
    char[] toTrim = new[] {'/'};

    GetObjectRequest request = new GetObjectRequest() {
      BucketName = Environment.GetEnvironmentVariable("assetbucket"),
      Key = input.SelectToken("path").ToString().TrimStart(toTrim)
    };

    GetObjectResponse response = client.GetObjectAsync(request).Result;

    byte[] data;
    using (StreamReader sr = new StreamReader(response.ResponseStream))
    {
      using (MemoryStream ms = new MemoryStream())
      {
        sr.BaseStream.CopyTo(ms);
        data = ms.ToArray();

        string responseBody = Convert.ToBase64String(data);
        Dictionary<string, string> headers = new Dictionary<string, string>();
        headers.Add("Content-Type", response.Headers["Content-Type"]);

        ApplicationLoadBalancerResponse albResponse = new ApplicationLoadBalancerResponse() {
          IsBase64Encoded = true,
          StatusCode = 200,
          StatusDescription = "200 OK",
          Headers = headers,
          Body = responseBody
        };

        return albResponse;
      }
    }
  } catch (Exception ex) {
      LambdaLogger.Log(ex.Message);

      return null;
  }
}


Podsumowanie

Moim głównym celem przy pisaniu tego artykułu było poeksperymentowanie i pokazanie, dlaczego mógłbyś korzystać z Lambdy za ALB, ponieważ przypadki użycia nie dla każdego muszą być jasne. W trakcie procesu uczenia się stało się dla mnie jasne, że istnieje wiele przypadków, w których ta metoda może być przydatna do rozpoczęcia korzystania z technologii serverless, bez konieczności uruchamiania wieloletnich projektów w celu zmiany dużych, złożonych architektur, w celu stosunkowo prostej zmiany.

Chciałbym poznać więcej przypadków użycia, z którymi się spotkałeś. Nie krępuj się, aby napisać do mnie na LinkedInie!


Oryginał tekstu w języku angielskim przeczytasz tutaj.

Lubisz dzielić się wiedzą i chcesz zostać autorem?

Podziel się wiedzą z 130 tysiącami naszych czytelników

Dowiedz się więcej