Salut tout le monde, Jules Martin ici, de retour sur agntmax.com !
Aujourd’hui, je veux parler de quelque chose qui me tracasse, et probablement beaucoup d’entre vous aussi, depuis environ un an : l’augmentation progressive des coûts de l’infrastructure cloud, en particulier quand il s’agit des fonctions sans serveur. Nous avons tous été séduits par le rêve du « payez pour ce que vous utilisez », et pendant longtemps, cela semblait être une réalité. Mais dernièrement, j’ai vu les factures grimper, parfois de manière incompréhensible, même lorsque les schémas de trafic semblaient stables. C’est comme si nous étions grignotés par la flexibilité même que nous avons adoptée. Alors, explorons quelque chose de très spécifique et d’actualité : Dominer le Monstre Sans Serveur : Dévoiler et Réduire les Coûts Cachés d’AWS Lambda.
Mon propre parcours dans ce domaine a commencé il y a environ six mois. Nous avons un microservice central qui gère l’authentification des utilisateurs et la gestion des sessions. Il est presque entièrement construit sur AWS Lambda, API Gateway, DynamoDB et Cognito. Pendant longtemps, les coûts étaient parfaitement prévisibles. Puis, l’été dernier, notre facture AWS pour ce service spécifique a sauté d’environ 15 %. Pas de nouvelles fonctionnalités, pas de pics de trafic significatifs. J’ai d’abord attribué cela à une fluctuation saisonnière ou à un petit bug que je n’avais pas encore découvert. Mais lorsque la facture du mois suivant est arrivée encore plus élevée, j’ai su que je devais enquêter. Ce n’était pas juste un incident isolé; c’était une tendance, et cela nous coûtait réellement de l’argent.
L’Illusion des Niveaux « Gratuits » et la Réalité des Invocations « Minimes »
Un des principaux arguments de vente des solutions sans serveur, surtout pour les startups ou les petites équipes, est le généreux niveau gratuit. Et il est généreux ! Un million d’invocations gratuites par mois pour Lambda, plus une quantité significative de temps de calcul. Le problème, c’est qu’à mesure que votre application grandit, ces « gratuites » invocations disparaissent plus vite qu’une part de pizza lors d’un meetup tech. Ce qui est souvent négligé, ce sont le volume considérable d’invocations minimes, apparemment sans importance, qui s’accumulent. Pensez aux tâches cron, aux vérifications de santé internes ou même aux mécanismes de réessai d’autres services. Chacune de ces invocations compte.
Mon enquête sur notre service d’authentification a révélé exactement cela. Nous avions une fonction Lambda, appelons-la auth-token-refresher, conçue pour rafraîchir périodiquement les tokens de service internes. Elle était programmée pour s’exécuter toutes les cinq minutes. Cela semble inoffensif, n’est-ce pas ? 288 invocations par jour. Multipliez cela par 30 jours, et vous obtenez 8 640 invocations par mois. Ajoutez nos environnements de développement, de staging et de production, et tout à coup, cela fait plus de 25 000 invocations juste pour une petite tâche de maintenance. Nous avions une douzaine de telles fonctions. Tout à coup, nos invocations « minimes » n’étaient plus si minimes que ça.
Trouver les Coupables : Les Métriques CloudWatch sont Vos Meilleurs Amis
La première étape pour dompter cette bête est de savoir où va votre argent. AWS CloudWatch est indispensable ici. Ne vous contentez pas de regarder le tableau de bord de facturation à un niveau élevé ; explorez les métriques spécifiques pour vos fonctions Lambda.
Voici sur quoi je me suis concentré :
- Invocations : C’est la métrique la plus simple. Des comptages d’invocations élevés pour des fonctions qui ne gèrent pas le trafic utilisateur direct sont des signaux d’alerte immédiats.
- Durée : Combien de temps chaque invocation fonctionne-t-elle ? Des durées plus longues signifient des coûts de calcul plus élevés.
- Utilisation de la Mémoire : Avez-vous surprovisionné de la mémoire pour vos fonctions ? Vous payez pour ce que vous allouez, pas pour ce que vous utilisez.
- Taux d’Erreur : Des taux d’erreur élevés peuvent entraîner des réessais, ce qui signifie plus d’invocations et des cycles de calcul gaspillés.
Pour notre auth-token-refresher, j’ai examiné sa métrique `Invocations`. En effet, elle tournait comme une horloge, toutes les cinq minutes. La durée était minimale, seulement environ 50ms. Mais le volume considérable contribuait à notre coût d’invocation global.
Exemple Pratique 1 : Consolider et Planifier de Manière Plus Intelligente
La solution pour auth-token-refresher et plusieurs autres fonctions de maintenance similaires était étonnamment simple : la consolidation. Au lieu d’avoir des fonctions Lambda individuelles déclenchées par des événements CloudWatch (ou EventBridge de nos jours) sur des horaires séparés, j’ai créé une seule fonction Lambda appelée « Maintenance Runner ».
Ce « Maintenance Runner » est déclenché par une seule règle d’événement CloudWatch, disons, une fois par heure. À l’intérieur de ce runner, j’ai un simple dispatch qui vérifie l’heure actuelle et exécute les tâches nécessaires. Par exemple :
import os
import datetime
def lambda_handler(event, context):
current_hour = datetime.datetime.now().hour
current_minute = datetime.datetime.now().minute
# Tâche 1 : Rafraîchir le token d'auth (s'exécutait toutes les 5 mins)
if current_minute % 10 == 0: # Exécuter maintenant toutes les 10 minutes
print("Exécution du rafraîchissement du token d'auth...")
# Appeler la logique réelle de rafraîchissement du token ou une autre fonction interne
refresh_auth_token()
# Tâche 2 : Nettoyer les anciens journaux (s'exécutait toutes les heures)
if current_hour % 1 == 0 and current_minute == 0: # Exécuter au début de l'heure
print("Exécution du nettoyage des journaux...")
cleanup_old_logs()
# Tâche 3 : Vérifier le statut du service externe (s'exécutait toutes les 30 mins)
if current_minute == 0 or current_minute == 30:
print("Vérification du statut du service externe...")
check_external_service()
return {
'statusCode': 200,
'body': 'Tâches de maintenance exécutées.'
}
def refresh_auth_token():
# ... logique de rafraîchissement de token réelle ...
pass
def cleanup_old_logs():
# ... logique de nettoyage des journaux réelle ...
pass
def check_external_service():
# ... logique de vérification du service externe réelle ...
pass
Ce simple changement a immédiatement réduit le nombre d’invocations pour ces tâches de maintenance de centaines de milliers par mois à quelques milliers. Les économies réalisées étaient tangibles, pas seulement en termes d’invocations Lambda, mais aussi en ce qui concerne l’ingestion des journaux CloudWatch et les appels à API Gateway (si l’un de ces appels était exposé via API Gateway).
Le Piège de la Surprovisionnement de Mémoire
Cela représente un autre facteur de coût subtil qui est souvent négligé. Lorsque vous créez une fonction Lambda, vous allouez une certaine quantité de mémoire (par exemple, 128Mo, 256Mo, 512Mo). Vous payez pour cette mémoire allouée, peu importe combien votre fonction en utilise réellement. De plus, la puissance CPU augmente proportionnellement à l’allocation de mémoire. Ainsi, si vous allouez 1 Go de mémoire pour un simple script Python qui n’a besoin que de 128 Mo, vous ne payez pas seulement trop cher pour la mémoire ; vous payez aussi trop cher pour les cycles CPU dont elle n’a pas besoin.
J’ai appris cela à mes dépens avec une fonction Lambda de traitement de données qui était initialement configurée avec 1 Go de mémoire « juste au cas où ». Quand j’ai regardé ses métriques CloudWatch pour l’utilisation de la mémoire, elle restait constamment en dessous de 200 Mo, même pendant les charges de pointe. Nous payions essentiellement pour 800 Mo de RAM inutilisée et le boost CPU correspondant.
Exemple Pratique 2 : Optimiser l’Allocation de Mémoire avec Lambda Power Tuning
Déterminer manuellement le réglage de mémoire optimal peut être fastidieux. Vous devez déployer, tester, surveiller, ajuster et répéter. Heureusement, il existe un excellent outil en open source appelé AWS Lambda Power Tuning (développé par Alex Casalboni chez AWS) qui facilite ce processus.
C’est une application sans serveur qui vous aide à visualiser et identifier le réglage de mémoire optimal pour vos fonctions Lambda en fonction des coûts et des performances. Vous la déployez sur votre compte AWS, puis vous pouvez l’utiliser pour tester vos fonctions.
Voici comment cela fonctionne généralement :
- Vous déployez l’outil Power Tuning via le Serverless Application Repository ou SAM.
- Vous invoquez une machine d’état (créée par l’outil) avec l’ARN de votre fonction Lambda et une charge utile.
- La machine d’état invoque votre Lambda plusieurs fois avec différentes configurations de mémoire (par exemple, 128 Mo, 256 Mo, 512 Mo, 1024 Mo, etc.).
- Elle analyse ensuite les journaux d’exécution et fournit une visualisation montrant les compromis entre coûts et rapidité pour chaque réglage de mémoire.
Pour ma fonction Lambda de traitement de données, le test avec le Power Tuner a montré que 256 Mo était le juste milieu pour le coût, avec une dégradation de performance négligeable par rapport à 1 Go. Nous avons immédiatement réduit l’allocation de mémoire à 256 Mo, ce qui a entraîné une réduction de 75 % des coûts de calcul pour cette fonction spécifique. Ce n’était pas un coup unique ; j’ai depuis fait en sorte que ce soit une pratique standard de faire passer de nouvelles fonctions ou celles réévaluées par cet outil.
Pour l’utiliser, après le déploiement, vous commenceriez typiquement la machine d’état avec quelque chose comme ceci (en ajustant l’ARN et la charge utile) :
aws stepfunctions start-execution \
--state-machine-arn "arn:aws:states:REGION:ACCOUNT_ID:stateMachine:powerTuningStateMachine" \
--input '{ "lambdaARN": "arn:aws:lambda:REGION:ACCOUNT_ID:function:YOUR_FUNCTION_NAME", "num": 100, "payload": {}, "parallel": 5 }'
La sortie fournit un graphique clair, montrant exactement où vos coûts et votre vitesse se croisent pour une performance optimale. C’est un changement significatif pour l’optimisation des coûts.
La Verbosité des Journaux et les Démarrages à Froid
Deux autres domaines qui vous surprennent souvent sont la verbosité des journaux et les démarrages à froid. Les journaux CloudWatch ne sont pas gratuits. Chaque ligne imprimée par votre fonction Lambda est ingérée et stockée, et vous payez pour cela. Bien qu’un bon journal soit crucial pour le débogage, une journalisation trop verbeuse (par exemple, imprimer des objets entiers ou répéter inutilement des messages de statut) peut rapidement gonfler votre facture de journaux CloudWatch.
J’ai trouvé quelques fonctions qui enregistraient le corps complet de la requête HTTP à chaque invocation. Bien que cela soit utile pour le développement initial, en production, ce n’était qu’un bruit et un coût. Un rapide ajustement pour n’enregistrer que les métadonnées essentielles (ID de requête, code d’état, point de terminaison) a considérablement réduit notre ingestion de journaux.
Les démarrages à froid, bien qu’ils ne soient pas un “coût” direct de la même manière, impactent l’expérience utilisateur et peuvent indirectement entraîner plus de réessais ou des durées de facturation plus longues si votre fonction doit attendre des ressources. Bien qu’AWS ait fait des progrès significatifs pour réduire les temps de démarrage à froid, optimiser la taille du bundle de votre fonction et éviter une logique d’initialisation complexe en dehors du gestionnaire peut encore faire une différence. Pour les fonctions critiques sensibles à la latence, la concurrence provisionnée est une option, mais sachez que vous payez pour cette concurrence allouée même lorsqu’elle est inoccupée.
Exemple Pratique 3 : Journalisation Intelligente et Variables d’Environnement
Pour la journalisation, la solution la plus simple est souvent la meilleure. Utilisez des variables d’environnement pour contrôler les niveaux de journalisation. En Python, par exemple, vous pouvez faire ceci :
import os
import logging
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO').upper()
logging.basicConfig(level=LOG_LEVEL)
logger = logging.getLogger()
def lambda_handler(event, context):
logger.debug("Ceci est un message de débogage, visible seulement si LOG_LEVEL est DEBUG")
logger.info("Traitement de l'événement : %s", event.get('request_id'))
try:
# ... logique de la fonction ...
logger.debug("Fini de traiter pour request_id : %s", event.get('request_id'))
return {
'statusCode': 200,
'body': 'Succès'
}
except Exception as e:
logger.error("Erreur lors du traitement de request_id %s : %s", event.get('request_id'), str(e), exc_info=True)
return {
'statusCode': 500,
'body': 'Erreur'
}
En définissant LOG_LEVEL sur INFO en production et DEBUG en développement/staging, vous pouvez considérablement réduire votre facture CloudWatch Logs sans sacrifier l’observabilité lorsque vous en avez besoin.
Un autre conseil est de prêter attention à ce qui est initialisé en dehors du gestionnaire. Tout code directement dans le scope global de votre fonction Lambda s’exécutera pendant le démarrage à froid. Si vous avez des opérations coûteuses comme le pooling de connexions à la base de données ou des imports de grandes bibliothèques, envisagez de les différer jusqu’à ce qu’elles soient réellement nécessaires dans le gestionnaire, ou assurez-vous qu’elles sont efficacement mises en cache pour les invocations chaudes suivantes.
Actions Concrètes pour Votre Croisade de Coûts Serverless
Bien, nous avons couvert pas mal de choses. Voici un résumé des étapes pratiques que vous pouvez entreprendre dès maintenant pour commencer à réduire ces coûts Lambda sournois :
- Surveillez sans relâche : Ne vous contentez pas de jeter un œil à votre facture AWS globale. Explorez les métriques CloudWatch pour les Invocations, la Durée et l’Utilisation de la Mémoire pour chaque fonction Lambda. Configurez des alarmes pour des pics inattendus.
- Consolidez les tâches cron : Si vous avez de nombreuses petites fonctions Lambda programmées, envisagez de les regrouper en un seul “Mainteneur” qui répartit les tâches selon un calendrier moins fréquent. Cela réduit considérablement le nombre d’invocations.
- Optimisez l’allocation de mémoire : Utilisez des outils comme AWS Lambda Power Tuning pour trouver le réglage de mémoire optimal pour vos fonctions. Ne vous contentez pas de deviner et de sur-provisionner. N’oubliez pas que plus de mémoire signifie plus de CPU, et vous payez pour les deux.
- Contrôlez la verbosité des journaux : Mettez en œuvre des niveaux de journalisation pilotés par des variables d’environnement (par exemple,
INFOpour la production,DEBUGpour le développement). Évitez de journaliser l’intégralité des corps de requête ou un état interne excessif en production. Votre facture CloudWatch Logs vous en sera reconnaissante. - Examinez les fonctions inutilisées : Auditez périodiquement vos fonctions Lambda. Y a-t-il d’anciennes fonctions, expérimentales ou obsolètes, encore actives et entraînant des coûts ? Supprimez-les !
- Surveillez la taille des packages : Des packages de déploiement plus petits signifient des démarrages à froid plus rapides et moins de coûts de stockage. Incluez uniquement les dépendances nécessaires.
- Comprenez votre modèle de tarification : Relisez la page de tarification Lambda. Comprenez comment les invocations, les GB-secondes et le transfert de données sont facturés. La connaissance est un pouvoir, surtout quand il s’agit de votre portefeuille.
Dominer le monstre serverless ne consiste pas à éviter le serverless ; il s’agit d’être intelligent et intentionnel dans notre utilisation. La flexibilité et la scalabilité sont inestimables, mais sans vigilance adéquate, ces “petits” coûts peuvent s’accumuler et représenter une part significative de votre budget. Allez-y, surveillez, optimisez et économisez !
C’est tout pour moi aujourd’hui. Faites-moi savoir dans les commentaires si vous avez d’autres conseils ou astuces pour l’optimisation des coûts Lambda !
🕒 Published: