Modification de l'environnement d'exécution - AWS Lambda

Modification de l'environnement d'exécution

Vous pouvez utiliser des extensions internes pour modifier le processus de runtime. Les extensions internes ne sont pas des processus séparés. Elles s'exécutent dans le cadre du processus du runtime.

Lambda fournit des variables d'environnement spécifiques du langage, que vous pouvez définir pour ajouter des options et des outils au runtime. Lambda intègre également des scripts encapsuleurs qui lui permettent de déléguer le démarrage du runtime à votre script. Vous pouvez créer un script encapsuleur pour personnaliser le comportement de démarrage du runtime.

Variables d'environnement spécifiques à un langage

Lambda prend en charge des méthodes de configuration pour permettre le pré-chargement de code lors de l'initialisation de la fonction via les variables d'environnement spécifiques du langage suivantes :

  • JAVA_TOOL_OPTIONS : sur Java, Lambda prend en charge cette variable d'environnement pour définir des variables de ligne de commande supplémentaires dans Lambda. Cette variable d'environnement vous permet de spécifier l'initialisation des outils, en particulier le lancement d'agents de langage de programmation natifs ou Java à l'aide des options agentlib ou javaagent.

  • NODE_OPTIONS – Sur Node.js 10x et versions ultérieures, Lambda prend en charge cette variable d'environnement.

  • DOTNET_STARTUP_HOOKS – Sur .NET Core 3.1 et versions ultérieures, cette variable d'environnement précise le chemin d'accès d'un assembly (dll) que Lambda peut utiliser.

L'utilisation de variables d'environnement spécifiques à un langage est la méthode privilégiée pour définir les propriétés de démarrage.

Exemple : Intercept Lambda appelle avec javaagent

La machine virtuelle Java (JVM) tente de localiser la classe spécifiée avec le paramètre javaagent sur la JVM, puis appelle sa méthode premain avant le point d'entrée de l'application.

L'exemple suivant utilise Byte Buddy, une bibliothèque permettant de créer et de modifier des classes Java pendant le runtime d'une application Java sans l'aide d'un compilateur. Byte Buddy propose une API supplémentaire pour générer des agents Java. Dans cet exemple, la classe Agent intercepte chaque appel de la méthode handleRequest effectué sur la classe RequestStreamHandler. Cette classe est utilisée en interne dans le runtime pour encapsuler les appels du gestionnaire.

import com.amazonaws.services.lambda.runtime.RequestStreamHandler; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; import net.bytebuddy.matcher.ElementMatchers; import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String agentArgs, Instrumentation inst) { new AgentBuilder.Default() .with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager()) .type(ElementMatchers.isSubTypeOf(RequestStreamHandler.class)) .transform((builder, typeDescription, classLoader, module) -> builder .method(ElementMatchers.nameContains("handleRequest")) .intercept(Advice.to(TimerAdvice.class))) .installOn(inst); } }

Dans l'exemple précédent, l'agent utilise la méthode TimerAdvice. TimerAdvice mesure le nombre de millisecondes utilisées avec l'appel de méthode et consigne le temps et les détails de la méthode, tels que le nom et les arguments transmis.

import static net.bytebuddy.asm.Advice.AllArguments; import static net.bytebuddy.asm.Advice.Enter; import static net.bytebuddy.asm.Advice.OnMethodEnter; import static net.bytebuddy.asm.Advice.OnMethodExit; import static net.bytebuddy.asm.Advice.Origin; public class TimerAdvice { @OnMethodEnter static long enter() { return System.currentTimeMillis(); } @OnMethodExit static void exit(@Origin String method, @Enter long start, @AllArguments Object[] args) { StringBuilder sb = new StringBuilder(); for (Object arg : args) { sb.append(arg); sb.append(", "); } System.out.println(method + " method with args: " + sb.toString() + " took " + (System.currentTimeMillis() - start) + " milliseconds "); } }

La méthode TimerAdvice ci-dessus a les dépendances suivantes.

*'com.amazonaws'*, *name*: *'aws-lambda-java-core'*, *version*: *'1.2.1'* *'net.bytebuddy'*, *name*: *'byte-buddy-dep'*, *version*: *'1.10.14'* *'net.bytebuddy'*, *name*: *'byte-buddy-agent'*, *version*: *'1.10.14'*

Après avoir créé une couche contenant le JAR de l'agent, vous pouvez transmettre le nom du JAR à la JVM du runtime en définissant une variable d'environnement.

JAVA_TOOL_OPTIONS=-javaagent:"/opt/ExampleAgent-0.0.jar"

Après avoir appelé la fonction avec {key=lambdaInput}, vous pouvez rechercher la ligne suivante dans les journaux :

public java.lang.Object lambdainternal.EventHandlerLoader$PojoMethodRequestHandler.handleRequest (java.lang.Object,com.amazonaws.services.lambda.runtime.Context) method with args: {key=lambdaInput}, lambdainternal.api.LambdaContext@4d9d1b69, took 106 milliseconds

Exemple : Ajout d'un hook d'arrêt au processus de runtime de la JVM

Lorsqu'une extension est enregistrée lors d'un événement Shutdown, le processus d'exécution utilise jusqu'à 500 ms pour gérer l'arrêt normal. Vous pouvez vous raccorder au processus de runtime ; lorsque la JVM lance son processus d'arrêt, elle démarre tous les hooks enregistrés. Pour enregistrer un hook d'arrêt, vous devez effectuer un enregistrement en tant qu'extension. Vous n'avez pas besoin d'effectuer un enregistrement explicite pour l'événement Shutdown car celui-ci est automatiquement envoyé à l'environnement d'exécution.

import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String agentArgs, Instrumentation inst) { // Register the extension. // ... // Register the shutdown hook addShutdownHook(); } private static void addShutdownHook() { // Shutdown hooks get up to 500 ms to handle graceful shutdown before the runtime is terminated. // // You can use this time to egress any remaining telemetry, close open database connections, etc. Runtime.getRuntime().addShutdownHook(new Thread(() -> { // Inside the shutdown hook's thread we can perform any remaining task which needs to be done. })); } }

Exemple : Récupération de InvokedFunctionArn

@OnMethodEnter static long enter() { String invokedFunctionArn = null; for (Object arg : args) { if (arg instanceof Context) { Context context = (Context) arg; invokedFunctionArn = context.getInvokedFunctionArn(); } } }

Scripts encapsuleurs

Vous pouvez créer un script encapsuleur pour personnaliser le comportement de démarrage du runtime de votre fonction Lambda. Un script encapsuleur vous permet de définir des paramètres de configuration qui ne peuvent pas être définis via des variables d'environnement spécifiques à un langage.

Note

Les appels peuvent échouer si le script encapsuleur ne démarre pas correctement le processus de runtime.

Les runtimes Lambda suivants prennent en charge les scripts encapsuleurs :

  • Node.js 14.x

  • Node.js 12.x

  • Node.js 10.x

  • Python 3.9

  • Python 3.8

  • Ruby 2.7

  • Java 11

  • Java 8 (java8.al2)

  • .NET Core 3.1

Lorsque vous utilisez un script encapsuleur pour votre fonction, Lambda démarre le runtime en utilisant votre script. Lambda envoie à votre script le chemin d'accès de l'interpréteur et tous les arguments d'origine pour le démarrage du runtime standard. Votre script peut étendre ou transformer le comportement de démarrage du programme. Par exemple, le script peut injecter et modifier des arguments, définir des variables d'environnement ou capturer des métriques, des erreurs et d'autres informations de diagnostic.

Vous spécifiez le script en définissant la valeur de la variable d'environnement AWS_LAMBDA_EXEC_WRAPPER comme étant le chemin vers le système de fichiers d'un fichier binaire ou d'un script exécutable.

Exemple : Créer et utiliser un script encapsuleur avec Python 3.8

Dans l'exemple suivant, vous créez un script encapsuleur pour démarrer l'interpréteur Python avec l'option -X importtime. Lorsque vous exécutez la fonction, Lambda génère une entrée de journal pour indiquer la durée de chaque importation.

Pour créer et utiliser un script encapsuleur avec Python 3.8

  1. Pour créer le script encapsuleur, collez le code suivant dans un fichier nommé importtime_wrapper:

    #!/bin/bash # the path to the interpreter and all of the originally intended arguments args=("$@") # the extra options to pass to the interpreter extra_args=("-X" "importtime") # insert the extra options args=("${args[@]:0:$#-1}" "${extra_args[@]}" "${args[@]: -1}") # start the runtime with the extra options exec "${args[@]}"
  2. Pour donner au script des autorisations d'exécution, entrez chmod +x importtime_wrapper à partir de la ligne de commande.

  3. Déployez le script en tant que couche Lambda.

  4. Créez une fonction à l'aide de la console Lambda.

    1. Ouvrez la console Lambda.

    2. Sélectionnez Create function (Créer une fonction).

    3. Sous Informations de base, pour Nom de la fonction, entrez wrapper-test-function.

    4. Pour Environnement d'exécution, sélectionnez Python 3.8.

    5. Sélectionnez Create function (Créer une fonction).

  5. Ajoutez la couche à votre fonction.

    1. Choisissez votre fonction, puis choisissez l'option Code si elle n'est pas déjà sélectionnée.

    2. Choisissez Add a layer (Ajouter une couche).

    3. Sous Choose a layer (Choisir une couche), choisissez le nom et la version de la couche compatible que vous avez créée précédemment.

    4. Choisissez Add (Ajouter).

  6. Ajoutez le code et la variable d'environnement à votre fonction.

    1. Dans l'éditeur de code, de fonction, collez le code de fonction suivant :

      import json def lambda_handler(event, context): # TODO implement return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
    2. Choisissez Enregistrer.

    3. Sous Variables d'environnement, choisissez Modifier.

    4. Choisissez Ajouter une variable d'environnement.

    5. Pour Clé, entrez AWS_LAMBDA_EXEC_WRAPPER.

    6. Pour le champ Value (Valeur), entrez /opt/importtime_wrapper.

    7. Choisissez Enregistrer.

  7. Pour exécuter la fonction, choisissez Tester.

    Votre script encapsuleur ayant lancé l'interpréteur Python avec l'option -X importtime, les journaux indiquent le temps requis pour chaque importation. Exemples :

    ... 2020-06-30T18:48:46.780+01:00 import time: 213 | 213 | simplejson 2020-06-30T18:48:46.780+01:00 import time: 50 | 263 | simplejson.raw_json ...