Lab : Dépannage Lambda
Objectif
Diagnostiquer et corriger des fonctions Lambda défaillantes en utilisant une approche méthodique.
Exercice 1 : Timeout
Code Problématique
# buggy_timeout.py
import time
import json
def lambda_handler(event, context):
"""
Cette fonction a un problème de timeout
"""
print("Starting processing...")
# Simulation d'un traitement long
data = event.get('data', [])
for item in data:
# Traitement très lent
time.sleep(2) # 2 secondes par item !
print(f"Processed: {item}")
return {
'statusCode': 200,
'body': json.dumps({'processed': len(data)})
}
Configuration
- Timeout : 3 secondes
- Payload :
{"data": ["a", "b", "c", "d", "e"]}
Questions
- Pourquoi cette fonction échoue-t-elle ?
- Calculez le temps total nécessaire
- Proposez 2 solutions différentes
💡 Solution
1. Pourquoi :
- 5 items × 2 secondes = 10 secondes minimum
- Timeout = 3 secondes
- La fonction est interrompue avant de finir
2. Temps nécessaire :
- Traitement : 5 × 2s = 10s
- Overhead Lambda : ~100ms
- Total : ~10.1 secondes
3. Solutions :
Solution A : Augmenter le timeout
aws lambda update-function-configuration \
--function-name buggy-timeout \
--timeout 30
Solution B : Optimiser le code
import json
from concurrent.futures import ThreadPoolExecutor
def process_item(item):
# Traitement (simulé avec sleep réduit)
return f"Processed: {item}"
def lambda_handler(event, context):
data = event.get('data', [])
# Traitement parallèle
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(process_item, data))
return {
'statusCode': 200,
'body': json.dumps({'results': results})
}
Exercice 2 : Erreur de Permissions
Code
# buggy_permissions.py
import json
import boto3
def lambda_handler(event, context):
"""
Cette fonction tente de lire depuis S3
"""
s3 = boto3.client('s3')
bucket = event['bucket']
key = event['key']
try:
response = s3.get_object(Bucket=bucket, Key=key)
content = response['Body'].read().decode('utf-8')
return {
'statusCode': 200,
'body': json.dumps({'content': content[:100]})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
Erreur Reçue
{
"statusCode": 500,
"body": "{\"error\": \"An error occurred (AccessDenied) when calling the GetObject operation: Access Denied\"}"
}
Questions
- Quelle est la cause de l'erreur ?
- Quelle politique IAM faut-il ajouter ?
- Écrivez la commande AWS CLI pour corriger
💡 Solution
1. Cause :
Le rôle d'exécution Lambda n'a pas la permission s3:GetObject
2. Politique IAM à ajouter :
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
3. Commandes CLI :
# Créer la politique
cat > s3-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}]
}
EOF
# Attacher au rôle
aws iam put-role-policy \
--role-name lambda-role \
--policy-name S3ReadAccess \
--policy-document file://s3-policy.json
Exercice 3 : Out of Memory
Code Problématique
# buggy_memory.py
import json
def lambda_handler(event, context):
"""
Cette fonction a un problème de mémoire
"""
size = event.get('size', 1000000)
# Créer une énorme liste en mémoire
data = []
for i in range(size):
data.append({
'id': i,
'name': f'Item {i}',
'description': 'A' * 1000 # 1KB par item
})
return {
'statusCode': 200,
'body': json.dumps({'count': len(data)})
}
Configuration
- Memory : 128 MB
- Payload :
{"size": 100000}(100K items × ~1KB = ~100MB)
Questions
- Pourquoi la fonction échoue-t-elle ?
- Quelle mémoire recommandez-vous ?
- Comment optimiser sans augmenter la mémoire ?
💡 Solution
1. Pourquoi :
- 100,000 items × ~1KB = ~100MB de données
- Overhead Python/Lambda : ~30-50MB
- Total : ~130-150MB > 128MB configuré
2. Mémoire recommandée :
- Minimum : 256 MB (avec marge)
- Recommandé : 512 MB (pour safety)
aws lambda update-function-configuration \
--function-name buggy-memory \
--memory-size 512
3. Optimisation sans plus de mémoire :
import json
def lambda_handler(event, context):
size = event.get('size', 1000000)
# Traitement par batch avec générateur
def process_batch(start, end):
for i in range(start, end):
yield {'id': i, 'processed': True}
# Traiter par chunks de 10000
batch_size = 10000
total_processed = 0
for batch_start in range(0, size, batch_size):
batch_end = min(batch_start + batch_size, size)
# Traiter le batch (ne garde pas tout en mémoire)
for item in process_batch(batch_start, batch_end):
total_processed += 1
# Libérer la mémoire entre les batches
return {
'statusCode': 200,
'body': json.dumps({'processed': total_processed})
}
Exercice 4 : Handler Not Found
Configuration
- Fichier :
my_function.py - Handler configuré :
lambda_function.handler
Erreur
{
"errorMessage": "Unable to import module 'lambda_function': No module named 'lambda_function'",
"errorType": "Runtime.ImportModuleError"
}
Questions
- Quelle est l'erreur de configuration ?
- Quelle valeur de handler est correcte ?
- Listez 3 erreurs courantes de handler
💡 Solution
1. Erreur :
Le handler est configuré pour lambda_function.handler mais le fichier s'appelle my_function.py
2. Handler correct :
my_function.handler
Format : <nom_fichier_sans_extension>.<nom_fonction>
3. Erreurs courantes :
- Mauvais nom de fichier (
lambda_functionvsmy_function) - Mauvais nom de fonction (
handlervslambda_handler) - Fichier dans un sous-dossier (
src/handler.mainau lieu dehandler.main) - Extension incluse par erreur (
handler.py.main) - Casse incorrecte (
Handlervshandler)
Exercice 5 : Diagnostic Complet
Scénario
Une fonction Lambda retourne intermittentement des erreurs. Voici les informations disponibles :
Logs CloudWatch :
START RequestId: abc-123
[INFO] Connecting to database...
END RequestId: abc-123
REPORT RequestId: abc-123 Duration: 29987.45 ms Billed Duration: 30000 ms Memory Size: 256 MB Max Memory Used: 89 MB Init Duration: 1234.56 ms
START RequestId: def-456
[INFO] Connecting to database...
[ERROR] Connection timed out
END RequestId: def-456
REPORT RequestId: def-456 Duration: 30000.00 ms Billed Duration: 30000 ms Memory Size: 256 MB Max Memory Used: 67 MB
Configuration :
- Timeout : 30 secondes
- Memory : 256 MB
- VPC : Oui (private subnet)
Questions
Remplissez le tableau de diagnostic :
| Élément | Observation | Problème Potentiel | Action |
|---|---|---|---|
| Duration | ? | ? | ? |
| Memory | ? | ? | ? |
| Init Duration | ? | ? | ? |
| Error message | ? | ? | ? |
| VPC | ? | ? | ? |
💡 Solution
| Élément | Observation | Problème Potentiel | Action |
|---|---|---|---|
| Duration | ~30s (timeout) | Connexion DB lente/bloquée | Timeout DB + retry |
| Memory | 67-89 MB / 256 MB | OK, pas de problème mémoire | - |
| Init Duration | 1234ms | Cold start élevé | Optimiser ou Provisioned |
| Error message | Connection timed out | DB inaccessible | Vérifier SG, NAT |
| VPC | Private subnet | Pas de NAT pour sortie Internet? | Ajouter NAT Gateway |
Diagnostic : La fonction est dans un VPC privé sans NAT Gateway. Elle ne peut pas atteindre la base de données externe.
Solutions :
- Ajouter un NAT Gateway au subnet privé
- Ou mettre la DB dans le même VPC
- Ou utiliser VPC Endpoints si la DB est un service AWS
- Ajouter des timeouts de connexion dans le code
Exercice 6 : Écrire des Logs de Qualité
Code Avant (mauvais logs)
def lambda_handler(event, context):
print("Starting")
# ... code ...
print("Done")
return result
Exercice
Réécrivez le code avec des logs de qualité qui facilitent le dépannage :
💡 Solution
import json
import logging
# Configuration du logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# Log structuré de l'entrée
logger.info(json.dumps({
'action': 'start',
'request_id': context.aws_request_id,
'event_keys': list(event.keys()),
'remaining_time_ms': context.get_remaining_time_in_millis()
}))
try:
# Votre logique
user_id = event.get('user_id')
logger.info(f"Processing user: {user_id}")
result = process_user(user_id)
# Log de succès
logger.info(json.dumps({
'action': 'complete',
'request_id': context.aws_request_id,
'user_id': user_id,
'status': 'success'
}))
return {'statusCode': 200, 'body': json.dumps(result)}
except Exception as e:
# Log d'erreur détaillé
logger.error(json.dumps({
'action': 'error',
'request_id': context.aws_request_id,
'error_type': type(e).__name__,
'error_message': str(e),
'user_id': event.get('user_id')
}), exc_info=True)
return {'statusCode': 500, 'body': json.dumps({'error': str(e)})}
Livrable
Document à Produire
- Solutions aux 6 exercices
- Tableau de diagnostic complété
- Code corrigé pour chaque bug
- Recommandations de bonnes pratiques
Critères d'Évaluation
| Critère | Points |
|---|---|
| Diagnostic correct | /25 |
| Solutions appropriées | /25 |
| Code corrigé fonctionnel | /25 |
| Qualité des logs | /15 |
| Documentation | /10 |
| Total | /100 |
Semaine suivante : MODULE 13 - Observabilité