Aller au contenu principal

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​

  1. Pourquoi cette fonction échoue-t-elle ?
  2. Calculez le temps total nécessaire
  3. 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​

  1. Quelle est la cause de l'erreur ?
  2. Quelle politique IAM faut-il ajouter ?
  3. É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​

  1. Pourquoi la fonction échoue-t-elle ?
  2. Quelle mémoire recommandez-vous ?
  3. 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​

  1. Quelle est l'erreur de configuration ?
  2. Quelle valeur de handler est correcte ?
  3. 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 :

  1. Mauvais nom de fichier (lambda_function vs my_function)
  2. Mauvais nom de fonction (handler vs lambda_handler)
  3. Fichier dans un sous-dossier (src/handler.main au lieu de handler.main)
  4. Extension incluse par erreur (handler.py.main)
  5. Casse incorrecte (Handler vs handler)

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émentObservationProblème PotentielAction
Duration???
Memory???
Init Duration???
Error message???
VPC???
💡 Solution
ÉlémentObservationProblème PotentielAction
Duration~30s (timeout)Connexion DB lente/bloquéeTimeout DB + retry
Memory67-89 MB / 256 MBOK, pas de problème mémoire-
Init Duration1234msCold start élevéOptimiser ou Provisioned
Error messageConnection timed outDB inaccessibleVérifier SG, NAT
VPCPrivate subnetPas 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 :

  1. Ajouter un NAT Gateway au subnet privé
  2. Ou mettre la DB dans le même VPC
  3. Ou utiliser VPC Endpoints si la DB est un service AWS
  4. 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​

  1. Solutions aux 6 exercices
  2. Tableau de diagnostic complété
  3. Code corrigé pour chaque bug
  4. Recommandations de bonnes pratiques

Critères d'Évaluation​

CritèrePoints
Diagnostic correct/25
Solutions appropriées/25
Code corrigé fonctionnel/25
Qualité des logs/15
Documentation/10
Total/100

Semaine suivante : MODULE 13 - Observabilité