Lambda Internals - Partie 2: Aller plus loin

Exploration des bibliothèques d'exécution AWS Lambda

Photo de Jim Beaudoin

Le développement sans serveur est tout simplement le meilleur. Double-cliquez, téléchargez votre code et vous avez terminé, n'est-ce pas? La plupart des gens sont plus qu'heureux d'en rester là. Si vous n'êtes pas la plupart des gens et que vous souhaitez explorer Lambda, cet article est pour vous.

Dans l'article précédent, nous avons obtenu un shell dans le conteneur Lambda, téléchargé l'environnement d'exécution Lambda et découvert ses composants:

  • bootstrap.py - le code python qui enveloppe notre gestionnaire.
  • awslambda / runtime.so - un objet partagé compatible python, bootstrap.py, l'utilise pour, enfin, à peu près tout.
  • liblambda * .so - À son tour, runtime.so utilise d'autres objets partagés. Nous allons nous concentrer sur liblambdaruntime.so, en charge de la gestion lourde de la logique Lambda.

Nous avons également eu du plaisir à bricoler bootstrap.py. Cette fois, nous allons retrousser nos manches et plonger dans les bibliothèques binaires de l'environnement d'exécution Lambda. Nous allons explorer le système de facturation de Lambda et (alerte au spoiler) nous amuser avec des temps morts Lambda.

«Oh, les endroits où tu iras! Il y a du plaisir à faire! Il ya des points à noter. Il y a des jeux à gagner. »- Dr. Seuss. Photo de Joshua Earle

Explorer les bibliothèques

Les bibliothèques (liblambda * .so) sont compilées avec des symboles, vous pouvez donc en apprendre beaucoup sur les bibliothèques en parcourant simplement les noms des symboles. De plus, runtime.so expose beaucoup de ces fonctions en les important et en les encapsulant afin qu'un script Python (bootstrap.py dans notre cas) puisse en utiliser certaines. Comme c'est pratique!

Liste des fonctions partielles du désassemblage de liblambdaruntime.so. Merci mon Dieu pour les symboles.

L’une des choses que j’ai voulu vérifier au début était les coulisses du système de facturation Lambda, et en regardant simplement les noms des fonctions, j’avais des expériences que je voulais essayer. Mais d’abord, parlons un peu de la facturation Lambda.

Lambda Billing

Lambda a un modèle de tarification basé sur le temps, et sans entrer dans tous les détails, l'essentiel est que plus votre Lambda est longue à fonctionner, plus vous payez. Lorsque vous appelez une Lambda, vous pouvez facilement identifier son début et sa fin dans CloudWatch Logs, ainsi que sa durée et sa durée facturée.

CloudWatch enregistre pour un Lambda. Vous pouvez voir à la fois la durée du Lambda et la durée facturée

Cependant, le scénario est plus compliqué. Considérez le Lambda suivant:

Sur une exécution typique, la durée de ce Lambda devrait être petite (la durée facturée devrait presque toujours être de 100 ms). Mais que se passe-t-il lors de la première invocation? Ou au démarrage à froid (où le module est réimporté)?

Lambda enregistre un démarrage à froid. La durée est beaucoup plus élevée qu'une invocation régulière

Les tests empiriques montrent que la durée de la première invocation Lambda (ou démarrage à froid) contient la durée d'initialisation. Mais je voulais vérifier comment Lambda implémentait cela.

Importer les bibliothèques

Dans bootstrap.py, il existe des appels aux fonctions suivantes, importés à partir des bibliothèques binaires:

  • lambda_runtime.receive_start () ou lambda_runtime.receive_invoke () - lorsqu'un nouveau déclencheur est reçu.
  • lambda_runtime.report_done () - chaque fois qu'un Lambda est terminé

Le moment est peut-être venu de vous donner plus de détails sur le slicer auquel je faisais référence dans l'article précédent. Le slicer est le composant de Lambda chargé de l'allocation de l'exécution à différents utilisateurs Lambdas s'exécutant sur le conteneur. Ces fonctions envoient une notification au slicer (et aux autres composants de gestion Lambda) lorsque des exécutions Lambda sont effectuées ou reçoivent des informations sur les exécutions nouvellement lancées.

Donc, après avoir identifié les appels de lambda_runtime et savoir ce qu’est le slicer, j’ai eu quelque chose à essayer: importer la bibliothèque d’exécution moi-même et s’amuser avec elle! (Ces expériences sont la façon dont j'ai découvert des choses sur le slicer, principalement en lisant le démontage et quelques essais et erreurs). Le test que je veux partager avec vous est également le premier essai que j'ai tenté: appeler lambda_runtime.report_done () depuis l'intérieur de mon Lambda. C'est le code que j'ai utilisé:

La chose surprenante que j'ai trouvée est que lors de l'exécution de cet exemple, mon code s'est arrêté après avoir uniquement imprimé «Beginning». Puis, quand j'ai de nouveau déclenché mon Lambda, il a repris son exécution exactement à l'endroit où nous l'avions laissée - et a imprimé «Après le premier fait»! (J'ai ajouté le sommeil parce que mon Lambda réussissait parfois à tirer une «empreinte» avant que le coupe-papier ne la mette en pause). Cela s'est produit encore et encore jusqu'à la fin de l'exécution de Lambda.

Journaux Cloudwatch pour l'exécution Lambda. Notez que nous avons plusieurs identifiants de demande pour le même Lambda!

Cela m'a donc rendu la chose définitive: la trancheuse nous facture pour autant que notre Lambda dispose de temps processeur. Cela signifie que notre durée facturée est composée de deux parties:

  1. Temps d'initialisation du module (uniquement lors du premier appel / démarrage à froid)
  2. Notre durée de fonction réelle

Éviter les délais d'attente Lambda

En plus d'être très cool, cette découverte a une utilisation pratique (bien… pratique est dans l'oeil du spectateur, mais c'est certainement intéressant): la gestion des délais d'attente Lambda! Considérez le Lambda suivant:

J'ai déclenché le Lambda une fois et il s'est arrêté à la ligne 13. Ensuite, j'ai attendu quelque temps et je l'ai réactivé. Le résultat est que le temps restant que la méthode de l’objet de contexte renvoie est égal à 0, mais que Lambda n’a pas expiré! Le délai d’expiration du Lambda a été réinitialisé car il s’agit d’une invocation différente et nous avons maintenant doublé le délai d’expiration de notre Lambda (et notre facture AWS, bien sûr)! Un cas utile pour cela, par exemple, pourrait être une boucle qui traite de nombreux enregistrements et arrive parfois à expiration. Nous pouvons maintenant vérifier si nous approchons d'un délai d'attente, et si c'est le cas, appelez lambda_runtime.report_done () et attendez que le prochain déclencheur reçoive l'exécution exactement à l'endroit où nous nous sommes arrêtés!

Le journal Cloudwatch de l'invocation Lambda. Temps restant: 0

Pendant que je travaillais sur le problème, AWS peut fournir une fonctionnalité réelle basée sur ce comportement, dans laquelle un utilisateur peut suspendre son Lambda et reprendre à partir de ce même emplacement lors de sa prochaine invocation. Cela pourrait être utile non seulement pour le traitement de grandes quantités de données et le traitement des délais d'attente au milieu. Un autre cas d’utilisation peut être, par exemple, suspendre votre Lambda pendant l’attente d’un résultat coûteux IO / d’une autre tâche, au lieu de payer pour le temps d’inactivité de votre Lambda! Le feront-ils? Je ne sais pas Est-ce ultra cool? Defo.

Il y a cependant un inconvénient à tout cela. Comme il s'agit d'une méthode simpliste, les deux invocations suivantes de Lambda échoueront avec une erreur interne d'Amazon. Je suis sûr que l’on peut résoudre ce problème aussi avec un peu d’effort, mais pour le moment, c’était suffisant pour moi.

Conclusion

Nous avons beaucoup appris sur les composants internes d’AWS Lambda. Nous avons exploré les bibliothèques binaires dans l'environnement d'exécution et le système de facturation Lambda. Nous avons également importé la bibliothèque d'exécution Lambda et l'avons utilisée pour gérer les délais d'attente! Cependant, il reste encore beaucoup à découvrir, sur AWS et d'autres fournisseurs. Dans l'attente des prochains défis, si vous avez des demandes, faites-le-moi savoir!

J'ai également mis à jour la bibliothèque open source contenant les différentes expériences que j'ai menées. J'espère que vous la trouverez utile!

Chez Epsagon, nous développons un outil de surveillance sur mesure pour les applications sans serveur. Vous utilisez sans serveur et souhaitez en savoir plus? Visitez notre site Internet!