Tech Talk: Een serverloze architectuur bouwen in AWS met lambda-functie-interactie (deel 1)
Functions as a Service (FaaS) is een onderdeel van het X-as-a-service paradigma, dat het delegeren van het beheer van platform (PaaS), infrastructuur (IaaS) of software (SaaS) naar een cloud provider omvat. Hierdoor kunnen bedrijven zich richten op hun bedrijfslogica en overheadkosten verlagen.
Het idee achter FaaS is dat je code schrijft om eenvoudige taken uit te voeren en dat de cloudaanbieder voor de rest zorgt. Wat 'eenvoudig' in deze context betekent, is natuurlijk subjectief en de provider zal beperkingen opleggen aan wat je code eigenlijk kan doen. In principe mag het niet veel geheugen gebruiken en mag het niet willekeurig lang draaien.
De voordelen van het gebruik van FaaS in plaats van het draaien van de code in een virtuele machine zijn:
Je hoeft geen infrastructuur te beheren, wat al een groot pluspunt is.
Gebeurtenissen triggeren de code, wat de softwareontwikkeling vereenvoudigt.
Wanneer er een piek is in de activiteit, wordt de code automatisch geschaald, waarbij functies parallel worden uitgevoerd als dat nodig is.
De cloud provider rekent je voor wat je gebruikt. De prijs bestaat uit twee componenten: de tijd dat je functie draait en hoeveel geheugen je toewijst aan je code om zijn ding te doen. Er zijn geen extra kosten voor licenties, systeembeheer, onderhoud of ondersteuning.
Deze eigenschappen maken FaaS een geweldig hulpmiddel voor taken waarbij de werkvraag niet constant is in de tijd, bijvoorbeeld 'piektaken' die veel resources nodig hebben voor een korte periode. Als je code 24/7 moet draaien, of met korte rusttijden, dan is FaaS niet het wapen van je keuze. Voor eenvoudige taken die slechts af en toe moeten worden uitgevoerd, is FaaS echter de juiste keuze. Door gebruik te maken van serverless computing-services verlicht je je hoofd- en portemonneepijn doordat je het onderhoud en de betaling van onderbenutte infrastructuren kunt overslaan.
De use-case met AWS
Bij Agilytic kregen we onlangs de kans om aan een project te werken waarbij we een serverloze architectuur bouwden met behulp van Amazon Web Services (AWS) Lambda Functions (FaaS in de AWS-nomenclatuur), naast andere cloudservices. Het doel van het project was het verzamelen en classificeren van documenten op basis van hun inhoud, gebruikmakend van verschillende technieken: van optische tekenherkenning tot tekstsamenvattingen. Onlangs hebben we ook een artikel gepubliceerd over het gebruik van de Function App met Azure om intensieve werklastoplossingen te bouwen.
We hebben besloten om Lambda-functies te gebruiken die gespecialiseerd zijn in verschillende subtaken van dit proces en die communiceren met S3-opslag en een database voor onze proof of concept. AWS verbindt deze Lambda's opeenvolgend, waarbij elke Lambda de output van de vorige Lambda gebruikt om zijn taak uit te voeren. In dit artikel willen we wat kennis delen over Lambda-functie orkestratie.
We gaan ervan uit dat je al weet hoe je Lambda-functies aanmaakt en de IAM-rollen en -beleidsregels beheert. Om concreet te zijn, vereenvoudigen we de architectuur die we gebruiken en verbinden we enkele Lambda-functies, waarbij elke functie afhankelijk is van de uitvoer van de vorige om zijn gespecialiseerde taak te kunnen uitvoeren.
We gebruiken de AWS-console om de verschillende thema's van het artikel te illustreren, terwijl we voor de client de hele architectuur hebben uitgerold als Infrastructure as Code, met behulp van terraform. Het gebruik van de console zorgt automatisch voor het aanmaken van rollen en, tot op zekere hoogte, het toekennen van rechten. Dit is goed voor een snelle implementatie, maar slecht voor de beveiliging, waar we in deel 2 van dit artikel op ingaan.
Functie-orkestratie met AWS
Dus, je hebt de code voor je eerste Lambda-functie en bent nu aan het kijken hoe je deze kunt triggeren, misschien om een test uit te voeren of je pijplijn te starten. Wat moet je doen? Laten we beginnen met de basis en aannemen dat je een test uitvoert. De eerste optie is om naar de AWS-console te gaan en het functieoverzicht van je Lambda te openen, dat er ongeveer zo uit zou moeten zien als afbeelding 1.
In die figuur zie je, geel gemarkeerd, de testknop. Hiermee kun je een aangepaste gebeurtenis maken om je functie te activeren. Gemakkelijk. Als uw code een gebeurtenis nodig heeft met een sleutel 'bestand' en een waarde 'pad', kunt u dit eenvoudig schrijven in het testfragment, zoals weergegeven in afbeelding 2. Als je code in plaats daarvan een gebeurtenis verwacht van een andere service in AWS, hoef je niet bang te zijn: er zijn genoeg sjablonen beschikbaar waaruit je kunt kiezen.
Na het uitvoeren van de test kun je de logs lezen met het resultaat van het aanroepen van je Lambda-functie, die je vertellen of het succesvol was of niet. In het laatste geval wordt er gewezen naar de eerste fout die is opgetreden, waardoor het debuggen begint.
Toch gaat dit artikel niet over het debuggen van je Lambda's, maar over ze met elkaar laten praten. Dus laten we eens doornemen wat hier is gebeurd. In een notendop heeft AWS de JSON die je met je test hebt gemaakt, ingevoerd in het gebeurtenisargument van de lambda_handler-functie in afbeelding 1 (blauw gemarkeerd). Dit is hoe gebeurtenissen de Lambda-functie triggeren.
De les is duidelijk: de manier om een Lambda te initiëren is om deze te voorzien van het (correct geformatteerde) JSON event. De runtime zet de JSON automatisch om in een object dat je programmeercode begrijpt.
Automatisch triggeren van een lambda-functie
Dus AWS heeft deze eenvoudige manier om de job van een serverless functie handmatig te starten, maar laten we eerlijk zijn, hoeveel use-cases laten het toe om naar de AWS-console te gaan elke keer dat je pipeline moet starten, en een handmatige trigger met de juiste structuur in te stellen? Je hebt misschien een manier nodig om jobs automatisch te starten, en meestal in een van de twee gevallen:
Trigger periodiek een Lambda.
Een Lambda triggeren als reactie op iets dat in de cloud is gebeurd.
De ingenieurs van AWS hebben daar ook over nagedacht en hebben een manier geïmplementeerd waarop sommige AWS-services gebeurtenissen naar je Lambda-functies kunnen sturen. Om een Lambda periodiek te triggeren in AWS, stel je een AWS Cloudwatch regel in onder 'events' (we weten het, dit woord komt veel te veel voor in dit artikel) in de AWS console, en selecteer je de Lambda van je keuze als doel voor die (alweer!) gebeurtenis, zoals in Figuur 3.
Voor de overige toepassingen is er een meer gecentraliseerde manier. In de AWS-console kun je op de configuratiepagina voor de Lambda (zie afbeelding 4) de service selecteren waarvan je wilt dat je Lambda wordt getriggerd en de instructies volgen om het event in te stellen (op het moment van schrijven kun je kiezen uit 16 AWS-services en nog veel meer services van partners). Vergeet niet om altijd het event sjabloon te controleren om de juiste code te schrijven! (Denk eraan: je vindt ze in het geel gemarkeerde deel van Figuur 1).
Trigger een tweede Lambda vanuit de eerste met AWS
Dat is geweldig! Je hebt geleerd hoe je je Lambda-functie periodiek of per gebeurtenis kunt triggeren. Hoe trigger je nu een tweede functie? Kan een Lambda zelfs een Lambda triggeren?
Als je nogmaals op de knop 'Add trigger' van afbeelding 4 klikt, zie je nergens AWS Lambda staan, maar wanhoop niet! De mensen die werken bij clouddiensten zoals AWS zijn slim. Het is niet gemakkelijk om ze te vinden in een situatie die ze nog niet hadden bedacht (een ander verhaal is of we het eens zijn met hun ontwerpkeuzes, maar dat is materiaal voor een ander artikel).
Als je eerste Lambda bijvoorbeeld elke dag om 12:00 uur draait, kan je tweede Lambda dagelijks om 12:20 uur draaien.
Ik hoop dat je gegrinnikt hebt, want dan snap je de grap. Zo niet, laten we dan nog eens nadenken over het belangrijkste probleem met wat je zojuist hebt gelezen (er zijn meerdere problemen met die benadering, maar laten we ze niet allemaal doornemen). Hoe zou de tweede Lambda in dat scenario informatie ontvangen die door de eerste Lambda is verwerkt?
Misschien kun je die informatie opslaan in een database of tussentijdse opslag en de tweede functie deze automatisch laten ophalen, maar dat maakt je code ingewikkelder (en is een grote bron voor fouten en hogere rekeningen). Een betere manier is om de tussenpersoon over te slaan en de gebeurtenis direct op te halen uit het return statement in de lambda_handler functie van figuur 1 (natuurlijk met de juiste info in de JSON!). Dat is tenslotte hoe een Lambda met een andere Lambda praat.
Een andere Lambda oproepen met AWS
De eerste optie om twee Lambda's direct met elkaar te laten praten is door gebruik te maken van de AWS Software Development Kit. Voor python heet dit boto3 en hiermee kun je AWS-bronnen beheren en ermee communiceren vanuit je code. Als je de documentatie bekijkt, zul je zien dat je een client kunt opzetten om Lambda's te beheren en de methode invoke kunt gebruiken om een andere Lambda te activeren.
import boto3, json
client = boto3.client(‘lambda’)
client.invoke(FunctionName='ListenerLambda', InvocationType='Event', Payload=json.dumps({‘file’: ‘path’}))
(Vergeet niet naar de documentatie te kijken om te zien wat de verschillende opties betekenen en welke andere er bestaan!) In dit codefragment sturen we een gebeurtenis met sleutel 'bestand' en waarde 'pad' naar onze luisterfunctie, met de toepasselijke naam 'ListenerLambda'. Het invocatietype dat we hebben ingesteld ('Event') betekent dat listener Lambda asynchroon wordt getriggerd: we wachten niet op het antwoord om verder te gaan met het evalueren van de code.
Deze eenvoudige oplossing doet het werk, maar het is niet altijd (of moet ik zeggen zelden) een goede keuze. Ter illustratie, stel je de volgende prestaties van onze Lambda's voor:
De eerste functie voert een eenvoudige taak uit en kan 1.000 opdrachten per seconde verwerken.
De tweede functie doet een veel zwaardere taak en kan 1 taak per seconde verwerken.
Dit zijn de ingrediënten voor een bottleneck. In vijf seconden kunnen we 5.000 jobs naar de tweede functie sturen, en die verwerkt er maar 5. Erger nog, het maximale aantal gelijktijdige taken dat een enkele Lambda kan uitvoeren (de concurrency) is 1.000. Dus wat is er gebeurd met de andere bijna 4.000 taken?
Ze zijn verdwenen! Dat is, zoals je je kunt voorstellen, niet goed. In het algemeen moet je de SDK alleen gebruiken om een Lambda-functie asynchroon aan te roepen als je 100% zeker weet dat de downstreamtaak altijd sneller zal zijn dan de upstreamtaak. Als je de Lambda synchroon aanroept, is dit geen probleem. Je code zal pauzeren tot de aanroep eindigt. De wachttijd wordt echter wel in rekening gebracht.
Conclusie
We hebben de eerste stappen laten zien in de bouw van efficiënte serverloze architecturen in de cloud met Lambda-functies van AWS. In deel 2 van dit Tech Talk-artikel behandelen we hoe AWS's SQS Service, Step Functions en een beperkende set machtigingen fungeren als basis voor een robuuste en veilige architectuur. Bovendien bespreken we hoe u de juiste oplossing voor uw organisatie kunt opzetten.