Qu'est-ce qu'Amazon DynamoDB Streams?
Annoncée durant l'été 2015, Amazon DynamoDB Streams est une mise à jour importante apportée au service de base de données NoSQL managée par Amazon.
DynamoDB Streams répond au besoin de certains utilisateurs de consigner les changements apportés à leurs tables DynamoDB (puts, updates, et deletes). Une fois la fonction Streams activée, la base gardera ces informations en mémoire durant 24 heures, ces dernières restant accessibles en quasi temps réel via le jeu d'API mis à disposition par Amazon. Dès lors, il est possible d'utiliser AWS Lambda suite à des événements remontés par Amazon DynamoDB Streams. Ces déclancheurs - ou triggers - vous permettront très simplement de traiter un certain nombre de tâches automatiquement : conditionner le scaling de la base, analyser les changements, demander la mise à jour de certaines données etc.
Introduction
Chez Osones, on est pragmatique: nous partons toujours d'un besoin. Aujourd'hui, on souhaite avoir une base de données d'utilisateurs comprenant nom, prénom et e-mail. Bien. Maintenant, on souhaite automatiser l'envoi d'un e-mail de bienvenue lors de l'insertion d'un nouvel utilisateur dans la base.
- A l'attaque !
Fonctionnement
Pour arriver à ce but, nous allons utiliser les services Amazon suivants :
- DynamoDB
- DynamoDB Streams
- IAM
- Lambda
- CloudWatch Logs
- SES
DynamoDB sera utilisé pour stocker les données, DynamoDB Streams pour déclencher une fonction Lambda qui elle-même enverra un e-mail de bienvenue via SES.
Les autres services - IAM et CloudWatch Logs - sont là pour faire fonctionner le tout.
Pas à pas
Création de la table DynamoDB
On commence donc par créer une table que l'on appelle osones
:
aws dynamodb create-table \
--table-name osones \
--attribute-definitions AttributeName=email,AttributeType=S \
--key-schema AttributeName=email,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
--stream-specification StreamEnabled=True,StreamViewType=NEW_IMAGE
Commande qui nous renvoie ceci :
{
"TableDescription": {
"TableArn": "arn:aws:dynamodb:eu-west-1:xxxxxxxxxxxx:table/osones",
"AttributeDefinitions": [
{
"AttributeName": "email",
"AttributeType": "S"
}
],
"ProvisionedThroughput": {
"NumberOfDecreasesToday": 0,
"WriteCapacityUnits": 1,
"ReadCapacityUnits": 1
},
"TableSizeBytes": 0,
"TableName": "osones",
"TableStatus": "CREATING",
"StreamSpecification": {
"StreamViewType": "NEW_IMAGE",
"StreamEnabled": true
},
"LatestStreamLabel": "2016-02-26T13:23:10.193",
"KeySchema": [
{
"KeyType": "HASH",
"AttributeName": "email"
}
],
"ItemCount": 0,
"CreationDateTime": 1456492990.19,
"LatestStreamArn": "arn:aws:dynamodb:eu-west-1:xxxxxxxxxxxx:table/osones/stream/2016-02-26T13:23:10.193"
}
}
Notez bien la ligne suivante, on en aura besoin plus tard :
"LatestStreamArn": "arn:aws:dynamodb:eu-west-1:xxxxxxxxxxxx:table/osones/stream/2016-02-26T13:23:10.193"
Voilà, la table est créée.
Role IAM Lambda
Pour que DynamoDB Streams puisse parler à Lambda, et que notre fonction Lambda puisse elle-même parler à SES, il faut donner les bons droits à notre fonction.
On commence par créer un rôle IAM :
cat > iam-role.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {"Service":"lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
EOF
Puis :
aws iam create-role \
--role-name lambda-dynamodb \
--assume-role-policy-document file://iam-role.json
Une fois que le rôle est créé, on peut écrire la policy :
cat > iam-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:DescribeStream",
"dynamodb:ListStreams",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ses:*"
],
"Resource": "*"
}
]
}
EOF
Puis :
aws iam put-role-policy \
--role-name lambda-dynamodb \
--policy-name lambda-dynamodb \
--policy-document file://iam-policy.json
Fonction Lambda de test
Nous sommes prêts, on peut maintenant écrire une petite fonction Lambda de test, pour voir si tout fonctionne.
cat > envoi_email.js << EOF
console.log('=== Début ===');
exports.handler = function(event, context) {
event.Records.forEach(function(record) {
console.log(record.eventID);
console.log(record.eventName);
console.log('DynamoDB Record: %j', record.dynamodb);
});
context.succeed("== Fin ==");
};
EOF
Une fois que cette fonction est écrite, on la zip :
zip envoi_email.zip envoi_email.js
Puis on peut envoyer cette fonction dans Lambda :
aws lambda create-function \
--function-name envoi_email \
--runtime nodejs \
--role arn:aws:iam::xxxxxxxxxxxx:role/lambda-dynamodb \
--handler envoi_email.handler \
--zip-file fileb://envoi_email.zip
Commande qui nous renvoie ceci :
{
"FunctionName": "envoi_email",
"CodeSize": 357,
"MemorySize": 128,
"FunctionArn": "arn:aws:lambda:eu-west-1:xxxxxxxxxxxx:function:envoi_email",
"Handler": "envoi_email.handler",
"Role": "arn:aws:iam::xxxxxxxxxxxx:role/lambda-dynamodb",
"Timeout": 3,
"LastModified": "2016-02-26T13:55:00.389+0000",
"Runtime": "nodejs",
"Description": ""
}
Cette fonction permet juste de d'afficher dans CloudWatch Logs les paramètres.
Il faut maintenant faire en sorte que cette fonction soit déclenchée lorsqu'un nouvel enregistrement est inséré dans notre table DynamoDB.
aws lambda create-event-source-mapping \
--event-source-arn arn:aws:dynamodb:eu-west-1:xxxxxxxxxxxx:table/osones/stream/2016-02-26T13:23:10.193 \
--function-name envoi_email \
--enabled \
--starting-position LATEST
Ce qui renvoie :
{
"UUID": "2a3b31a5-ed00-48e5-a5c2-5fe052639d87",
"StateTransitionReason": "User action",
"LastModified": 1456495304.215,
"BatchSize": 100,
"EventSourceArn": "arn:aws:dynamodb:eu-west-1:xxxxxxxxxxxx:table/osones/stream/2016-02-26T13:23:10.193",
"FunctionArn": "arn:aws:lambda:eu-west-1:xxxxxxxxxxxx:function:envoi_email",
"State": "Creating",
"LastProcessingResult": "No records processed"
}
Voilà, nous sommes fins prêt, nous allons pouvoir ajouter un enregistrement dans notre base
Enregistrement d'un item
On commence donc par créer un petit fichier JSON :
cat > item.json << EOF
{
"email": {"S": "alexis.gunst@osones.com"},
"nom": {"S": "GÜNST HORN"},
"prenom": {"S": "Alexis"}
}
EOF
Puis on injecte ça dans DynamoDB :
aws dynamodb put-item \
--table-name osones \
--item file://item.json \
--return-consumed-capacity TOTAL
Normalement, cette action a déclenché la fonction Lambda. Allons vérifier.
Lecture des logs
Lambda écrit dans CloudWatch Logs. Allons donc voir ce qu'il en est.
On commence par lister les streams disponibles :
aws logs describe-log-streams \
--log-group-name /aws/lambda/envoi_email \
--query 'logStreams[*].logStreamName' \
--output table
----------------------------------------------------------
| DescribeLogStreams |
+--------------------------------------------------------+
| 2016/02/26/[$LATEST]1b3343a141eb437a9f642444b6b6e360 |
+--------------------------------------------------------+
Il suffit donc de lire ce stream :
aws logs get-log-events \
--log-group-name /aws/lambda/envoi_email \
--log-stream-name '2016/02/26/[$LATEST]1b3343a141eb437a9f642444b6b6e360'\
--query 'events[*].message'
Ce qui renvoie ceci :
[
"START RequestId: e00bc024-a1a4-47f3-9921-409f0dc2f086 Version: $LATEST\n",
"2016-02-26T14:44:47.826Z\te00bc024-a1a4-47f3-9921-409f0dc2f086\t=== Début ===\n",
"2016-02-26T14:44:47.827Z\te00bc024-a1a4-47f3-9921-409f0dc2f086\ta701e6bd4ec41f8454f6d6e5f309836a\n",
"2016-02-26T14:44:47.827Z\te00bc024-a1a4-47f3-9921-409f0dc2f086\tINSERT\n",
"2016-02-26T14:44:47.827Z\te00bc024-a1a4-47f3-9921-409f0dc2f086\tDynamoDB Record: {\"Keys\":{\"email\":{\"S\":\"alexis.gunst@osones.com\"}},\"NewImage\":{\"nom\":{\"S\":\"GÜNST HORN\"},\"prenom\":{\"S\":\"Alexis\"},\"email\":{\"S\":\"alexis.gunst@osones.com\"}},\"SequenceNumber\":\"1300000000003218571175\",\"SizeBytes\":82,\"StreamViewType\":\"NEW_IMAGE\"}\n",
"END RequestId: e00bc024-a1a4-47f3-9921-409f0dc2f086\n",
"REPORT RequestId: e00bc024-a1a4-47f3-9921-409f0dc2f086\tDuration: 0.58 ms\tBilled Duration: 100 ms \tMemory Size: 128 MB\tMax Memory Used: 27 MB\t\n"
]
Victoire ! On voit qu'on a bien reçu l'événement d'insertion. Et si on met un peu en forme les données reçues, on obtient ceci :
{
"Keys": {
"email": {
"S": "alexis.gunst@osones.com"
}
},
"NewImage": {
"nom": {
"S": "GÜNST HORN"
},
"prenom": {
"S": "Alexis"
},
"email": {
"S": "alexis.gunst@osones.com"
}
},
"SequenceNumber": "1300000000003218571175",
"SizeBytes": 82,
"StreamViewType": "NEW_IMAGE"
}
On a donc tout ce qu'il faut pour envoyer un e-mail.
Envoi d'un e-mail
Maintenant qu'on a réussi à lier DynamoDB à Lambda via DynamoDB Streams, il ne reste plus qu'à modifier un peu notre fonction Lambda et faire en sorte qu'elle puisse envoyer un émail via SES.
Nouvelle fonction lambda :
cat > envoi_email.js << EOF
var AWS = require('aws-sdk') ;
exports.handler = function(event, context) {
console.log(event);
event.Records.forEach(function(record) {
if (record.eventName == 'INSERT') {
var ses = new AWS.SES();
var email = record.dynamodb.Keys.email.S ;
var prenom = record.dynamodb.NewImage.prenom.S ;
var nom = record.dynamodb.NewImage.nom.S ;
var params = {
Destination: { ToAddresses: [ email ] },
Message: {
Body: {
Text: {
Data: 'Bienvenue '+ prenom + ' ' + nom + ' !\n\nMerci de valider votre e-mail: https://s3-eu-west-1.amazonaws.com/agh-osones/valider.html?email='+email,
Charset: 'UTF-8'
}
},
Subject: {
Data: 'Bienvenue '+ prenom + ' ' + nom + ' !',
Charset: 'UTF-8'
}
},
Source: 'alexis.gunst@osones.com',
};
console.log(params);
ses.sendEmail (params, function(err, data) {
if (err) context.fail (err) ;
else context.succeed (data) ;
});
}
});
};
EOF
On zipe :
zip envoi_email.js envoi_email.zip
Et on envoie :
Mise à jour de la fonction
aws lambda update-function-code \
--function-name envoi_email \
--zip-file fileb://envoi_email.zip
Test final
On peut recommencer notre test (ajout d'un nouvel utilisateur)... Et oh ! Miracle ! Le mail est bien parti !
Alexis GÜNST HORN
C'est à vous de jouer !
Questions, remarques, suggestions... Contactez-nous directement sur Twitter sur @osones !
Pour discuter avec nous de vos projets, nous restons disponibles directement via contact@osones.com ou via le chat !
- Encore un peu de temps ? Parcourez nos dossiers :
A la découverte d'AWS Lambda
A la découverte d'Amazon DynamoDB Streams
Container as a Service avec Amazon EC2 Container Service (ECS)
On a testé Amazon Elastic File System (EFS)
Rejoignez VOTRE groupe LinkedIn dès maintenant : Utilisateurs Francophones d'Amazon Web Services (AWS).
Découvrez les derniers articles d'alter way
- kubevpn
- Kubernetes 1.32
- re:Invent 2024 : AWS mise tout sur l'IA pour son Cloud
- OVHcloud Summit 2024 : L’innovation au cœur d’un cloud souverain et performant
- Big Data & AI Paris 2024 : L'IA au cœur de toutes les transformations.
- Un retour vers l'open-source ? Vos outils DevOps préférés et leurs equivalents open-source.