DEV Community

Ma facture AWS raconte une histoire. J’ai construit un pipeline pour la comprendre.

Une facture AWS peut monter pour une excellente raison.

On active des journaux d’audit. On améliore la détection des menaces. On renforce les sauvegardes. Et, le mois suivant, la courbe des dépenses grimpe.

Le graphique dit : « ça coûte plus cher ».

Il ne dit pas : « nous avons acheté de la visibilité et réduit un risque ».

À l’inverse, une facture stable peut masquer un disque qui se remplit, une réservation qui arrive à échéance ou une alarme tellement bruyante que plus personne ne la regarde.

C’est le problème auquel j’ai voulu répondre en construisant une chaîne de reporting mensuel AWS. Pas un dashboard supplémentaire. Un document qui rapproche ce que l’infrastructure coûte, ce qu’elle fait et les décisions qu’elle appelle.

La stack est volontairement simple : Bash, AWS CLI, Python, JSON, HTML et PDF. L’IA intervient pour aider à rédiger les commentaires, pas pour produire les chiffres.

Le principe : automatiser les faits, assister l’analyse, garder la responsabilité des conclusions.

Un dashboard montre. Un rapport doit faire décider.

Pour ce suivi, j’avais besoin de répondre à trois questions :

  1. Qu’est-ce qui a changé depuis le précédent bilan ?
  2. Qu’est-ce qui mérite une intervention ?
  3. Quelle décision faut-il prendre maintenant ?

Ces questions traversent plusieurs consoles AWS. Cost Explorer connaît les dépenses. CloudWatch connaît les métriques. Les APIs d’inventaire connaissent les ressources. Et les raisons métier d’un changement se trouvent souvent ailleurs : dans un incident, une action planifiée ou une discussion.

Le rapport devait réunir ces morceaux sans transformer une hypothèse en certitude.

J’ai donc séparé le système en trois couches : collecter, interpréter, présenter.

Architecture du reporting : AWS vers un instantané JSON, puis un rendu Python en HTML et PDF. Une branche distincte extrait les faits pour l’IA ; la validation humaine alimente le JSON éditorial.

Le chemin des chiffres et celui des commentaires se rejoignent au rendu, pas dans la collecte. Les encadrés de code ci-dessous sont des extraits simplifiés et anonymisés, pas un outil complet à installer.

1. Commencer par une photo, pas par un PDF

La première sortie de la chaîne n’est pas un document. C’est un instantané JSON.

Le collecteur interroge notamment :

Source Ce que j’en tire
Cost Explorer Dépenses par service et comparaison avec une période de référence
CloudWatch Métriques de cache, stockage S3, files SQS et historique d’alarmes
APIs EC2, RDS, ElastiCache, ELB Inventaire et caractéristiques des ressources
AWS Backup Exécutions de sauvegarde, échecs et restaurations
IAM Credential Report État du MFA, ancienneté des clés et signaux d’inactivité
Systems Manager Occupation des systèmes de fichiers et versions logicielles sur les instances accessibles

Le JSON porte aussi la période, la date de collecte et les avertissements rencontrés.

Voici le type de requête utilisé pour les coûts. Les dates servent uniquement d’exemple ; End est exclusif dans Cost Explorer.

export AWS_PROFILE="reporting"  # Profil local fictif

aws ce get-cost-and-usage \
    --time-period Start=2026-08-01,End=2026-09-01 \
    --granularity MONTHLY \
    --metrics UnblendedCost \
    --group-by Type=DIMENSION,Key=SERVICE \
    --output json
Enter fullscreen mode Exit fullscreen mode

Ce choix de métrique compte : UnblendedCost ne représente pas une répartition amortie du coût des engagements. Avant de commenter un montant, il faut savoir ce qu’il mesure.

L’intérêt de cette étape intermédiaire est très concret : je peux refaire la mise en page sans refaire la collecte.

Une correction de texte ne devrait pas déclencher une nouvelle exploration du compte AWS. Et une nouvelle exploration, plusieurs semaines plus tard, ne restitue pas nécessairement les mêmes données : les ressources évoluent et les historiques disponibles dépendent du service.

Le collecteur refuse donc de remplacer un instantané existant, sauf demande explicite. Le générateur ne réécrit pas ce JSON.

Ce n’est pas de l’immuabilité cryptographique. C’est un garde-fou opérationnel : on ne remplace pas silencieusement la photo sur laquelle un bilan s’appuie.

Une nuance importante : tout n’est pas historique

Les dépenses et métriques sont interrogées sur une fenêtre. L’inventaire décrit principalement l’état au moment de la collecte.

Un rapport mensuel n’est donc pas un enregistrement exhaustif de chaque ressource ayant existé pendant le mois. Une instance créée puis supprimée entre deux collectes peut avoir un coût sans apparaître dans l’inventaire final.

Cette distinction mérite d’être expliquée au lecteur. Sinon, une photo finit par passer pour un film.

2. Deux JSON : les faits d’un côté, le contexte de l’autre

À l’instantané AWS s’ajoute un second JSON, propre au mois : les données éditoriales.

Il contient les commentaires par section, les incidents, les actions à suivre et les échéances renseignées manuellement.

Pourquoi ne pas tout mélanger ?

Parce que « le coût de ce service a augmenté » et « cette augmentation correspond à une décision validée » n’ont pas la même provenance.

Le premier énoncé vient d’une mesure. Le second nécessite du contexte.

Le générateur combine ces deux entrées pour produire le rapport. Il refuse de travailler si l’entrée éditoriale attendue manque. Au changement de mois, un utilitaire reprend le suivi précédent mais vide les commentaires : les actions peuvent continuer ; les conclusions doivent être relues.

C’est un détail qui évite une catégorie d’erreurs particulièrement discrète : un paragraphe parfaitement rédigé… mais encore vrai uniquement le mois précédent.

Autre règle utile : dans les actions persistantes, préférer une date à « depuis trois mois ». Une date vieillit honnêtement. Une durée copiée-collée devient fausse sans faire de bruit.

Voici une entrée éditoriale volontairement minimale. Ce commentaire est fictif ; les autres rubriques sont omises.

{
    "mois": "2026-08",
    "commentaire_couts": [
        "Une hausse est détectée sur le stockage : origine à vérifier."
    ],
    "actions": [],
    "incidents": [],
    "expirations_manuelles": []
}
Enter fullscreen mode Exit fullscreen mode

Au quotidien, un Makefile donne une porte d’entrée commune :

make new-input MONTH=2026-08
make collect MONTH=2026-08
# Extraire les faits, proposer les commentaires, les relire.
make report MONTH=2026-08
Enter fullscreen mode Exit fullscreen mode

Ces cibles appartiennent à mon outillage, pas à AWS CLI. Le point important est la pause avant report : produire un fichier n’est pas valider son contenu.

3. Le piège du 12 du mois

Imaginons un point de suivi en cours de mois.

Exemple entièrement fictif : on a dépensé 420 dollars sur les dix premiers jours, contre 1 200 dollars sur l’ensemble du mois précédent.

Un calcul rapide annonce une baisse de 65 %. C’est arithmétiquement correct et opérationnellement trompeur.

Si les dix premiers jours du mois précédent coûtaient 400 dollars, la comparaison à durée égale raconte plutôt une hausse de 5 %.

Exemple fictif : comparer 420 dollars sur dix jours à 1 200 dollars sur un mois donne moins 65 %, mais comparer à 400 dollars sur les dix premiers jours précédents donne plus 5 %.

Le même montant raconte deux histoires selon la référence choisie. Tous les montants de ce visuel sont fictifs.

Le calcul n’est pas compliqué. Le choix des deux valeurs est la vraie difficulté :

# Exemple autonome ; tous les montants sont fictifs.
current_first_10_days = 420.0
previous_full_month = 1200.0
previous_first_10_days = 400.0

def variation_pct(current, reference):
    if reference == 0:
        return None  # Pas de pourcentage défini : traiter le nouveau coût à part.
    return (current - reference) / reference * 100

print(variation_pct(current_first_10_days, previous_full_month))     # -65.0
print(variation_pct(current_first_10_days, previous_first_10_days))  # 5.0
Enter fullscreen mode Exit fullscreen mode

Le pipeline détecte les mois non clos et produit un rapport intermédiaire :

  • la période couverte et le nombre de jours sont affichés ;
  • la référence est une requête sur les premiers jours du mois précédent, et non son total complet ;
  • une projection linéaire de fin de mois est proposée ;
  • les noms de sortie distinguent l’intermédiaire du définitif.

La référence n’est donc pas simplement le total précédent divisé par trente. Elle utilise les dépenses observées sur une fenêtre comparable.

Mais « même nombre de jours » ne signifie pas « même activité ». Les week-ends, les batchs et la saisonnalité peuvent encore fausser l’interprétation. Et une projection linéaire reste une extrapolation, pas une promesse.

Le document doit rendre ces limites visibles, pas les enfouir derrière un pourcentage.

4. L’IA tient le stylo. Elle ne tient pas la calculatrice.

L’IA n’est pas intégrée à la collecte AWS ni au moteur de rendu. Elle intervient dans un workflow distinct de suggestion des commentaires, sous forme d’un skill pour l’assistant de développement.

Avant la rédaction, un script extrait les faits utiles :

  • coûts courants et de référence ;
  • variations calculées ;
  • services nouveaux ou en forte hausse ;
  • métriques opérationnelles ;
  • évolutions d’inventaire ;
  • commentaires du précédent bilan.

Ce script réutilise le registre de sections du générateur. Le découpage transmis à l’assistant reste ainsi cohérent avec celui du rapport.

Les consignes de rédaction sont explicites : une synthèse chiffrée, puis quelques observations utiles ; chaque chiffre doit venir des faits extraits ; une recommandation doit rester identifiable comme telle.

La règle la plus importante tient en une phrase :

Si les données ne permettent pas d’expliquer une hausse, écrire « origine à vérifier » plutôt que d’inventer une cause plausible.

Un cache peu sollicité dont le coût augmente ne prouve pas que les snapshots sont responsables. C’est une piste éventuelle, pas une conclusion.

Le workflow prévoit aussi la lecture des commentaires précédents. Non pour les recycler, mais pour distinguer un problème nouveau d’un sujet déjà suivi.

Après proposition, les commentaires restent à relire avant diffusion. La chaîne ne contacte pas automatiquement un LLM à chaque génération : on peut produire un rapport à partir de commentaires déjà validés, sans intervention de l’IA.

Ce que j’attends de l’assistant : une formulation plus claire. Ce que je ne lui délègue pas : la vérité du rapport.

5. Une donnée manquante n’est pas une bonne nouvelle

Supposons qu’une métrique ne soit pas récupérée.

Est-ce zéro ? Est-ce une permission insuffisante ? Une instance inaccessible ? Un délai de publication ?

Ces situations ne devraient pas avoir la même signification dans un bilan.

Le collecteur applique des délais d’expiration aux appels et conserve des avertissements lorsque des requêtes échouent. Le rapport comporte une section qui les expose. Des contrôles supplémentaires signalent aussi certaines absences de données.

Le mécanisme a une limite : des valeurs de repli existent encore dans le traitement. Un zéro affiché ne devient pas automatiquement une mesure fiable. Il faut lire les avertissements avec les tableaux, et améliorer progressivement la représentation des valeurs indisponibles.

Cette prudence compte notamment pour S3, où les métriques de taille ne sont pas instantanées, et pour les instances non accessibles via Systems Manager.

Un rapport utile ne dit pas seulement ce qu’il sait. Il laisse voir ce qu’il n’a pas réussi à vérifier.

6. Retirer SSH du reporting, sans prétendre retirer le risque

Pour récupérer l’occupation disque et certaines versions logicielles, j’utilise SSM Run Command plutôt qu’une connexion SSH.

Cela évite de faire dépendre ce suivi de clés SSH et d’un accès SSH direct aux machines. La collecte cible les instances gérées par Systems Manager dont l’agent est en ligne. Les autres sont signalées.

Il reste cependant un point de sécurité essentiel : ssm:SendCommand n’est pas une permission de lecture seule.

Même si les commandes utilisées ici sont des diagnostics, la capacité d’exécuter des commandes à distance doit être encadrée : rôle dédié, ressources autorisées, documents permis et traçabilité.

Passer de SSH à SSM déplace le contrôle d’accès. Cela ne le rend pas inutile.

Même vigilance lors de la collecte réseau : certaines réponses AWS peuvent contenir des secrets. Pour les connexions VPN, la requête sélectionne explicitement les champs utiles au rapport au lieu de récupérer et afficher toute la réponse.

Réduire les données à la source vaut mieux que compter sur une suppression après coup.

7. Le service oublié doit quand même apparaître

Le générateur utilise un registre de sections : chaque section déclare ses mots-clés de coûts et sa fonction de rendu.

Cela permet de rapprocher, par exemple, une dépense de stockage des ressources et observations correspondantes.

Mais un service AWS peut apparaître sans avoir de section dédiée. Je ne voulais pas qu’il disparaisse simplement parce que mon rapport ne savait pas encore où le ranger.

Les dépenses non couvertes sont donc regroupées dans « Autres services facturés », avec un signalement en fin de génération.

Le système applique également des règles simples pour attirer l’attention sur les nouveautés et les fortes hausses. Les seuils combinent variation relative et montant absolu : un pourcentage spectaculaire sur quelques centimes n’a pas forcément d’intérêt.

Voici une version simplifiée des règles utilisées, avec les seuils actuels du générateur :

def cost_alert(current, previous):
    if current >= 5.0 and previous < 1.0:
        return "new service"

    if previous > 0:
        delta = current - previous
        pct = delta / previous * 100
        if pct >= 50.0 and delta >= 10.0:
            return "large increase"

    return None
Enter fullscreen mode Exit fullscreen mode

Les seuils sont des choix de tri, pas des lois universelles : ils doivent correspondre à la taille de la facture et au niveau de détail souhaité.

Ce n’est pas un modèle prédictif. C’est un filtre explicable qui désigne les lignes à examiner.

L’objectif n’est pas d’obtenir un badge rouge. C’est d’aboutir à une question utile : coût attendu, changement de périmètre ou anomalie à investiguer ?

8. Un rapport sans application à maintenir

Le rendu final est un HTML autonome : CSS intégré, graphiques SVG, pas de JavaScript ni de bibliothèque de graphiques à charger. Une conversion avec wkhtmltopdf produit le PDF.

Ce choix correspond au besoin : un bilan périodique consultable hors ligne, archivable et lisible sans déployer un portail supplémentaire.

Il a aussi ses compromis. Un document statique ne remplace pas une exploration interactive. Et wkhtmltopdf repose sur un moteur ancien : son usage ici n’en fait pas un choix universel pour un nouveau projet.

La simplicité de distribution ne doit pas être confondue avec l’innocuité du contenu. Un rapport d’infrastructure peut exposer des identités, adresses et versions. La diffusion reste un sujet de sécurité à part entière.

9. Tester le document, pas seulement le script

Un générateur peut s’exécuter sans erreur et produire un mauvais rapport.

La non-régression compare donc le HTML généré à des sorties attendues, avec des jeux de données couvrant un mois complet et un mois partiel.

Pour rendre ces comparaisons stables, les tests fixent les dates et désactivent les appels de rafraîchissement AWS. Ils vérifient aussi le refus d’une entrée éditoriale absente et l’échappement des commentaires HTML.

Une nuance empêche de promettre une reproductibilité parfaite en production : à la génération, les réservations RDS et ElastiCache peuvent être rafraîchies en mémoire pour présenter leurs échéances actuelles. Le JSON d’origine reste intact, mais deux rendus espacés dans le temps peuvent différer.

Il faut donc distinguer restituer un bilan historique et actualiser les informations utiles à une décision aujourd’hui. Les deux sont légitimes ; ce ne sont pas le même contrat.

Ces tests ne prouvent pas non plus que toutes les APIs ont répondu ni qu’une recommandation est pertinente. Ils protègent le rendu. La relecture protège l’interprétation.

Ce que je garderais si je recommençais

Pas forcément Bash. Pas forcément le convertisseur PDF. Peut-être même pas le même format de données.

Mais je garderais ces cinq décisions :

  1. Sauvegarder les faits avant de les présenter.
  2. Séparer les observations du contexte et des recommandations.
  3. Comparer des périodes explicites, surtout en cours de mois.
  4. Faire apparaître les échecs et les services non classés.
  5. Donner à l’IA des faits vérifiables et un rôle limité.

Je ne prétends pas avoir remplacé un outil FinOps ou un audit de sécurité. J’ai construit une chaîne adaptée à un besoin plus ciblé : rendre un bilan mensuel répétable, lisible et utile à la discussion.

La prochaine évolution la plus intéressante n’est d’ailleurs pas « plus d’IA ». C’est davantage de précision sur la provenance des données, les valeurs indisponibles et les conditions de reproduction d’un ancien rapport.

Au fond, la facture n’était que le début de l’histoire.

Le vrai livrable, c’est de pouvoir dire : voilà ce qui a changé, voilà ce que nous savons, voilà ce qu’il faut encore vérifier — et voilà la décision à prendre.


Et vous : dans vos bilans cloud, quelle question reste sans réponse alors que toutes les métriques semblent disponibles ?

Top comments (0)