Tech Talk: Een serverloze architectuur bouwen in AWS met lambda-functie-interactie (deel 2)

Afbeeldingen: https://www.agilytic.be/blog/tech-talk-serverloze-architectuur-aws-lambda-functie-interactie

In het eerste deel van deze artikelreeks hebben we besproken hoe de lambda-functie als basis dient voor serverloze architecturen in de cloud. Nu zullen we onderzoeken hoe de interacties tussen lambda-functies en wachtrijen een natuurlijke volgende stap zijn in het uitbouwen van een robuuste serverloze architectuur.

Eenvoudige wachtrijservice (SQS) in AWS

We eindigden het eerste deel van dit artikel met een knelpunt: onze gebeurtenissen konden niet worden verwerkt door de eenvoudige serverloze architectuur. De eenvoudigste mogelijkheid om het probleem van de ontbrekende berichten op te lossen is door gebruik te maken van een wachtrijservice voor berichten. AWS noemt deze service Simple Queuing Service, of kortweg SQS. Het lost het knelpuntenprobleem op door berichten te verzenden, ontvangen en op te slaan tussen software zonder dat berichten verloren gaan of dat de services beschikbaar moeten zijn. Als de tweede Lambda-functie in het vorige voorbeeld bezig is met de eerste 1.000 taken die het heeft ontvangen, houdt SQS geduldig de volgende 4.000 taken vast totdat 'ListenerLambda' ze kan binnenhalen.

We kunnen het bericht programmatisch naar de SQS sturen met behulp van de AWS SDK vanuit de Lambda-functie;

client = boto3.client('sqs')

client.send_message(QueueUrl='string', MessageBody='{"file": "path"})

waarbij 'QueueUrl' de URL is die overeenkomt met de specifieke wachtrij die we willen gebruiken (controleer nogmaals de documentatie voor een veelheid aan andere opties die we in dit voorbeeld weglaten). Voor de tweede Lambda-functie om naar deze wachtrij te luisteren, kunnen we opnieuw naar het consolescherm in afbeelding 4 gaan, op 'Add trigger' klikken en daar de gewenste SQS selecteren.

Zo eenvoudig is het ons gelukt om het knelpuntenprobleem op te lossen. Wanneer het event verwerkt wordt in de listener functie, wordt het automatisch verwijderd uit de wachtrij om te voorkomen dat het opnieuw verzonden wordt. Als de listener Lambda om wat voor reden dan ook niet klaar is met het verwerken van het event (het geeft een foutmelding), dan zal de wachtrij het later opnieuw proberen te versturen.

Dit is geweldig... of toch niet? In veel gevallen kan het ingewikkelder worden. Stel je voor dat 'ListenerLambda' slechts 99% van de gebeurtenissen die binnenkomen van de eerste Lambda succesvol kan verwerken, en voor een gebeurtenis die één keer is mislukt, mislukt de code altijd. In deze situatie wordt het bericht teruggestuurd naar de wachtrij, die het naar 'ListenerLambda' stuurt, die faalt, dus wordt het bericht teruggestuurd naar de wachtrij... je ziet het, toch? Om deze lus van wanhoop te voorkomen, is SQS uitgerust met een retentieperiode die berichten die in die periode niet verwerkt zijn automatisch wist (afbeelding 5).

Figuur 5: Configuratie van SQS

Stel je voor dat je hebt ingeschat dat al je jobs binnen twee dagen klaar zijn als ze foutloos verlopen. Dan zou je ervan uitgaan dat elk bericht dat na twee dagen nog in de wachtrij staat, te wijten is aan het mislukken van de taak en er veilig uit gewist kan worden. Dus stel je je bewaarperiode in op twee dagen. Dit doet opnieuw het werk, maar is geen goede praktijk. Hoe kunt u uw code debuggen en verbeteren als u alle informatie over het type gebeurtenissen dat de fout veroorzaakte, hebt gewist?

Nogmaals, geen zorgen, we zijn niet de eersten die hierover nadenken. Als je naar beneden scrolt in het scherm van de AWS-console, vind je de mogelijkheid om een Dead-Letter Queue in te stellen voor je SQS. Dit is gewoon een andere SQS waarnaar je problematische gebeurtenissen doorstuurt. Door een lange retentieperiode in te stellen in de Dead-Letter Queue, kun je op elk moment je taak monitoren zonder dat je je zorgen hoeft te maken dat gebeurtenissen die niet goed zijn afgehandeld door je code continu naar je Lambda-functie worden gestuurd.

Gefeliciteerd! Hiermee zijn we erin geslaagd om twee Lambda-functies robuust met elkaar te laten praten, knelpunten en problematische gebeurtenissen af te handelen met SQS en Dead-Letter Queues.

Als je terugkijkt naar je use case en ziet dat je tientallen Lambda-functies hebt die klaar zijn voor implementatie, met een niet-sequentiële logica die ze verbindt, en dat je moet omgaan met het opvangen en opnieuw proberen van fouten, denk je misschien dat de tools die je tot je beschikking hebt beperkt zijn. Dat is waar. Wat er aan de hand is, is dat we de AWS Step Function nog niet hebben geïntroduceerd.

AWS stapfunctie

AWS Step Function is een orchestrator voor serverloze functies. Je kunt Lambda's en andere serverloze AWS-services snel visueel coördineren. De sleutelterm hier is 'visueel', omdat de grafische interface verduidelijkt wat er op elk moment gebeurt, of het nu gaat om een Lambda-functie die een event naar de volgende stuurt, een fout opvangt en opnieuw probeert of het implementeren van if-then-else logica, om maar een paar voorbeelden te noemen. Figuur 6 toont een screenshot van de configuratiepagina, waar je verschillende voorbeeldsjablonen kunt vinden om aan de slag te gaan en de mogelijkheden van deze service te leren kennen.

Afbeelding 6: Lambda-functie orkestratie met AWS Step Function

Het doet dit allemaal en handelt de gebeurtenissen tussen serverloze functies automatisch af. Je hebt al die yadda-yadda die je net hebt gelezen niet nodig! Als zodanig is het een zeer krachtig hulpmiddel om je Lambdas te orkestreren. Waarom zouden we het dan niet altijd gebruiken om onze code te vereenvoudigen en tijd te besparen bij het lezen van dit artikel? De reden is dat er een prijskaartje hangt aan deze oplossing, dus je moet afwegen hoeveel je wilt betalen en hoeveel je wilt beheren. Deze optie is zeker de moeite waard voor middelmatig gecompliceerde workflows.

Minimale machtigingen met AWS

Afbeelding 7: Een Lambda laten communiceren met SQS

We eindigen dit artikel met het allerbelangrijkste onderwerp: beveiliging. Het volgen van tutorials en sjablonen online is een geweldige manier om te leren hoe je infrastructuur kunt implementeren, maar meestal geven deze de voorkeur aan het snel implementeren van een pijplijn boven overwegingen zoals beveiliging. Het is tenslotte een tutorial en je gaat alle gemaakte bronnen vernietigen (de cloud is een gevaarlijke plek: vernietig altijd alle bronnen die je niet gaat gebruiken). In de huidige context betekent dit dat je, om een Lambda-functie te laten communiceren met een SQS-wachtrij, naar de IAM-console in AWS moet gaan en daar een ingebouwd beveiligingsbeleid moet toewijzen aan de rol van de Lambda-functie.

Na het toewijzen van dit beleid aan de Lambda-rol, kan men de inhoud ervan in JSON-formaat terugvinden in de IAM-console, waar iets vergelijkbaars staat als:

{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Effect": "Allow",
           "Action": [
               "sqs:*"
           ],
           "Resource": "*"
       }
   ]
}

Afbeelding 8: Een Lambda laten communiceren met SQS

Het beveiligingsprobleem van dit soort beleid zit in de wildcards '*'. In het bijzonder staat "sqs:*" de Lambda toe om onbeperkt te interageren met de SQS wachtrij. Het is zelfs mogelijk om de wachtrij te wissen of meer berichten aan te maken voor de Lambda om te consumeren, waardoor een oneindige lus ontstaat! Het ergste is de tweede wildcard in "Resource": "*" laat de Lambda-functie interageren met om het even welke wachtrij, dus bestaat er een reële mogelijkheid dat we de serverloze functie aanroepen met de verkeerde gebeurtenissen die bestemd zijn voor een andere functie of, erger nog, een volledig onafhankelijk project.

Je kunt het eerste van deze problemen verlichten door in plaats daarvan de SQS Queue Execution rol te kiezen, zoals te zien is in afbeelding 8. In dit geval is de set acties die de Lambda kan uitvoeren met betrekking tot de SQS beperkt. In dit geval is de set acties die de Lambda kan uitvoeren met betrekking tot de SQS beperkt. De JSON samenvatting van de toegestane policies luidt nu:

{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Effect": "Allow",
           "Action": [
               "sqs:ReceiveMessage",
               "sqs:DeleteMessage",
               "sqs:GetQueueAttributes"
           ],
           "Resource": "*"
       }
   ]
}

Dit maakt het mogelijk om berichten te ontvangen, berichten te verwijderen of wachtrijattributen op te vragen (niet meer per ongeluk de wachtrij verwijderen of nieuwe berichten aanmaken). Het enige overgebleven zwakke punt hier is de "Resource": "*" term die de Lambda toestaat om te interageren met elke wachtrij. We kunnen dit oplossen door het beleid te beperken tot een bepaalde resource: de SQS waarvan de Lambda de berichten moet lezen. Om dit te doen, moet je de ARN van de SQS verkrijgen, die je kunt krijgen in de AWS-console, zoals getoond in Figuur 9.

Figuur 9: De ARN van een SQS wachtrij ophalen

Door deze ARN-code te schrijven in plaats van het jokerteken '*' in het Resource-veld van het beleid, zorg je ervoor dat de Lambda-functie alleen berichten kan ontvangen, berichten kan verwijderen en de wachtrijattributen van die wachtrij kan krijgen.

Een laatste opmerking komt van de voor de hand liggende vraag: wat als ik berichten van de ene wachtrij wil lezen en berichten naar een andere wachtrij wil sturen, bijvoorbeeld om een derde Lambda-functie aan te roepen? Zou het dan niet contraproductief zijn om de resource te beperken tot de ontvangende wachtrij? Het antwoord daarop is eenvoudig: je kunt meer beleidsregels aan je Lambda-rol toevoegen. Je kunt bijvoorbeeld de toestemming toevoegen om berichten naar een andere wachtrij te sturen, in de trant van:

{
   "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 de toegestane acties en bronnen in je beleid altijd tot de minimale set die je nodig hebt. Zo voorkom je problemen in de toekomst bij het schalen van je applicatie naar productie.

Conclusie

We hebben gezien hoe we robuuste en efficiënte serverloze architecturen in de cloud kunnen bouwen met behulp van Lambda-functies, SQS-service, Step Functions en een restrictieve instelling van machtigingen.

Voor werklasten die piekgevoelig zijn of lange inactiviteitstijden hebben, biedt dit een kosteneffectief en eenvoudig te beheren alternatief voor de inzet van monolithische stukken code in virtuele machines.

In dit artikel hebben we ons ter illustratie gericht op de AWS-omgeving, maar deze opmerkingen zijn ook direct van toepassing op andere cloudserviceproviders zoals MS Azure (zie artikel: beschrijving van een use-case) of Google Cloud.

Vorige
Vorige

5 manieren waarop leidinggevenden impactvolle analyseprojecten mislopen

Volgende
Volgende

Tech Talk: Een serverloze architectuur bouwen in AWS met lambda-functie-interactie (deel 1)