Serverless sur AWS
Objectifs
• Créer des fonctions Lambda
• Déployer des API REST avec API Gateway
• Gérer la persistance avec DynamoDB
• Automatiser les déploiements avec AWS SAM
• Comprendre les concepts fondamentaux du serverless
glossaire
● Serverless: Modèle d'exécution où le cloud gère l'infrastructure
● Lambda: Service AWS de compute serverless (fonctions as a service)
● API Gateway: Service AWS pour créer et gérer des API
● DynamoDB: Base de données NoSQL serverless d'AWS
● SAM: Serverless Application Model - framework IaC pour serverless
● CloudFormation: Service AWS d'infrastructure as code
● Cold Start: Délai au premier démarrage d'une fonction Lambda
● Warm Start: Exécution d'une fonction Lambda déjà initialisée
● IAM: Identity and Access Management - gestion des permissions AWS
● CORS: Cross-Origin Resource Sharing - mécanisme de sécurité web
Partie 1 : pratique de la console AWS
Objectif de cette partie
Créer une application TODO complète en utilisant l'interface web AWS Console. Cette approche vous permet de comprendre visuellement chaque composant avant d'automatiser avec du code.
Prérequis
● Compte AWS actif (free tier suffit)
● Accès à la console AWS
● Navigateur web moderne
● Postman ou curl pour tester l'API
Créer la table DynamoDB
On commence par DynamoDB car nos fonctions Lambda auront besoin de la table. Il faut toujours créer les dépendances avant les ressources qui les utilisent
Se connecter à la console AWS
Dans la barre de recherche en haut, tapez "DynamoDB" et cliquez sur le service
Cliquez sur le bouton orange "Créer une table" (Create table)
Configurez la table avec ces paramètres :
Nom de la table : Tasks
Clé de partition : taskId (Type: String)
Settings :
- Table settings : Customize settings
- Read/write capacity settings : On-demand → parfait pour le développement, pas besoin de provisionner de capacité
- Laissez tous les autres paramètres par défaut
Cliquez sur "Créer une table" (Create table) en bas de page
Attendez que le statut passe à "Active" (environ 30 secondes)
Vérification avec un test de validation :
Dans l'onglet Explore items, cliquez sur "Create item". Créez un item de test : taskId="test-001", title="Ma première tâche". Si l'item apparaît dans la liste, votre table fonctionne ! Vous pouvez le supprimer ensuite.
Créer la première fonction Lambda (Create Task)
Nous allons créer une fonction Lambda pour créer des tâches dans DynamoDB
Dans la console AWS, recherchez et ouvrez "Lambda"
Cliquez sur "Créer une fonction" (Create function)function
Sélectionnez "Créer à partir de zéro" (Author from scratch)scratch
Configurez la fonction :
- Nom : CreateTaskFunction
- Runtime : Python 3.
11 (ou 3.12 si disponible)xx - Architecture : x86_64
- Permissions > change default execution role :
Select existing “LabRole” roleLabRole
create
Cliquezpuis sur "Créer une fonction"
Attendezattendre que la fonction soit créée (quelques secondes)
Dans l'éditeur de code qui apparaît, remplacez tout le code par
import json
import boto3
import uuid
from datetime import datetime
# Initialisation du client DynamoDB
# boto3 est préinstallé dans Lambda, pas besoin de l'installer
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Tasks')
def lambda_handler(event, context):
"""
Fonction Lambda pour créer une nouvelle tâche
Cette fonction :
1. Extrait les données du corps de la requête HTTP
2. Génère un ID unique pour la tâche
3. Enregistre la tâche dans DynamoDB
4. Retourne la tâche créée avec headers CORS
Args:
event: Événement API Gateway contenant body, headers, etc.
context: Contexte d'exécution Lambda (non utilisé ici)
Returns:
dict: Réponse HTTP avec statusCode, headers et body JSON
"""
try:
# 1. Parser le corps de la requête JSON
# API Gateway envoie le body comme string, il faut le parser
body = json.loads(event.get('body', '{}'))
# 2. Extraire et valider les champs requis
title = body.get('title')
description = body.get('description', '')
# Validation : le titre est obligatoire
if not title:
return {
'statusCode': 400,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*' # CORS
},
'body': json.dumps({
'error': 'Le champ title est obligatoire'
})
}
# 3. Créer l'objet tâche avec les métadonnées
task = {
'taskId': str(uuid.uuid4()), # ID unique généré
'title': title,
'description': description,
'status': 'pending', # Statut par défaut
'createdAt': datetime.now().isoformat(), # Timestamp ISO
'updatedAt': datetime.now().isoformat()
}
# 4. Enregistrer dans DynamoDB
table.put_item(Item=task)
# 5. Retourner la réponse de succès
return {
'statusCode': 201, # 201 = Created
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*' # Permet CORS
},
'body': json.dumps({
'message': 'Tâche créée avec succès',
'task': task
})
}
except Exception as e:
# Gestion des erreurs
print(f"Erreur: {str(e)}") # Log dans CloudWatch
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({
'error': 'Erreur serveur interne',
'details': str(e)
})
}
Configuration des permissions DynamoDB
Cliquez sur "Deploy" (bouton orange en haut) pour sauvegarder le code
Dans l’environnement AWS “test”, vous n’avez pas la main sur la configuration des droits. Vous sélectionnerez le paramétrage suivant. Les lignes suivantes sont à but pédagogique
- Cliquez sur l'onglet "Configuration"
- Dans le menu de gauche, cliquez sur "Permissions"
- Cliquez sur le nom du rôle (lien bleu, quelque chose comme CreateTaskFunction-role-xxxxx)
- Une nouvelle page IAM s'ouvre : cliquez sur "Ajouter des permissions" > "Attacher des stratégies"
- Dans la barre de recherche, tapez "DynamoDB" et cochez "AmazonDynamoDBFullAccess"
- Cliquez sur "Ajouter des permissions"
En production, utilisez des permissions plus restreintes ! AmazonDynamoDBFullAccess donne accès à toutes les tables. Pour ce TP, c'est acceptable, mais en prod, créez une policy qui ne donne accès qu'à la table Tasks.
Validation :
Cliquez sur l'onglet "Test"
Cliquez sur "Créer un nouvel événement de test"
Nommez-le "CreateTaskTest" et collez ce JSON :
{
"body": "{\"title\": \"Apprendre Lambda\", \"description\": \"Comprendre les fonctions serverless\"}"
}
Cliquez sur "Tester"
Vous devriez voir "Execution result: succeeded" en vert
Dans la section "Response", vérifiez que statusCode est 201
Allez dans DynamoDB > Tables > Tasks > Explorer items
Vous devriez voir votre tâche créée !
Créer les autres fonctions Lambda
Créez 4 autres fonctions Lambda en suivant le même processus que l'étape 2.
Fonction 2 : GetTasksFunction (Lister toutes les tâches)
import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Tasks')
def lambda_handler(event, context):
"""Liste toutes les tâches"""
try:
response = table.scan()
tasks = response.get('Items', [])
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({
'tasks': tasks,
'count': len(tasks)
})
}
except Exception as e:
print(f"Erreur: {str(e)}")
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'error': 'Erreur serveur interne'})
}
Fonction 3 : GetTaskFunction (Récupérer une tâche par ID)
import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Tasks')
def lambda_handler(event, context):
"""Récupère une tâche spécifique par son ID"""
try:
task_id = event.get('pathParameters', {}).get('taskId')
if not task_id:
return {
'statusCode': 400,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'error': 'taskId manquant'})
}
response = table.get_item(Key={'taskId': task_id})
if 'Item' not in response:
return {
'statusCode': 404,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'error': 'Tâche non trouvée'})
}
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'task': response['Item']})
}
except Exception as e:
print(f"Erreur: {str(e)}")
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'error': 'Erreur serveur interne'})
}
Fonction 4 : UpdateTaskFunction
import json
import boto3
from datetime import datetime
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Tasks')
def lambda_handler(event, context):
"""Met à jour une tâche existante"""
try:
task_id = event.get('pathParameters', {}).get('taskId')
body = json.loads(event.get('body', '{}'))
if not task_id:
return {
'statusCode': 400,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'error': 'taskId manquant'})
}
# Construction de l'expression de mise à jour
update_expression = "SET updatedAt = :updatedAt"
expression_values = {':updatedAt': datetime.now().isoformat()}
expression_names = {}
if 'title' in body:
update_expression += ", title = :title"
expression_values[':title'] = body['title']
if 'description' in body:
update_expression += ", description = :description"
expression_values[':description'] = body['description']
if 'status' in body:
update_expression += ", #status = :status"
expression_values[':status'] = body['status']
expression_names['#status'] = 'status'
update_params = {
'Key': {'taskId': task_id},
'UpdateExpression': update_expression,
'ExpressionAttributeValues': expression_values,
'ReturnValues': 'ALL_NEW'
}
if expression_names:
update_params['ExpressionAttributeNames'] = expression_names
response = table.update_item(**update_params)
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({
'message': 'Tâche mise à jour',
'task': response['Attributes']
})
}
except Exception as e:
print(f"Erreur: {str(e)}")
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'error': 'Erreur serveur interne'})
}
Fonction 5 : DeleteTaskFunction
import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Tasks')
def lambda_handler(event, context):
"""Supprime une tâche de la base de données"""
try:
task_id = event.get('pathParameters', {}).get('taskId')
if not task_id:
return {
'statusCode': 400,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'error': 'taskId manquant'})
}
table.delete_item(Key={'taskId': task_id})
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({
'message': 'Tâche supprimée avec succès'
})
}
except Exception as e:
print(f"Erreur: {str(e)}")
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'error': 'Erreur serveur interne'})
}
À ce stade, vous devriez avoir 5 fonctions Lambda créées : CreateTaskFunction, GetTasksFunction, GetTaskFunction, UpdateTaskFunction, DeleteTaskFunction
Créer l'API Gateway
Maintenant que nos fonctions Lambda sont prêtes, créons l'API REST qui les exposera.
Création de l'API
Dans la console AWS, recherchez et ouvrez "API Gateway"
Cliquez sur "Créer une API"
Trouvez "REST API" (pas "REST API privée") et cliquez sur "Créer"
Configurez l'API :
- Protocole : REST
- Nouvelle API : New API
- Nom de l'API : TasksAPI
- Description : API REST pour gérer des tâches
- Type de point de terminaison : Regional
Cliquez sur "Créer une API"
Configuration des routes et méthodes
Nous allons créer la structure suivante :
/tasks
├── GET → GetTasksFunction (lister toutes les tâches)
├── POST → CreateTaskFunction (créer une tâche)
└── /{taskId}
├── GET → GetTaskFunction (récupérer une tâche)
├── PUT → UpdateTaskFunction (mettre à jour une tâche)
└── DELETE → DeleteTaskFunction (supprimer une tâche)
Créer la ressource /tasks
Cliquez sur "Créer une ressource"
Nom de ressource : tasks
Chemin de ressource : /tasks “/”
Cochez "Activer CORS pour l'API Gateway"
Cliquez sur "Créer une ressource"
Ajouter la méthode POST sur /tasks
Sélectionnez la ressource "/tasks" dans l'arbre
Cliquez sur "Créer une méthode"
Sélectionnez "POST" dans la liste déroulante
Configurez l'intégration :
- Type d'intégration : Fonction Lambda
- Utiliser l'intégration proxy Lambda : ☑ Coché (IMPORTANT !)
- Région Lambda : votre région (ex: us-east-1)
- Fonction Lambda : CreateTaskFunction
- Cliquez sur "Enregistrer"
- Une popup demande la permission : cliquez sur "OK"
Cliquez sur "Enregistrer"
Une popup demande la permission : cliquez sur "OK"
L'intégration proxy est ESSENTIELLE ! Elle transfère directement l'événement complet à Lambda (body, headers, params, etc.). Sans proxy, vous devriez configurer manuellement les mapping templates. Toujours cocher cette option !
Ajouter les autres méthodes
Répétez le processus pour GET sur /tasks → GetTasksFunction
Ensuite, créez la ressource /{taskId} :
Sélectionnez la ressource "/tasks"
Créer une ressource
Nom : taskId
Chemin : {taskId} (avec les accolades !)
Cochez "Activer CORS pour l'API Gateway"
Créer
Créez les 3 méthodes suivantes sur /{taskId} :
- GET → GetTaskFunction
- PUT → UpdateTaskFunction
- DELETE → DeleteTaskFunction
Déployer l'API
Cliquez sur "Déployer l'API"
Étape de déploiement : [Nouvelle étape]
Nom de l'étape : prod
Description : Production stage
Cliquez sur "Déployer"
Validation
Après le déploiement, vous verrez l'URL d'invocation
URL d'invocation : https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod
Vos endpoints sont :
POST https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/tasks
GET https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/tasks
GET https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/tasks/{taskId}
PUT https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/tasks/{taskId}
DELETE https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/tasks/{taskId}
Copiez cette URL, vous en aurez besoin pour les tests !
Tester l'API complète
Nous allons tester tous les endpoints avec curl ou Postman
Test 1 : Créer une tâche (POST)
curl -X POST https://VOTRE_URL.execute-api.us-east-1.amazonaws.com/prod/tasks \
-H "Content-Type: application/json" \
-d '{
"title": "Acheter du lait",
"description": "Lait demi-écrémé au supermarché"
}'
Résultat attendu : 201 Created avec l'objet task contenant un taskId
Test 2 : Lister toutes les tâches (GET)
curl https://VOTRE_URL.execute-api.us-east-1.amazonaws.com/prod/tasks
Résultat attendu : 200 OK avec un tableau tasks
Test 3 : Mettre à jour une tâche (PUT)
curl -X PUT https://VOTRE_URL.execute-api.us-east-1.amazonaws.com/prod/tasks/TASK_ID \
-H "Content-Type: application/json" \
-d '{"status": "completed"}'
Résultat attendu : 200 OK avec la tâche mise à jour
Si tous vos tests passent, votre application serverless fonctionne parfaitement ! Vous avez créé une API REST complète avec Lambda, API Gateway et DynamoDB. Passons maintenant à l'automatisation avec SAM.