Tech talk: een serverless architectuur in AWS bouwen met interactie tussen Lambda-functies (deel 2)

In het eerste deel van deze artikelenreeks bespraken we hoe de Lambda-functie de basis vormt van serverlessarchitecturen in de cloud. Nu bekijken we hoe de wisselwerking tussen Lambda-functies en wachtrijen een logische volgende stap is naar een robuuste serverlessarchitectuur.
Simple Queuing Service (SQS) in AWS
We eindigden het eerste deel met een knelpunt: onze eenvoudige serverlessarchitectuur kon de events niet verwerken. Een berichtenwachtrij is de eenvoudigste oplossing voor de ontbrekende berichten. AWS noemt die dienst Simple Queuing Service, kortweg SQS. Die lost het knelpunt op door berichten tussen software te versturen, te ontvangen en op te slaan zonder verlies en zonder dat de diensten beschikbaar moeten zijn. Als de tweede Lambda-functie uit het vorige voorbeeld bezig is met de eerste 1.000 ontvangen taken, bewaart SQS geduldig de volgende 4.000 tot ‘ListenerLambda’ ze kan opnemen.
Vanuit de Lambda-functie kunnen we het bericht via de AWS SDK programmatisch naar SQS sturen;
client = boto3.client('sqs')
client.send_message(QueueUrl='string', MessageBody='{"file": "path"})
‘QueueUrl’ is de URL van de wachtrij die we willen gebruiken. Raadpleeg de documentatie voor de vele andere opties die we in dit voorbeeld weglaten. Om de tweede Lambda-functie naar deze wachtrij te laten luisteren, gaan we opnieuw naar het consolescherm in figuur 4, klikken we op ‘Add trigger’ en kiezen we de gewenste SQS.
Zo eenvoudig lossen we het knelpunt op. Zodra de listenerfunctie het event verwerkt, wordt het automatisch uit de wachtrij verwijderd om dubbel versturen te voorkomen. Rondt de listener-Lambda de verwerking om een of andere reden niet af en geeft ze een fout, dan probeert de wachtrij het later opnieuw te versturen.
Dat is geweldig… of toch niet? Vaak wordt het ingewikkelder. Stel dat ‘ListenerLambda’ slechts 99% van de events uit de eerste Lambda succesvol verwerkt en dat de code voor een eenmaal mislukt event altijd faalt. Dan gaat het bericht terug naar de wachtrij, die het naar ‘ListenerLambda’ stuurt, die faalt, waarna het bericht weer teruggaat… U ziet het probleem? Om die eindeloze lus te vermijden, heeft SQS een bewaartermijn waarna onverwerkte berichten automatisch worden gewist (figuur 5).

Figuur 5: SQS configureren
Stel dat u raamt dat alle taken zonder fouten binnen twee dagen klaar zijn. Dan zou u aannemen dat elk bericht dat na twee dagen nog in de wachtrij staat, bij een mislukte taak hoort en veilig kan worden verwijderd. U stelt dus twee dagen bewaartermijn in. Dat werkt, maar is geen goede praktijk. Hoe kunt u uw code debuggen en verbeteren als alle informatie over de events die de fout veroorzaakten, is gewist?
Geen zorgen, ook daar is al aan gedacht. Verder naar beneden in de AWS-console kunt u een Dead-Letter Queue voor uw SQS instellen. Dat is gewoon een andere SQS waar u problematische events naartoe stuurt. Met een lange bewaartermijn in die Dead-Letter Queue kunt u de taak op elk moment controleren, zonder dat events die de code niet goed verwerkt voortdurend opnieuw naar uw Lambda-functie gaan.
Proficiat! Zo laten we twee Lambda-functies robuust met elkaar communiceren. SQS en Dead-Letter Queues vangen knelpunten en problematische events op.
Als uw toepassing tientallen Lambda-functies heeft die klaar zijn voor uitrol, met niet-sequentiële logica ertussen en foutafhandeling en herpogingen, lijken de beschikbare tools misschien beperkt. Dat klopt. We hebben AWS Step Function namelijk nog niet voorgesteld.
AWS Step Function
AWS Step Function orkestreert serverlessfuncties. U kunt Lambda's en andere serverlessdiensten van AWS snel visueel coördineren. ‘Visueel’ is hier het sleutelwoord: de grafische interface toont op elk moment wat er gebeurt. Bijvoorbeeld een Lambda die een event naar de volgende stuurt, een fout opvangt en opnieuw probeert, of if-then-else-logica uitvoert. Figuur 6 toont het configuratiescherm met verschillende voorbeeldsjablonen om te beginnen en de mogelijkheden van de dienst te leren kennen.

Figuur 6: Lambda-functies orkestreren met AWS Step Function
De dienst doet dat allemaal en verwerkt de events tussen serverlessfuncties automatisch. Al die uitleg hierboven is dus niet nodig! Het is een bijzonder krachtige tool om Lambda's te orkestreren. Waarom gebruiken we die dan niet altijd om onze code te vereenvoudigen en u het lezen van dit artikel te besparen? Omdat deze oplossing geld kost. U moet afwegen hoeveel u wilt betalen en hoeveel beheer u zelf wilt doen. Voor matig complexe processen is deze optie zeker de moeite waard.
Minimale rechten met AWS
We sluiten af met het belangrijkste onderwerp: veiligheid. Onlinehandleidingen en sjablonen zijn een goede manier om infrastructuur te leren uitrollen. Meestal geven ze echter voorrang aan een snelle pipeline boven veiligheid. Het is tenslotte een oefening en u gaat alle aangemaakte middelen weer verwijderen. De cloud is een gevaarlijke plek: verwijder altijd middelen die u niet gebruikt. In dit voorbeeld wordt u om een Lambda-functie met een SQS-wachtrij te verbinden doorgaans naar de AWS IAM-console gestuurd. Daar kent u een ingebouwd beveiligingsbeleid toe aan de Lambda-rol, vaak met volledige toegang, zoals in figuur 7.

Figuur 7: een Lambda toegang geven tot SQS
Na toekenning van dit beleid aan de Lambda-rol vindt u de inhoud als JSON in de IAM-console. Die ziet er ongeveer zo uit:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:*"
],
"Resource": "*"
}
]
}
Het veiligheidsprobleem zit in de jokertekens ‘*’. Vooral "sqs:*" laat de Lambda onbeperkt met de SQS-wachtrij werken. Ze kan die zelfs wissen of extra berichten maken om zelf te verwerken, wat een oneindige lus veroorzaakt! Erger nog: het tweede jokerteken in "Resource": "*" geeft de Lambda toegang tot elke wachtrij. Zo kunnen we de serverlessfunctie echt aanroepen met verkeerde events die voor een andere functie bestemd zijn, of zelfs voor een volledig onafhankelijk project.

Figuur 8: een Lambda toegang geven tot SQS
Het eerste probleem kunt u beperken door de rol SQS Queue Execution te kiezen, zoals in figuur 8. Daarmee is het aantal SQS-acties van de Lambda beperkt. De JSON-samenvatting van het toegestane beleid wordt dan:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes"
],
"Resource": "*"
}
]
}
Dit staat berichten ontvangen, berichten verwijderen en wachtrijkenmerken opvragen toe. Niet langer per ongeluk de wachtrij wissen of nieuwe berichten maken. Het resterende zwakke punt is "Resource": "*", waarmee de Lambda elke wachtrij kan benaderen. Dat lossen we op door het beleid te beperken tot één resource: de SQS waaruit de Lambda berichten moet lezen. Daarvoor hebt u de ARN van de SQS nodig. Die vindt u in de AWS-console, zoals figuur 9 toont.

Figuur 9: de ARN van een SQS-wachtrij ophalen
Vervang het jokerteken ‘*’ in het Resource-veld door die ARN. Zo kan de Lambda-functie alleen van die specifieke wachtrij berichten ontvangen en verwijderen en kenmerken opvragen. Dan blijft een logische vraag: wat als ik uit de ene wachtrij wil lezen en berichten naar een andere wil sturen, bijvoorbeeld om een derde Lambda-functie aan te roepen? Is de resource tot de ontvangende wachtrij beperken dan niet contraproductief? Het antwoord is eenvoudig: u kunt meerdere beleidsregels aan de Lambda-rol koppelen. Voeg bijvoorbeeld toestemming toe om berichten naar een andere wachtrij te sturen:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:ChangeMessageVisibility",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes",
"sqs:ReceiveMessage"
],
"Resource": "ARN_OF_QUEUE_TO_READ_MESSAGES_FROM"
},
{
"Effect": "Allow",
"Action": [
"sqs:SendMessage"
],
"Resource": "ARN_OF_QUEUE_TO_SEND_MESSAGES_TO"
}
}
Beperk toegestane acties en resources altijd tot het minimum dat u nodig hebt. Zo voorkomt u latere problemen bij het opschalen naar productie
Conclusie
We zagen hoe u robuuste en efficiënte serverlessarchitecturen in de cloud bouwt met Lambda-functies, SQS, Step Functions en beperkte toegangsrechten.
Voor werklasten met pieken of lange inactieve periodes biedt dat een kostenefficiënt en eenvoudig te beheren alternatief voor monolithische code op virtuele machines.
We gebruikten AWS ter illustratie, maar deze opmerkingen gelden ook voor andere cloudaanbieders, zoals MS Azure (zie het artikel met een toepassingsvoorbeeld) of Google Cloud.