AWS Lambda est un service AWS qui permet d’exécuter du code. Cette exécution peut être déclenchée via des événements provenant des nombreux services AWS (S3, SNS, DynamoDB, …) ou invoquée via un appel explicite (SDK, API AWS ou API Gateway). Actuellement, les langages supportés sont le Javascript, Java et Python.

Les premiers cas d’usages évidents sont par exemple le traitement d’images ou la validation de données. Ce service est tout particulièrement intéressant si l’on ne souhaite pas se préoccuper d’infrastructure. Il est alors possible d’imaginer des usages bien plus avancés. Et c’est de ce type de service1 qu’est né le dernier concept d’architecture : serverless. Ce modèle d’architecture abuse des services des fournisseurs de cloud pour ne plus avoir besoin de déployer et d’administrer des serveurs, VM ou conteneurs.

Implémentation

Le fichier hello_world.js ci-dessous est une implémentation en Javascript d’une fonction Lambda :

console.log('Loading function');

exports.handler = function(event, context, callback) {
    console.log('Received event:', JSON.stringify(event, null, 2));
    callback(null, "Hello world");
};

Configuration et déploiement

Autorisation IAM

Ce code ne sera pas déployé sur un serveur (comme on aurait pu le faire traditionnellement). Alors comment consulter les logs générés ? Pour cela, nous allons utiliser le service CloudWatch logs d’Amazon. Il faut donc attribuer les bons droits à la fonction Lambda via un rôle décrit dans un fichier lambdas-role.json :

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Principal": {"Service": "lambda.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }
}

Pour créer ce rôle, nous allons utiliser le client AWS :

$ aws iam create-role \
  --role-name myproject-lambdas \
  --assume-role-policy-document file://./lambdas-role.json

{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "lambda.amazonaws.com"
                }
            }
        },
        "RoleId": "AROAJHHOVJSU7VOSOH5WA",
        "CreateDate": "2016-06-16T22:12:19.866Z",
        "RoleName": "myproject-lambdas",
        "Path": "/",
        "Arn": "arn:aws:iam::client-id:role/myproject-lambdas"
    }
}

Remarque : ARN du rôle contient votre identifiant client à la place de la chaine client-id et sera utile pour le déploiement de la fonction.

Une fois le rôle créé, il faut lui attribuer les droits nécessaires via un fichier de policy lambdas-policy.json :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Resource": "*",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Effect": "Allow"
        }
    ]
}

Ces droits sont ajoutés au rôle via la commande suivante :

$ aws iam put-role-policy \
  --role-name myproject-lambdas \
  --policy-name myproject-lambdas-policy \
  --policy-document file://./lambdas-policy.json

Déploiement

Pour déployer la fonction Lambda, il faut l’empaqueter dans un fichier zip :

$ zip hello_world.zip hello_world.js

Puis, elle est envoyé au service AWS Lambda avec cette commande (notez la présence de l’ARN du rôle précédemment créé) :

$ aws lambda create-function \
  --function-name MyProjectHelloWorldLambdaFunction \
  --zip-file fileb://./hello_world.zip \
  --role arn:aws:iam::client-id:role/myproject-lambdas \
  --handler hello_world.handler \
  --runtime nodejs4.3

{
    "CodeSha256": "91HtK/J+gJ/sQ82nht7tmgbt+5vN3u/vK4PlzgRuoq8=",
    "FunctionName": "MyProjectHelloWorldLambdaFunction",
    "CodeSize": 285,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:eu-west-1:client-id:function:MyProjectHelloWorldLambdaFunction",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::client-id:role/myproject-lambdas",
    "Timeout": 3,
    "LastModified": "2016-06-16T22:23:25.651+0000",
    "Handler": "hello_world.handler",
    "Runtime": "nodejs4.3",
    "Description": ""
}

Si vous rencontrez l’erreur A client error (InvalidParameterValueException) occurred when calling the CreateFunction operation: The role defined for the function cannot be assumed by Lambda., c’est sûrement que l’ajout des droits n’est pas encore effectif. Il faut alors attendre quelques secondes et réessayer.

Exécution

Il est possible d’exécuter la fonction Lambda avec le client AWS :

$ aws lambda invoke \
  --invocation-type RequestResponse \
  --function-name MyProjectHelloWorldLambdaFunction \
  output.txt

{
    "StatusCode": 200
}

$ cat output.txt
"Hello world"

Les logs sont accessibles via CloudWatch logs :

# recherche du stream
$ aws logs describe-log-streams \
   --log-group-name /aws/lambda/MyProjectHelloWorldLambdaFunction \
   --query 'logStreams[*].logStreamName' \
   --output text

2016/06/16/[$LATEST]1edd6b3d27fc42c9baccb05c08b6cd66

# lecture du stream
$ aws logs get-log-events \
   --log-group-name /aws/lambda/MyProjectHelloWorldLambdaFunction \
   --log-stream-name '2016/06/16/[$LATEST]1edd6b3d27fc42c9baccb05c08b6cd66' \
   --query 'events[*].message'

[
    "2016-06-16T21:54:26.745Z\tundefined\tLoading function\n",
    "START RequestId: fb0f626a-340c-11e6-99e9-1d96d739b0de Version: $LATEST\n",
    "2016-06-16T21:55:03.789Z\tfb0f626a-340c-11e6-99e9-1d96d739b0de\tReceived event: {}\n",
    "END RequestId: fb0f626a-340c-11e6-99e9-1d96d739b0de\n",
    "REPORT RequestId: fb0f626a-340c-11e6-99e9-1d96d739b0de\tDuration: 0.87 ms\tBilled Duration: 100 ms \tMemory Size: 128 MB\tMax Memory Used: 15 MB\t\n"
]

La fonction a pris 0,87 ms de temps de traitement2 et a utilisé 15 Mo sur les 128 Mo attribués.

Passage de paramètre

Avant de s’arrêter sur cette introduction aux fonctions Lambda, voyons comment dynamiser cette exécution avec le passage de paramètre. Voici donc la nouvelle fonction Lambda hello_you.js :

console.log('Loading function');

exports.handler = function(event, context, callback) {
    console.log('Received event:', JSON.stringify(event, null, 2));
    callback(null, "Hello " + event.name);
};

Le déploiement est strictement identique :

$ zip hello_you.zip hello_you.js
$ aws lambda create-function \
  --function-name MyProjectHelloYouLambdaFunction  \
  --zip-file fileb://./hello_you.zip \
  --role arn:aws:iam::client-id/myproject-lambdas  \
  --handler hello_you.handler \
  --runtime nodejs4.3

{
    "CodeSha256": "W9uWZ3zbMS2QwQArJpUKlTXgds9fLZhEFr8Umz0LDPk=",
    "FunctionName": "MyProjectHelloYouLambdaFunction",
    "CodeSize": 285,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:eu-west-1:client-id:function:MyProjectHelloYouLambdaFunction",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::client-id:role/myproject-lambdas",
    "Timeout": 3,
    "LastModified": "2016-06-18T23:58:15.134+0000",
    "Handler": "hello_you.handler",
    "Runtime": "nodejs4.3",
    "Description": ""
}

Les paramètres sont transmis à la fonction Lambda via un fichier json (que l’on nomme input.json pour l’exemple) :

{
  "name":"you"
}

La commande pour exécuter la fonction est presque la même :

$ aws lambda invoke \
  --invocation-type RequestResponse \
  --function-name MyProjectHelloYouLambdaFunction \
  --payload file://./input_you.txt \
  output.txt

$ cat output.txt
"Hello you"

$ aws logs describe-log-streams \
   --log-group-name /aws/lambda/MyProjectHelloYouLambdaFunction \
   --query 'logStreams[*].logStreamName' \
   --output text

2016/06/20/[$LATEST]a3f614b52afb4dee90ad7e26c99960d6

$ aws logs get-log-events \
   --log-group-name /aws/lambda/MyProjectHelloYouLambdaFunction \
   --log-stream-name '2016/06/20/[$LATEST]a3f614b52afb4dee90ad7e26c99960d6' \
   --query 'events[*].message'

[
    "2016-06-20T00:43:57.164Z\tundefined\tLoading function\n",
    "START RequestId: 12431a44-3680-11e6-96f0-d1fa2700947f Version: $LATEST\n",
    "2016-06-20T00:43:57.171Z\t12431a44-3680-11e6-96f0-d1fa2700947f\tReceived event: {\n  \"name\": \"you\"\n}\n",
    "END RequestId: 12431a44-3680-11e6-96f0-d1fa2700947f\n",
    "REPORT RequestId: 12431a44-3680-11e6-96f0-d1fa2700947f\tDuration: 0.77 ms\tBilled Duration: 100 ms \tMemory Size: 128 MB\tMax Memory Used: 31 MB\t\n"
]

Nettoyage

Une fois tous ces tests terminés, il est possible de supprimer les logs, les fonctions lambdas et le rôle avec le client AWS :

$ aws logs delete-log-group \
  --log-group-name /aws/lambda/MyProjectHelloWorldLambdaFunction

$ aws logs delete-log-group \
  --log-group-name /aws/lambda/MyProjectHelloYouLambdaFunction

$ aws lambda delete-function \
  --function-name MyProjectHelloWorldLambdaFunction

$ aws lambda delete-function \
  --function-name MyProjectHelloYouLambdaFunction

$ aws iam delete-role-policy \
  --role-name myproject-lambdas \
  --policy-name myproject-lambdas-policy     

$ aws iam delete-role \
  --role-name myproject-lambdas

Conclusion

Nous venons de voir comment déployer du code exécutable dans le Cloud en quelques lignes de commande. Ce code déployé est scalable et ne nécessite aucune administration d’infrastructure. La balle est dans votre camp pour imaginer ce qu’il est possible de faire avec ce service très simple !

  1. Proposé également chez la concurrence comme Google Cloud Functions, Azure Functions, OpenWhisk ou webtask.io
  2. Facturé 100 ms puisque c’est l’unité minimale de facturation