Salut tout le monde, Jules Martin ici, de retour sur agntmax.com !
Aujourd’hui, je veux aborder quelque chose qui me taraude, et probablement beaucoup d’entre vous, depuis environ un an : le coût croissant de l’infrastructure cloud, en particulier en ce qui concerne les fonctions sans serveur. Nous avons tous été séduits par le rêve du « payer 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 inexplicable, même lorsque les schémas de trafic semblaient stables. C’est comme si nous étions grignotés par la même flexibilité que nous avions 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 utilisateur 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 bondi 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 trouvé. Mais lorsque la facture du mois suivant est arrivée encore plus élevée, j’ai su que je devais approfondir. Ce n’était pas juste un pic ; c’était une tendance, et cela nous coûtait de l’argent réel.
L’Illusion des Niveaux « Gratuits » et la Réalité des Invocations « Minuscules »
L’un des plus grands atouts du sans serveur, en particulier 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, en plus d’une quantité significative de temps de calcul. Le problème, c’est qu’à mesure que votre application se développe, ces invocations « gratuites » disparaissent plus vite qu’une part de pizza lors d’un meetup tech. Ce qui est souvent négligé, ce sont le volume total d’invocations minuscules, apparemment insignifiantes, qui s’accumulent. Pensez aux tâches cron, aux vérifications de santé internes, ou même aux mécanismes de réessai provenant 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 jetons de service internes. Elle était programmée pour s’exécuter toutes les cinq minutes. Semble inoffensif, non ? 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 soudain cela représente plus de 25,000 invocations juste pour une petite tâche de maintenance. Nous avions une douzaine de telles fonctions. Tout à coup, nos invocations « minuscules » n’étaient plus si petites que ça.
Trouver les Coupables : Les Métriques CloudWatch sont Votre Meilleur Ami
La première étape pour dominer 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 de haut niveau ; explorez les métriques spécifiques de vos fonctions Lambda.
Voici sur quoi je me suis concentré :
- Invocations : C’est la métrique la plus simple. Des comptes d’invocation é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 dure chaque invocation ? Des durées plus longues signifient des coûts de calcul supérieurs.
- Utilisation de la Mémoire : Vous sur-provisionnez-vous de la mémoire pour vos fonctions ? Vous payez pour ce que vous allouez, pas pour ce que vous utilisez.
- Taux d’Erreurs : Des taux d’erreur élevés peuvent entraîner des réessais, ce qui signifie plus d’invocations et de cycles de calcul gaspillés.
Pour notre auth-token-refresher, j’ai examiné sa métrique `Invocations`. En effet, elle s’exécutait comme une horloge, toutes les cinq minutes. La durée était minimale, seulement environ 50 ms. Mais le volume énorme contribuait à notre coût global d’invocation.
Exemple Pratique 1 : Consolider et Programmer 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 « 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 répartiteur 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 jeton d'authentification (s'exécutait toutes les 5 mins)
if current_minute % 10 == 0: # S'exécute maintenant toutes les 10 minutes
print("Exécution du rafraîchissement du jeton d'authentification...")
# Appeler la logique réelle de rafraîchissement du jeton ou une autre fonction interne
refresh_auth_token()
# Tâche 2 : Nettoyer les anciens logs (s'exécutait toutes les heures)
if current_hour % 1 == 0 and current_minute == 0: # S'exécute au début de l'heure
print("Exécution du nettoyage des logs...")
cleanup_old_logs()
# Tâche 3 : Vérifier l'état des services externes (s'exécutait toutes les 30 mins)
if current_minute == 0 or current_minute == 30:
print("Vérification de l'état des services externes...")
check_external_service()
return {
'statusCode': 200,
'body': 'Tâches de maintenance exécutées.'
}
def refresh_auth_token():
# ... logique réelle de rafraîchissement de jeton ...
pass
def cleanup_old_logs():
# ... logique réelle de nettoyage des logs ...
pass
def check_external_service():
# ... logique réelle de vérification des services externes ...
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, non seulement en ce qui concerne les invocations Lambda, mais aussi en ce qui concerne l’ingestion des logs CloudWatch et les appels à l’API Gateway (si l’un d’eux était exposé via API Gateway).
Le Piège de la Sur-Provision de Mémoire
C’est 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, 128 Mo, 256 Mo, 512 Mo). Vous payez pour cette mémoire allouée, peu importe combien votre fonction utilise réellement. De plus, la puissance du CPU évolue proportionnellement à l’allocation de mémoire. Donc, 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 pour la mémoire ; vous payez également pour des cycles CPU supplémentaires dont vous n’avez 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 « au cas où ». Lorsque j’ai regardé ses métriques CloudWatch pour l’utilisation de la mémoire, elle restait systématiquement en dessous de 200 Mo, même en période de forte charge. 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 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 dans votre compte AWS, puis vous pouvez l’utiliser pour tester vos fonctions.
Voici généralement comment cela fonctionne :
- 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 un payload.
- La machine d’état invoque votre Lambda plusieurs fois avec des configurations de mémoire variées (par exemple, 128 Mo, 256 Mo, 512 Mo, 1024 Mo, etc.).
- Elle analyse ensuite les logs d’exécution et fournit une visualisation montrant les compromis de coût et de vitesse pour chaque réglage de mémoire.
Pour ma fonction Lambda de traitement de données, en la passant par le Power Tuner, j’ai découvert que 256 Mo était le meilleur choix en termes de 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 cas isolé ; j’ai depuis pris l’habitude de faire passer les nouvelles fonctions ou celles réévaluées par cet outil.
Pour l’utiliser, après le déploiement, vous commenceriez généralement la machine d’état avec quelque chose comme ceci (en ajustant l’ARN et le payload) :
aws stepfunctions start-execution \
--state-machine-arn "arn:aws:states:RÉGION:ID_COMPTE:stateMachine:powerTuningStateMachine" \
--input '{ "lambdaARN": "arn:aws:lambda:RÉGION:ID_COMPTE:function:NOM_DE_VOTRE_FONCTION", "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.
Verbosity des Logs et Démarrages à Froid
Deux autres domaines qui peuvent souvent vous surprendre sont la verbosité des logs et les démarrages à froid. Les logs CloudWatch ne sont pas gratuits. Chaque ligne que votre fonction Lambda imprime est ingérée et stockée, et vous payez pour cela. Bien qu’une bonne journalisation soit cruciale pour le débogage, une journalisation trop verbeuse (par exemple, imprimer des objets entiers ou répéter des messages de statut inutilement) peut rapidement gonfler votre facture CloudWatch Logs.
J’ai trouvé quelques fonctions qui enregistraient le corps entier des requêtes HTTP à chaque invocation. Bien qu’utile pour le développement initial, en production, cela ne faisait que du bruit et coûtait de l’argent. Un ajustement rapide pour ne consigner que les métadonnées essentielles (ID de requête, code de statut, point d’accès) a réduit de manière significative notre ingestion de logs.
Les démarrages à froid, bien qu’ils ne représentent pas un « coût » direct de la même manière, affectent l’expérience utilisateur et peuvent entraîner indirectement plus de réessaies 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 : Journaux Intelligents et Variables d’Environnement
Pour le journal, 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 uniquement si LOG_LEVEL est DEBUG")
logger.info("Traitement de l'événement : %s", event.get('request_id'))
try:
# ... logique de fonction ...
logger.debug("Traitement terminé 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 réduire considérablement votre facture CloudWatch Logs sans sacrifier l’observabilité quand vous en avez besoin.
Un autre conseil est de prêter attention à ce qui est initialisé en dehors du gestionnaire. Tout code directement dans la portée globale de votre fonction Lambda s’exécutera pendant le démarrage à froid. Si vous avez des opérations coûteuses comme le pooling de connexion à la base de données ou de grandes importations de 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.
Points Clés pour Votre Croisade sur les Coûts Sans Serveur
D’accord, nous avons couvert pas mal de choses. Voici un résumé des étapes pratiques que vous pouvez prendre dès maintenant pour commencer à réduire ces frais 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 les pics inattendus.
- Consolidez les tâches cron : Si vous avez plusieurs petites fonctions Lambda programmées, envisagez de les combiner en un unique « Maintenance Runner » qui dispatch des tâches selon un emploi du temps 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 la configuration de mémoire optimale pour vos fonctions. Ne faites pas de suppositions et ne provisionnez pas en trop. Rappelez-vous, plus de mémoire signifie plus de CPU, et vous payez pour les deux.
- Contrôlez la verbosité des journaux : Implémentez des niveaux de journalisation pilotés par des variables d’environnement (par exemple,
INFOpour la production,DEBUGpour le développement). Évitez de journaliser les corps de requêtes entiers ou un état interne excessif en production. Votre facture CloudWatch Logs vous remerciera. - Révisez les fonctions inutilisées : Auditez périodiquement vos fonctions Lambda. Y a-t-il des fonctions anciennes, expérimentales ou obsolètes encore actives et engendrant des coûts ? Supprimez-les !
- Surveillez la taille des paquets : Des paquets de déploiement plus petits signifient des démarrages à froid plus rapides et des coûts de stockage moindres. N’incluez que 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.
Dompter le monstre sans serveur n’est pas une question d’éviter le sans serveur ; il s’agit d’être malin et intentionnel dans la façon dont nous l’utilisons. La flexibilité et l’évolutivité sont inestimables, mais sans vigilance appropriée, ces « petits » coûts peuvent s’accumuler pour représenter une part significative de votre budget. Allez-y, surveillez, optimisez et économisez !
Voilà, c’est tout pour moi aujourd’hui. Faites-moi savoir dans les commentaires si vous avez d’autres astuces ou conseils pour l’optimisation des coûts Lambda !
🕒 Published: