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:
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:
Już znasz teorię, więc sprawdźmy, jak to wygląda w praktyce.
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:
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):
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;
}
}
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.