[{"content":"Dès qu\u0026rsquo;on parle FinOps, le réflexe est le même : \u0026ldquo;c\u0026rsquo;est pour réduire la facture cloud\u0026rdquo;. C\u0026rsquo;est un raccourci qui rate l\u0026rsquo;essentiel. Le FinOps n\u0026rsquo;est pas une technique d\u0026rsquo;optimisation, c\u0026rsquo;est une démarche, au même titre que le DevOps l\u0026rsquo;a été pour le déploiement. Le DevOps a mis en dialogue le développement et l\u0026rsquo;exploitation. Le FinOps met en dialogue la technique, la finance et le produit, avec le coût comme langage commun.\nLe coût comme outil de décision, pas comme problème à minimiser Réduire les coûts cloud est un sous-produit du FinOps, pas son objectif. L\u0026rsquo;objectif, c\u0026rsquo;est de donner à une organisation la visibilité et le langage nécessaires pour prendre des décisions éclairées face aux coûts qu\u0026rsquo;elle engage.\nConcrètement, ça veut dire être capable de répondre à des questions simples, qui restent pourtant sans réponse dans beaucoup d\u0026rsquo;entreprises : combien coûte cette feature à faire tourner, combien de temps humain a t\u0026rsquo;elle nécessité pour être livrée, et quelle valeur business génère t\u0026rsquo;elle en retour ? Sans ces réponses, une équipe ne décide pas, elle subit une facture en fin de mois.\nEstimer un ROI, ce n\u0026rsquo;est pas seulement regarder la ligne \u0026ldquo;infra\u0026rdquo; d\u0026rsquo;une facture cloud. C\u0026rsquo;est mettre en face le coût humain (temps de dev, de review, de run, de support) et le coût infra (compute, stockage, réseau, licences) d\u0026rsquo;un côté, et la valeur générée de l\u0026rsquo;autre (chiffre d\u0026rsquo;affaires, rétention, réduction de charge opérationnelle). Une feature qui coûte 500€/mois d\u0026rsquo;infra mais qui a mobilisé trois développeurs pendant deux mois pour un usage marginal n\u0026rsquo;est pas rentable, même si la ligne cloud paraît négligeable.\nUn révélateur d\u0026rsquo;inefficiences, au delà du financier Ce qui rend une démarche FinOps utile va bien au delà du financier. Dès qu\u0026rsquo;une organisation commence à tagger ses ressources, à les rattacher à des équipes ou des produits, et à regarder ce que ça coûte vraiment, des inefficiences remontent qui n\u0026rsquo;ont souvent rien à voir avec le cloud en lui même :\nProduit : des features maintenues alors que personne ne les utilise, des environnements de staging qui tournent 24/7 pour un usage ponctuel. Process : des validations qui allongent le time to market sans réduire le risque, des équipes qui redéveloppent la même brique faute de visibilité sur l\u0026rsquo;existant. Vélocité d\u0026rsquo;équipe : le coût humain d\u0026rsquo;une feature, rapporté à sa valeur, révèle souvent des frictions organisationnelles avant de révéler un problème d\u0026rsquo;infra. Le FinOps sert alors de miroir. Il ne dit pas seulement \u0026ldquo;ça coûte trop cher\u0026rdquo;, il dit \u0026ldquo;voilà où l\u0026rsquo;énergie de l\u0026rsquo;organisation part, et voilà si elle part au bon endroit\u0026rdquo;.\nUne maturité encore faible La plupart des entreprises que j\u0026rsquo;ai croisées sont encore peu matures sur ces sujets. Le budget cloud est souvent suivi au niveau global, sans granularité par équipe ou par produit. Les tags de ressources, quand ils existent, sont incomplets ou incohérents. Le sujet FinOps est porté par une seule personne ou une petite cellule, sans relais côté équipes produit, ce qui en fait un exercice de reporting plutôt qu\u0026rsquo;une culture partagée.\nCette immaturité n\u0026rsquo;est pas un problème en soi, c\u0026rsquo;est un point de départ. Le risque, c\u0026rsquo;est de vouloir aller trop vite.\nNe pas transformer l\u0026rsquo;organisation, itérer Comme pour toute démarche de transformation, il ne faut pas chercher à tout changer d\u0026rsquo;un coup. Plaquer un cadre FinOps complet (gouvernance, allocation, forecast, chargeback) sur une organisation qui n\u0026rsquo;a même pas de tagging fiable est voué à l\u0026rsquo;échec. Une approche itérative, par paliers de maturité, fonctionne mieux dans la grande majorité des cas.\nLe FinOps Framework formalise ça en trois phases, qui se répètent en boucle plutôt que de se dérouler une seule fois :\nCollecter (Inform) : mettre en place les tags de ressources, connecter les exports de facturation (idéalement au format FOCUS), construire des dashboards par équipe ou produit. Sans cette étape, tout le reste repose sur des estimations. Optimiser (Optimize) : une fois la visibilité en place, agir sur l\u0026rsquo;allocation de ressources (rightsizing), les réservations et engagements sur le long terme, le nettoyage des ressources orphelines, et le décommissionnement de ce qui ne sert plus. Opérer (Operate) : installer l\u0026rsquo;amélioration continue, suivre l\u0026rsquo;usage dans la durée, faire du FinOps un réflexe dans les revues de conception plutôt qu\u0026rsquo;un audit ponctuel. Chaque itération renforce la suivante : plus la collecte est fiable, plus l\u0026rsquo;optimisation est pertinente, plus l\u0026rsquo;opération devient un réflexe partagé plutôt qu\u0026rsquo;une contrainte imposée d\u0026rsquo;en haut.\nLe cas particulier de l\u0026rsquo;IA L\u0026rsquo;essor des usages LLM ajoute une dimension que beaucoup de stratégies FinOps ne couvrent pas encore, alors que la facture peut grimper très vite et de façon peu prévisible. Trois points méritent une attention particulière :\nGouvernance des modèles : savoir quels modèles sont utilisés, par quelles équipes, pour quels cas d\u0026rsquo;usage, plutôt que de laisser chaque projet choisir son fournisseur et son modèle sans coordination. Gestion des tokens : suivre la consommation de tokens comme n\u0026rsquo;importe quelle autre ressource cloud, avec des budgets et des alertes, plutôt que de découvrir la facture en fin de mois. Fallback vers des modèles moins coûteux : basculer automatiquement sur un modèle moins cher en cas de dépassement de budget ou d\u0026rsquo;indisponibilité, sans bloquer l\u0026rsquo;usage ni changer le code applicatif. Tracking par équipe/produit : rattacher chaque appel à une clé d\u0026rsquo;équipe ou de produit, pour garder la même granularité que sur le reste de l\u0026rsquo;infra. J\u0026rsquo;ai détaillé une mise en œuvre concrète de ces principes avec LiteLLM, qui permet de proxifier les appels aux LLM pour appliquer justement ce type de stratégie.\nConclusion Le FinOps n\u0026rsquo;est pas un projet qu\u0026rsquo;on lance et qu\u0026rsquo;on referme une fois la facture réduite. C\u0026rsquo;est une démarche continue qui met les coûts, humains et infra, au centre du dialogue entre technique, finance et produit. Bien menée, elle ne se contente pas de faire baisser une facture cloud : elle révèle où l\u0026rsquo;énergie de l\u0026rsquo;organisation part réellement, et permet enfin d\u0026rsquo;estimer un ROI sur des bases concrètes plutôt que sur une intuition.\n","date":"2026-09-18T09:00:00+02:00","image":"/posts/finops-couts-business-roi/index.png","permalink":"/posts/finops-couts-business-roi/","title":"FinOps : mettre les coûts cloud au centre du dialogue produit"},{"content":"C\u0026rsquo;est fait : j\u0026rsquo;ai obtenu la certification Google Cloud Certified - Professional Cloud Architect. Elle vient couronner une série d\u0026rsquo;articles écrits ces derniers mois autour de GCP : GKE, réseau (Shared VPC, Hub \u0026amp; Spoke, NCC), Infrastructure as Code. Cette certification valide ces compétences techniques ponctuelles en les replaçant dans une vraie posture d\u0026rsquo;architecte : celle qui doit choisir, arbitrer et justifier une architecture plutôt que simplement savoir la construire.\nUn examen plus corsé que prévu Je m\u0026rsquo;attendais à un examen orienté services et bonnes pratiques. En réalité, il demande surtout de tenir plusieurs contraintes en tête en même temps et de trouver l\u0026rsquo;architecture qui les satisfait toutes, pas la plus élégante sur le papier. Ça se joue souvent sur un détail de l\u0026rsquo;énoncé qui élimine trois options sur quatre.\nPour le préparer, je me suis appuyé sur ce cours Udemy, complété par une grosse semaine de révision intensive avant de passer l\u0026rsquo;examen réel.\nSi vous n\u0026rsquo;avez jamais manipulé la console Google Cloud, je conseille de commencer par la certification Associate Cloud Engineer : les compétences demandées y sont plus accessibles quand on découvre la plateforme, et elles servent de socle pour attaquer ensuite la Professional Cloud Architect.\nCe qui m\u0026rsquo;a marqué dans le contenu J\u0026rsquo;ai particulièrement apprécié la place donnée à l\u0026rsquo;IA dans l\u0026rsquo;exam guide : inférence, monitoring de modèles, entraînement, gestion de datasets, détection de drift. Ce sont des sujets dans l\u0026rsquo;air du temps, et les voir intégrés à une certification d\u0026rsquo;architecture (plutôt que traités à part) reflète bien la réalité des projets actuels.\nLe reste de l\u0026rsquo;examen met face à des cas d\u0026rsquo;usage très proches du terrain : il s\u0026rsquo;agit de trouver la meilleure architecture pour un contexte donné, avec des contraintes précises à respecter (SLA/SLO, coût, sécurité, gouvernance, SDLC\u0026hellip;). C\u0026rsquo;est cet exercice d\u0026rsquo;arbitrage, plus que la connaissance brute des services, qui fait la difficulté et l\u0026rsquo;intérêt de la certification.\n","date":"2026-09-17T11:41:00+02:00","image":"/posts/professional-cloud-architect-certification/index.png","permalink":"/posts/professional-cloud-architect-certification/","title":"Certifié Google Cloud Professional Cloud Architect"},{"content":"Dès qu\u0026rsquo;un projet GCP dépasse un seul projet et une seule équipe, la question du réseau se pose : est-ce que tout le monde partage le même VPC, ou est-ce que chaque environnement a le sien et on les relie ? Et si on les relie, avec quoi ? La réponse dépend surtout d\u0026rsquo;où vous en êtes, pas d\u0026rsquo;un idéal théorique.\nLes modèles en présence Shared VPC Un projet \u0026ldquo;hôte\u0026rdquo; possède le VPC, et le partage avec des projets \u0026ldquo;service\u0026rdquo; qui y créent leurs ressources. Tout le monde vit dans le même réseau : mêmes subnets, mêmes tables de routage, un seul domaine de broadcast. L\u0026rsquo;isolation se fait par IAM (qui a le droit d\u0026rsquo;utiliser quel subnet), pas par le réseau lui-même.\ngraph TB subgraph HOST[\"Projet hôte — 1 VPC\"] SubA[\"subnet-a\"] SubB[\"subnet-b\"] end SvcProject1[\"Projet service : équipe A\"] --\u003e|\"Network User\"| SubA SvcProject2[\"Projet service : équipe B\"] --\u003e|\"Network User\"| SubBSimple à opérer, pas de routage à gérer entre réseaux. Le revers : une erreur de firewall ou de route au niveau du hôte impacte tout le monde, et l\u0026rsquo;équipe qui possède le projet hôte devient vite un point de passage obligé pour toute modification réseau.\nHub \u0026amp; Spoke via VPC Peering Un hub central, des spokes qui s\u0026rsquo;y connectent en peering. C\u0026rsquo;est le modèle que j\u0026rsquo;ai mis en pratique sur GCP avec Terraform. Chaque spoke garde son propre VPC (donc son propre domaine d\u0026rsquo;administration), mais le peering VPC natif GCP n\u0026rsquo;est pas transitif : un spoke ne voit pas un autre spoke à travers le hub sans configuration additionnelle (next-hop routes vers une appliance dans le hub, par exemple).\ngraph TB HUB[\"Hub VPC\"] S1[\"Spoke dev\"] S2[\"Spoke prod\"] S3[\"Spoke data\"] S1 \u003c--\u003e|Peering| HUB S2 \u003c--\u003e|Peering| HUB S3 \u003c--\u003e|Peering| HUBBonne isolation par design (pas de connectivité spoke-à-spoke par défaut), mais ça ne scale pas indéfiniment : la limite par défaut est de 25 connexions de peering actives par VPC (extensible, mais avec un plafond). Au-delà d\u0026rsquo;une dizaine de spokes, la gestion manuelle des peerings et des routes devient pénible.\nNetwork Connectivity Center (NCC) NCC reprend l\u0026rsquo;idée du hub \u0026amp; spoke, mais le hub gère nativement le routage transitif entre spokes (VPC, VPN, Interconnect, routeur virtuel) et la connectivité hybride vers l\u0026rsquo;on-prem fait partie du même modèle plutôt que d\u0026rsquo;être bricolée à côté. C\u0026rsquo;est pensé pour des dizaines, voire des centaines de spokes.\ngraph TB HUB[\"NCC Hub\"] S1[\"Spoke VPC dev\"] S2[\"Spoke VPC prod\"] S3[\"Spoke VPN — on-prem\"] S4[\"Spoke Interconnect\"] S1 \u003c--\u003e HUB S2 \u003c--\u003e HUB S3 \u003c--\u003e HUB S4 \u003c--\u003e HUB S1 -.transitif via hub.-\u003e S2Plus de flexibilité et de scalabilité que le peering, mais aussi plus de surface : le hub devient une pièce critique, et la transitivité par défaut demande d\u0026rsquo;être explicite sur ce qui doit vraiment communiquer (via des groupes de spokes ou des policies dédiées), sinon on recrée un grand réseau plat comme un Shared VPC, avec la complexité de NCC en plus.\nLes autres options Private Service Connect (PSC) : pas de connectivité réseau entre VPC, juste l\u0026rsquo;exposition d\u0026rsquo;un service précis (une API, une base de données managée) consommable depuis un autre VPC via une IP privée. C\u0026rsquo;est la surface d\u0026rsquo;attaque la plus faible des quatre options, pertinent quand on n\u0026rsquo;a besoin que d\u0026rsquo;accéder à un service précis, pas au réseau entier. VPC isolés, sans connexion : la valeur par défaut. Pas de complexité réseau, isolation maximale. Suffisant tant qu\u0026rsquo;aucune ressource n\u0026rsquo;a besoin de parler à une autre à travers les frontières de VPC. Tableau récapitulatif Modèle Isolation réseau Transitivité Connectivité hybride Scale Bon pour Shared VPC Faible (IAM only) N/A, réseau unique À gérer au niveau hôte Limité par la taille d\u0026rsquo;un seul VPC Une équipe, un niveau de confiance Hub \u0026amp; Spoke (peering) Forte Non native Bricolée (VPN dans le hub) ~10-25 spokes Quelques environnements bien isolés NCC Forte, configurable Native Native Dizaines à centaines de spokes Multi-équipes, multi-région, hybride PSC Maximale N/A, service à service N/A Très élevé Exposer un service précis, pas un réseau Par où commencer quand on ne sait pas de quoi sera fait l\u0026rsquo;avenir Ne partez pas sur NCC ou un hub \u0026amp; spoke élaboré parce que \u0026ldquo;ça pourrait servir plus tard\u0026rdquo;. Le même principe que pour Kubernetes s\u0026rsquo;applique : dimensionner pour un futur hypothétique coûte cher tout de suite et rarement pour la bonne raison. Commencez simple, en général un Shared VPC ou même deux/trois VPC isolés selon le nombre d\u0026rsquo;environnements, et gardez deux décisions bien prises dès le départ, parce que ce sont les seules coûteuses à revenir en arrière :\nLe plan d\u0026rsquo;adressage IP. Réservez des plages CIDR non chevauchantes par environnement et par région dès le premier projet, même si un seul VPC existe aujourd\u0026rsquo;hui. Changer les plages d\u0026rsquo;un VPC déjà peuplé de ressources est un chantier, pas une commande Terraform. La structure projets/dossiers. Organisez les projets GCP comme si la segmentation future existait déjà (un projet par environnement, par équipe ou par domaine), même s\u0026rsquo;ils partagent le même réseau pour l\u0026rsquo;instant. Migrer une frontière IAM/projet est beaucoup plus simple que migrer une frontière réseau. Le reste (peering, NCC, PSC) se rajoute par-dessus une base bien adressée sans tout casser.\nPrécautions au passage à l\u0026rsquo;échelle Le plafond du peering arrive plus vite qu\u0026rsquo;on ne le pense. Si le nombre de spokes croît régulièrement (nouvelles équipes, nouveaux environnements), planifiez la bascule vers NCC avant de taper le plafond, pas après. La migration se fait spoke par spoke, mais elle est plus confortable anticipée que subie. Le Shared VPC centralise aussi la charge opérationnelle. Toute création de subnet ou règle de firewall passe par le propriétaire du projet hôte. Au-delà de quelques équipes, ça devient un goulot d\u0026rsquo;étranglement ; les rôles IAM délégués (Network User par subnet) et les firewall policies hiérarchiques limitent la casse, mais ne suppriment pas le point de contention. La transitivité de NCC n\u0026rsquo;est pas gratuite en clarté. Sans groupes de spokes ou policies explicites, un hub NCC ouvert par défaut recrée un grand réseau plat, avec en plus la complexité d\u0026rsquo;un hub multi-technologie (VPC, VPN, Interconnect). Documentez qui doit parler à qui avant d\u0026rsquo;activer la transitivité globale. Le blast radius grandit avec la mutualisation. Un Shared VPC ou un hub NCC mal cloisonné, c\u0026rsquo;est une erreur de règle de routage qui impacte tous les environnements en même temps, y compris la prod à cause d\u0026rsquo;un changement en dev. Plus le modèle mutualise, plus les revues de changement réseau doivent être strictes. La connectivité hybride change les CIDR utilisables. Dès qu\u0026rsquo;un VPN ou un Interconnect relie à l\u0026rsquo;on-prem, les plages IP du cloud doivent rester non chevauchantes avec celles du datacenter, pas seulement entre VPC GCP. À vérifier avant, pas après avoir peuplé les subnets. En résumé Commencez par le modèle le plus simple qui couvre le besoin réel d\u0026rsquo;aujourd\u0026rsquo;hui, en général Shared VPC ou quelques VPC isolés. Passez au hub \u0026amp; spoke par peering quand l\u0026rsquo;isolation entre environnements devient nécessaire et que le nombre de spokes reste modeste. Passez à NCC quand le nombre de spokes, les besoins de transitivité ou la connectivité hybride dépassent ce que le peering peut raisonnablement gérer. Et réservez PSC pour les cas où seul un service précis, pas tout un réseau, a besoin d\u0026rsquo;être atteint. Le seul investissement à faire tôt, quel que soit le modèle de départ, c\u0026rsquo;est le plan d\u0026rsquo;adressage IP.\n","date":"2026-09-14T18:30:00+02:00","image":"/posts/shared-vpc-hub-spoke-ncc/index.png","permalink":"/posts/shared-vpc-hub-spoke-ncc/","title":"Shared VPC, Hub \u0026 Spoke, NCC : quelle architecture réseau choisir sur GCP ?"},{"content":"Sur LinkedIn, en conférence, en entretien technique : Kubernetes est partout. Des entreprises qui n\u0026rsquo;ont pas encore validé leur produit provisionnent déjà des clusters multi-nœuds, écrivent des Helm charts et débattent de leur stratégie de service mesh. Dans la majorité des cas que j\u0026rsquo;ai croisés, ce n\u0026rsquo;était pas nécessaire.\nLe syndrome du marteau \u0026ldquo;Quand on a un marteau dans les mains, on pense que tout est un clou.\u0026rdquo; Kubernetes est devenu ce marteau. Il est sexy, il fait bien sur un CV, il est LA réponse qu\u0026rsquo;on attend en entretien quand on parle de scalabilité. Sauf que la scalabilité n\u0026rsquo;est pas un problème du jour 1. C\u0026rsquo;est une étape dans l\u0026rsquo;évolution d\u0026rsquo;un produit, qui arrive quand le produit a prouvé qu\u0026rsquo;il fonctionne et qu\u0026rsquo;il a effectivement besoin d\u0026rsquo;absorber de la charge.\nAvant d\u0026rsquo;en arriver là, l\u0026rsquo;objectif d\u0026rsquo;une entreprise n\u0026rsquo;est pas de faire des choses compliquées pour le plaisir des technicien(ne)s. C\u0026rsquo;est de dégager du chiffre d\u0026rsquo;affaires. Une infrastructure Kubernetes que personne dans l\u0026rsquo;équipe ne maîtrise, pour un produit qui n\u0026rsquo;a pas encore trouvé ses utilisateurs, ne sert ni l\u0026rsquo;un ni l\u0026rsquo;autre.\nUn cas vécu J\u0026rsquo;ai été confronté à ce contexte dans une entreprise. Le produit s\u0026rsquo;appuyait sur des modèles de machine learning pour reconstruire des images 3D à partir d\u0026rsquo;images 2D, une charge d\u0026rsquo;inférence typiquement variable et candidate naturelle à une infra \u0026ldquo;scalable\u0026rdquo;.\nUn architecte a conçu, en amont, une infrastructure Kubernetes pensée pour absorber une montée en charge à venir. Sur le papier c\u0026rsquo;était cohérent avec le besoin produit. Dans les faits :\nUne seule personne, devops junior, était en charge de déployer et maintenir cette infrastructure. Une seule personne côté MLOps, également junior, sans connaissance de Kubernetes, devait faire tourner les modèles dessus. Le volume à traiter au démarrage était très faible, avec une montée en charge qui devait être progressive. Résultat : deux personnes juniors se retrouvaient à porter la complexité opérationnelle d\u0026rsquo;un cluster (réseau, ordonnancement, observabilité, mises à jour, sécurité) pour un volume qui aurait tenu sur une poignée de conteneurs managés, voire sur du serverless pur. L\u0026rsquo;énergie de l\u0026rsquo;équipe est allée dans la compréhension de Kubernetes plutôt que dans l\u0026rsquo;amélioration du produit ou la fiabilisation des modèles. Le risque opérationnel (un cluster mal maîtrisé qui tombe) a été introduit avant même que la charge ne le justifie.\nCe n\u0026rsquo;est pas un problème de compétence individuelle. C\u0026rsquo;est un problème de séquencement : on a dimensionné pour un futur incertain avant d\u0026rsquo;avoir validé le présent.\nVérifier que le produit fonctionne avant de scaler Avant toute infrastructure complexe, deux questions devraient être tranchées :\nLe produit répond-il à un besoin réel, avec des utilisateurs qui reviennent ? A-t-on effectivement du volume à absorber, ou anticipe-t-on un volume hypothétique ? Tant que la réponse à la première question n\u0026rsquo;est pas claire, investir dans la scalabilité, c\u0026rsquo;est optimiser une variable qui n\u0026rsquo;est pas le facteur limitant. Le risque n\u0026rsquo;est pas de manquer de capacité, il est de construire quelque chose que personne n\u0026rsquo;utilise, avec une infrastructure sophistiquée en prime.\nUne approche itérative Plutôt que de partir directement sur Kubernetes, une progression par paliers, en fonction du volume réel et de la maturité de l\u0026rsquo;équipe, marche mieux dans la grande majorité des cas :\nCloud Functions (serverless) : zéro gestion d\u0026rsquo;infra, facturation à l\u0026rsquo;usage, adapté à un volume faible ou irrégulier. C\u0026rsquo;est le bon point de départ pour valider un besoin sans immobiliser de ressources humaines sur l\u0026rsquo;infra. Cloud Run (conteneurs managés) : on garde la flexibilité du conteneur (dépendances, runtime, temps d\u0026rsquo;exécution plus long) sans la charge opérationnelle d\u0026rsquo;un orchestrateur. C\u0026rsquo;est l\u0026rsquo;étape naturelle quand le serverless devient limitant (cold starts, durée, mémoire) mais que la charge ne justifie pas encore un cluster. Kubernetes : quand la charge est là, réelle et mesurée, que les besoins d\u0026rsquo;orchestration deviennent complexes (multi-services, scaling fin, topologies réseau spécifiques), et surtout que l\u0026rsquo;équipe a les compétences ou les moyens de les acquérir pour l\u0026rsquo;opérer durablement. Dans le cas vécu plus haut, l\u0026rsquo;équipe aurait probablement pu tenir l\u0026rsquo;étape 1 ou 2 pendant plusieurs mois, le temps de stabiliser les modèles et de monter en compétence, avant d\u0026rsquo;envisager Kubernetes.\nQuand Kubernetes est justifié Ce n\u0026rsquo;est pas une charge contre Kubernetes en tant que tel. C\u0026rsquo;est un outil solide, et il devient le bon choix quand plusieurs conditions se rejoignent :\nLe produit a une traction avérée, avec une charge réelle et croissante à absorber. L\u0026rsquo;équipe dispose de compétences DevOps/SRE en interne, ou a le budget pour se les offrir (plateforme managée, prestataire, recrutement), et pas une seule personne junior qui porte le sujet seule. Les besoins dépassent ce qu\u0026rsquo;un service managé (Cloud Run, ECS, App Runner) peut couvrir : orchestration multi-services fine, autoscaling avancé, contraintes réseau ou de sécurité spécifiques. Le coût d\u0026rsquo;opération (temps humain, formation, veille sécurité) a été mis en face du gain attendu, pas seulement de l\u0026rsquo;attrait technique. Si ces conditions ne sont pas réunies, Kubernetes n\u0026rsquo;ajoute pas de valeur : il ajoute de la complexité et du risque, financé par une équipe qui pourrait investir ce temps ailleurs.\nConclusion Tout est une question de contexte, de moyens financiers et humains. La scalabilité ne se décrète pas au jour 1, elle se mérite à mesure que le produit prouve qu\u0026rsquo;il en a besoin. Avant de sortir le marteau Kubernetes, il vaut mieux vérifier qu\u0026rsquo;on a bien affaire à un clou.\n","date":"2026-09-08T17:41:00+02:00","image":"/posts/kubernetes-pas-la-solution-a-tout/index.png","permalink":"/posts/kubernetes-pas-la-solution-a-tout/","title":"Pourquoi Kubernetes n'est pas la solution à tout"},{"content":"Contexte En cherchant comment maîtriser les coûts d\u0026rsquo;appels LLM sur plusieurs projets/équipes, je suis tombé sur LiteLLM : un proxy qui expose une API compatible OpenAI devant n\u0026rsquo;importe quel fournisseur (Anthropic, DeepSeek, Bedrock, Vertex AI, modèles locaux, etc.).\nCe que j\u0026rsquo;ai appris LiteLLM ne se contente pas d\u0026rsquo;unifier les appels, il ajoute une vraie couche FinOps : budgets par clé/équipe/projet, fallback automatique vers un modèle moins coûteux en cas d\u0026rsquo;erreur ou de dépassement de budget, et un suivi de conso par tag/utilisateur exposé dans un dashboard intégré.\nDétails Un déploiement minimal via Docker, avec un fichier de config déclarant les modèles disponibles et une stratégie de fallback :\nmodel_list: - model_name: claude-sonnet litellm_params: model: anthropic/claude-sonnet-4-5 api_key: os.environ/ANTHROPIC_API_KEY - model_name: deepseek-chat litellm_params: model: deepseek/deepseek-chat api_key: os.environ/DEEPSEEK_API_KEY router_settings: fallbacks: [{\u0026#34;claude-sonnet\u0026#34;: [\u0026#34;deepseek-chat\u0026#34;]}] litellm_settings: max_budget: 100 budget_duration: 30d docker run -p 4000:4000 -v $(pwd)/config.yaml:/app/config.yaml \\ ghcr.io/berriai/litellm:main-latest --config /app/config.yaml Chaque appel passe ensuite par http://localhost:4000 comme s\u0026rsquo;il s\u0026rsquo;agissait de l\u0026rsquo;API OpenAI, avec une clé virtuelle par équipe pour tracer et plafonner la conso. Si Claude est indisponible ou que le budget de l\u0026rsquo;équipe est dépassé, LiteLLM bascule automatiquement sur DeepSeek, nettement moins cher, sans changer une ligne de code applicatif.\nPourquoi c\u0026rsquo;est utile Ça permet de découpler le code applicatif du fournisseur de LLM, d\u0026rsquo;éviter les mauvaises surprises de facturation (budget + fallback automatique), et de donner de la visibilité par équipe sans devoir bricoler ça soi-même côté billing de chaque provider.\nSources LiteLLM - GitHub LiteLLM - Documentation ","date":"2026-09-08T14:38:58+02:00","image":"/posts/til-litellm-finops/index.png","permalink":"/posts/til-litellm-finops/","title":"LiteLLM pour piloter une stratégie FinOps sur les tokens LLM"},{"content":"C\u0026rsquo;est fait : j\u0026rsquo;ai obtenu la certification HashiCorp Certified: Terraform Associate aujourd\u0026rsquo;hui, au terme de la série d\u0026rsquo;articles commencée avec l\u0026rsquo;objectif certification.\nL\u0026rsquo;examen Pas de mauvaise surprise sur le fond : les sujets couverts correspondaient à ce que j\u0026rsquo;avais préparé au fil des huit précédents articles, suivant l\u0026rsquo;exam guide point par point. Le seul vrai piège, ce sont certaines tournures de questions volontairement ambiguës, qui font douter même quand on connaît la réponse. Il faut prendre le temps de bien relire l\u0026rsquo;énoncé avant de répondre.\nCe qui a le plus aidé Écrire les articles au fil de l\u0026rsquo;eau. Reformuler chaque module avec mes propres mots a forcé la clarification de ce que je pensais déjà maîtriser, et a fait ressortir les zones floues. La mise en pratique sur un cas concret (hub \u0026amp; spoke sur GCP) plutôt que des exemples jetables : ça ancre les concepts (state, modules, workspaces) bien mieux que la lecture seule. Les questions type examen, pour se confronter au format réel et repérer les points faibles avant le jour J. La suite Direction la certification Google Professional Cloud Architect (PCA), pour élargir cette base Terraform à une vision plus large de l\u0026rsquo;architecture cloud sur GCP.\n","date":"2026-09-03T14:58:35+02:00","image":"/posts/terraform-associate-certification/image.png","permalink":"/posts/terraform-associate-certification/","title":"Terraform Associate #9 — Certifié !"},{"content":"Après les sept premiers modules de ma préparation à la certification Terraform Associate (fondamentaux, IaC, workflow, configuration, modules, state,\u0026hellip;), place à la pratique : je mets ces connaissances en application en construisant une architecture réseau hub \u0026amp; spoke sur GCP.\nIntroduction Une architecture hub \u0026amp; spoke, c\u0026rsquo;est comme un aéroport de correspondance : un hub central qui concentre les ressources et services partagés, et des spokes qui y sont raccordés mais qui n\u0026rsquo;ont pas de liaison directe entre eux. Tout le trafic transite par le hub.\nAppliqué à un réseau cloud, ça donne :\nun hub : un VPC central qui héberge les ressources partagées (passerelle NAT, connectivité vers l\u0026rsquo;extérieur, éventuellement pare-feu et DNS centralisés) ; des spokes : des VPC isolés par environnement, ici dev et prod, connectés au hub mais étanches l\u0026rsquo;un par rapport à l\u0026rsquo;autre. L\u0026rsquo;intérêt : mutualiser ce qui peut l\u0026rsquo;être tout en gardant une isolation forte entre environnements, un cas d\u0026rsquo;usage classique pour tester en conditions réelles les notions vues dans les articles précédents (modules, state, workflow).\nÀ la fin de ce tutoriel, vous saurez construire cette architecture sur GCP avec Terraform.\nPrérequis Avoir suivi les articles précédents de la série Terraform Associate, notamment sur les modules et le state. Un compte GCP avec un projet. Terraform installé et le CLI gcloud configuré. Architecture Voici l\u0026rsquo;architecture visée :\ngraph TB subgraph HUB[\"hub-vpc — 10.0.0.0/24\"] HubSub[\"hub-subnet\"] HubVM[\"hub-vm\"] HubSub --- HubVM end subgraph DEV[\"spoke-dev-vpc — 10.1.0.0/24\"] DevSub[\"spoke-dev-subnet\"] DevVM[\"spoke-dev-vm\"] DevNAT[\"Cloud Router + Cloud NAT\"] DevSub --- DevVM DevSub --- DevNAT end subgraph PROD[\"spoke-prod-vpc — 10.2.0.0/24\"] ProdSub[\"spoke-prod-subnet\"] ProdVM[\"spoke-prod-vm\"] ProdNAT[\"Cloud Router + Cloud NAT\"] ProdSub --- ProdVM ProdSub --- ProdNAT end DEV \u003c--\u003e|VPC Peering| HUB PROD \u003c--\u003e|VPC Peering| HUB DevNAT --\u003e Internet((\"Internet\")) ProdNAT --\u003e Internet VPC Subnet CIDR hub-vpc hub-subnet 10.0.0.0/24 spoke-dev-vpc spoke-dev-subnet 10.1.0.0/24 spoke-prod-vpc spoke-prod-subnet 10.2.0.0/24 Chaque spoke aura un peering bidirectionnel avec le hub (hub_to_spoke / spoke_to_hub); aucun peering entre les différent spoke, pas de NAT ni IP publique pour les VM.\nIterations 1. Mise en place de l\u0026rsquo;infrastructure de base Ici l\u0026rsquo;idée a été de partir d\u0026rsquo;une configuration simple en laissant volontairement des pistes d\u0026rsquo;améliorations. On part d\u0026rsquo;une configuration qui fonctionne et petit à petit on vise quelque chose d\u0026rsquo;optimisé et portable entreprise.\nVoici l\u0026rsquo;architecture obtenue :\n. ├── .gitignore ├── .terraform.lock.hcl ├── README.md ├── compute.tf ├── firewall.tf ├── main.tf ├── peering.tf ├── routing.tf ├── subnet.tf ├── variables.tf └── vpc.tf Une structure plate, un fichier par type de ressource (VPC, subnet, firewall, peering, routing, compute), le tout dans un seul state local.\nLa totalité de la configuration est disponible ici.\n2. Refactoring à l\u0026rsquo;aide de modules Si on analyse la configuration précédente, on voit rapidement que pas mal de configuration est dupliquée :\nresource \u0026#34;google_compute_instance\u0026#34; \u0026#34;hub_vm\u0026#34; { name = \u0026#34;hub-vm\u0026#34; machine_type = \u0026#34;e2-micro\u0026#34; zone = \u0026#34;europe-west1-b\u0026#34; boot_disk { initialize_params { image = \u0026#34;debian-cloud/debian-13\u0026#34; } } network_interface { network = google_compute_network.hub.id subnetwork = google_compute_subnetwork.hub.id # pas de access_config =\u0026gt; pas d\u0026#39;IP publique } } resource \u0026#34;google_compute_instance\u0026#34; \u0026#34;spoke_vm\u0026#34; { for_each = var.spokes name = \u0026#34;${each.key}-vm\u0026#34; machine_type = \u0026#34;e2-micro\u0026#34; zone = \u0026#34;europe-west1-b\u0026#34; boot_disk { initialize_params { image = \u0026#34;debian-cloud/debian-13\u0026#34; } } network_interface { network = google_compute_network.spoke[each.key].id subnetwork = google_compute_subnetwork.spoke[each.key].id } } Créons donc un module compute:\nmain.tf\nresource \u0026#34;google_compute_instance\u0026#34; \u0026#34;this\u0026#34; { name = \u0026#34;${var.name}-vm\u0026#34; machine_type = \u0026#34;${var.machine_type}\u0026#34; zone = \u0026#34;europe-west1-b\u0026#34; boot_disk { initialize_params { image = \u0026#34;debian-cloud/debian-13\u0026#34; } } network_interface { network = var.network_interface.network subnetwork = var.network_interface.subnetwork } } variables.tf\nvariable \u0026#34;name\u0026#34; { type = string description = \u0026#34;Nom logique de la machine\u0026#34; } variable \u0026#34;machine_type\u0026#34; { type = string description = \u0026#34;Type de la machine\u0026#34; default = \u0026#34;e2-micro\u0026#34; } variable \u0026#34;network_interface\u0026#34;{ type = object({ network = string subnetwork = string }) } outputs.tf\noutput \u0026#34;compute_id\u0026#34; { value = google_compute_instance.this.id } output \u0026#34;network_name\u0026#34; { value = google_compute_instance.this.name } Dans notre module root il suffit alors d\u0026rsquo;appeler ce module via :\nmodule \u0026#34;hub_vm\u0026#34; { source = \u0026#34;./modules/compute\u0026#34; name = \u0026#34;hub\u0026#34; network_interface = { network = module.hub.network_id subnetwork = module.hub.subnetwork_id } } module \u0026#34;spoke_vm\u0026#34; { source = \u0026#34;./modules/compute\u0026#34; for_each = var.spokes name = \u0026#34;spoke-${each.key}\u0026#34; network_interface = { network = module.spoke[each.key].network_id subnetwork = module.spoke[each.key].subnetwork_id } } Version fonctionnelle ici\n3. Ajout de Lifecycle Le VPC hub est une ressource critique de notre infrastructure puisque les deux spoke passe par lui. Afin de faire échouer un plan si on tente un destroy sur cette ressource on peut s\u0026rsquo;appuyer sur le meta argument lifecycle.\nresource \u0026#34;google_compute_network\u0026#34; \u0026#34;hub\u0026#34; { name = \u0026#34;hub-vpc\u0026#34; auto_create_subnetworks = false lifecycle { prevent_destroy = true } } 4. Bloc de validation Dans la même idée on peut mettre en place une règle de validation sur le module vpc pour empêcher le chevauchement de cidr avec le hub.\nmodules/vpc/variables.tf\nvariable \u0026#34;cidr\u0026#34; { type = string description = \u0026#34;Subnet CIDR range (e.g. 10.0.0.0/24)\u0026#34; validation { condition = var.cidr != \u0026#34;10.0.0.0/24\u0026#34; error_message = \u0026#34;The cidr range shouldn\u0026#39;t overlap with hub\u0026#39;s one.\u0026#34; } } Un plan qui tente ce chevauchement aura ce message d\u0026rsquo;erreur :\n╷ │ Error: Invalid value for variable │ │ on main.tf line 29, in module \u0026#34;spoke\u0026#34;: │ 29: cidr = each.value.cidr │ ├──────────────── │ │ var.cidr is \u0026#34;10.0.0.0/24\u0026#34; │ │ The cidr range shouldn\u0026#39;t overlap with hub\u0026#39;s one. │ │ This was checked by the validation rule at │ modules/vpc/variables.tf:15,3-13. 5. Data sources Autre ajout mineur mais qui permet de mettre en exergue l\u0026rsquo;instruction data, l\u0026rsquo;image Debian est codée en dur (ex: debian-cloud/debian-13). Si dans 6 mois Google dépréciée cette image, l\u0026rsquo;apply échoue. Avec data \u0026quot;google_compute_image\u0026quot; { family = \u0026quot;debian-13\u0026quot;, project = \u0026quot;debian-cloud\u0026quot; }, Terraform récupère toujours la dernière image au moment de l\u0026rsquo;apply, sans maintenance manuelle.\ndata \u0026#34;google_compute_image\u0026#34; \u0026#34;debian\u0026#34; { family = \u0026#34;debian-13\u0026#34; project = \u0026#34;debian-cloud\u0026#34; } resource \u0026#34;google_compute_instance\u0026#34; \u0026#34;this\u0026#34; { name = \u0026#34;${var.name}-vm\u0026#34; machine_type = \u0026#34;${var.machine_type}\u0026#34; zone = \u0026#34;europe-west1-b\u0026#34; boot_disk { initialize_params { #Utilisation de la data créée plus haut image = data.google_compute_image.debian.self_link } } 6. Séparation des states Enfin, un changement un peu plus costaud mais qui peut réellement être pertinent en entreprise : séparé la partie réseau dans un state dédié.\nA ce stade, modifier une VM oblige à revoir un plan qui inclut tout le réseau (VPC, firewall, routes), ce qui est risqué. En séparant en deux stacks (network/ et compute/), l\u0026rsquo;équipe réseau pourrait appliquer ses changements indépendamment de l\u0026rsquo;équipe applicative. La partie compute lit ensuite les infos du VPC via un bloc data qui pointe vers le state network au lieu de dupliquer les valeurs en dur : si le réseau change, le compute suit automatiquement au prochain apply.\nLe découpage final peut être retrouvé ici.\nAvant :\n. ├── main.tf ├── providers.tf ├── variables.tf └── modules/ ├── compute/ ├── firewall/ ├── peering/ ├── routing/ └── vpc/ Après :\n. ├── modules/ │ ├── compute/ │ ├── firewall/ │ ├── peering/ │ ├── routing/ │ └── vpc/ ├── network/ │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ └── variables.tf └── compute/ ├── main.tf └── providers.tf Le dossier modules/ reste commun aux deux stacks. Ce qui était à la racine (main.tf, providers.tf, variables.tf, donc un seul state) est éclaté en deux : network/ (VPC, subnets, firewall, peering, routing) et compute/ (VMs), chacun avec son propre backend et donc son propre state.\ncompute/ n\u0026rsquo;a plus de variables.tf : il récupère les infos réseau via un bloc data \u0026quot;terraform_remote_state\u0026quot; qui pointe vers le state de network/, plutôt que de dupliquer des valeurs en dur.\nHCP Terraform Comme nous l\u0026rsquo;avons vu dans l\u0026rsquo;article précédent, HCP Terraform est la plateforme cloud d\u0026rsquo;Hashicorp. J\u0026rsquo;ai voulu l\u0026rsquo;essayer pour découvrir plus en détail ses capacités.\nConfiguration Tout démarre via la création d\u0026rsquo;un compte Terraform/HCP, vous avez ensuite la possibilité de lier ce compte à un VCS. Pour ma part j\u0026rsquo;ai choisi de le faire pour bénéficier de l\u0026rsquo;intégration VCS avec Github.\nEnfin il faut créer une organisation, bernedotcom pour ma part.\nDernière étape : créer un workspace pour cela vous avez deux possibilités :\nLancer terraform login depuis votre poste local + terraform init Le créer manuellement Migration du backend Il faut désormais indiquer à notre configuration que le backend est HCP désormais via l\u0026rsquo;instruction cloud. Suite à la séparation des states j\u0026rsquo;ai deux workspaces à gérer : gcp-hub-and-spoke-network et gcp-hub-and-spoke-compute.\nVoici un exemple de configuration dans network/providers.tf\nterraform { required_providers { google = { source = \u0026#34;hashicorp/google\u0026#34; version = \u0026#34;7.45.0\u0026#34; } } required_version = \u0026#34;\u0026gt;= 1.15\u0026#34; cloud { organization = \u0026#34;bernedotcom\u0026#34; workspaces { name = \u0026#34;gcp-hub-and-spoke-network\u0026#34; } } } Un coup de terraform init pour valider tout ça et c\u0026rsquo;est terminé pour cette partie.\nService account Auparavant terraform était lancé depuis mon poste local et utilisait mon compte personnel GCP pour appliquer la configuration (ce n\u0026rsquo;est évidemment pas une bonne pratique). Maintenant c\u0026rsquo;est HCP qui va lancer les plan \u0026amp; apply depuis une machine distante, il faut alors l\u0026rsquo;autoriser à faire ce type d\u0026rsquo;opérations sur mon projet GCP.\nPour cela deux possibilités :\nUn token d\u0026rsquo;authorization (peu secure) Workload identity (plus complexe à mettre en place mais beaucoup plus robuste). J\u0026rsquo;ai opté pour la workload identity ici :\n# 1. Activer les APIs nécessaires gcloud services enable iam.googleapis.com iamcredentials.googleapis.com sts.googleapis.com --project=hubandspoke-demo # 2. Créer le Workload Identity Pool gcloud iam workload-identity-pools create \u0026#34;hcp-terraform-pool\u0026#34; \\ --project=\u0026#34;hubandspoke-demo\u0026#34; \\ --location=\u0026#34;global\u0026#34; \\ --display-name=\u0026#34;HCP Terraform\u0026#34; # 3. Créer le provider OIDC dans ce pool, restreint à org=bernedotcom / workspaces=gcp-hub-and-spoke-network,gcp-hub-and-spoke-compute gcloud iam workload-identity-pools providers create-oidc \u0026#34;hcp-terraform-provider\u0026#34; \\ --project=\u0026#34;hubandspoke-demo\u0026#34; \\ --location=\u0026#34;global\u0026#34; \\ --workload-identity-pool=\u0026#34;hcp-terraform-pool\u0026#34; \\ --display-name=\u0026#34;HCP Terraform Provider\u0026#34; \\ --attribute-mapping=\u0026#34;google.subject=assertion.sub,attribute.terraform_organization_name=assertion.terraform_organization_name,attribute.terraform_workspace_name=assertion.terraform_workspace_name,attribute.terraform_run_phase=assertion.terraform_run_phase\u0026#34; \\ --attribute-condition=\u0026#34;assertion.terraform_organization_name==\\\u0026#34;bernedotcom\\\u0026#34; \u0026amp;\u0026amp; assertion.terraform_workspace_name in [\\\u0026#34;gcp-hub-and-spoke-network\\\u0026#34;, \\\u0026#34;gcp-hub-and-spoke-compute\\\u0026#34;]\u0026#34; \\ --issuer-uri=\u0026#34;https://app.terraform.io\u0026#34; # 4. Créer le service account que Terraform utilisera gcloud iam service-accounts create \u0026#34;hcp-terraform-runner\u0026#34; \\ --project=\u0026#34;hubandspoke-demo\u0026#34; \\ --display-name=\u0026#34;HCP Terraform runner\u0026#34; # 5. Lui donner les droits nécessaires sur le projet (couvre VPC, subnets, firewall, routeur/NAT, peering, VM) gcloud projects add-iam-policy-binding hubandspoke-demo \\ --member=\u0026#34;serviceAccount:hcp-terraform-runner@hubandspoke-demo.iam.gserviceaccount.com\u0026#34; \\ --role=\u0026#34;roles/compute.networkAdmin\u0026#34; gcloud projects add-iam-policy-binding hubandspoke-demo \\ --member=\u0026#34;serviceAccount:hcp-terraform-runner@hubandspoke-demo.iam.gserviceaccount.com\u0026#34; \\ --role=\u0026#34;roles/compute.instanceAdmin.v1\u0026#34; # roles/compute.networkAdmin exclut explicitement les firewalls, il faut ce rôle en plus gcloud projects add-iam-policy-binding hubandspoke-demo \\ --member=\u0026#34;serviceAccount:hcp-terraform-runner@hubandspoke-demo.iam.gserviceaccount.com\u0026#34; \\ --role=\u0026#34;roles/compute.securityAdmin\u0026#34; # 6. Autoriser le pool WIF à impersonate ce service account (uniquement depuis le workspace gcp-hub-and-spoke-network \u0026amp; compute) gcloud iam service-accounts add-iam-policy-binding \u0026#34;hcp-terraform-runner@hubandspoke-demo.iam.gserviceaccount.com\u0026#34; \\ --project=\u0026#34;hubandspoke-demo\u0026#34; \\ --role=\u0026#34;roles/iam.workloadIdentityUser\u0026#34; \\ --member=\u0026#34;principalSet://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/hcp-terraform-pool/attribute.terraform_workspace_name/gcp-hub-and-spoke-network\u0026#34; gcloud iam service-accounts add-iam-policy-binding \u0026#34;hcp-terraform-runner@hubandspoke-demo.iam.gserviceaccount.com\u0026#34; \\ --project=\u0026#34;hubandspoke-demo\u0026#34; \\ --role=\u0026#34;roles/iam.workloadIdentityUser\u0026#34; \\ --member=\u0026#34;principalSet://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/hcp-terraform-pool/attribute.terraform_workspace_name/gcp-hub-and-spoke-compute\u0026#34; Ensuite, dans les workspaces HCP Terraform (bernedotcom/gcp-hub-and-spoke-network(compute) → Settings → Variables), ajouter en variables d\u0026rsquo;environnement :\nTFC_GCP_PROVIDER_AUTH = true TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL = hcp-terraform-runner@hubandspoke-demo.iam.gserviceaccount.com TFC_GCP_WORKLOAD_PROVIDER_NAME = projects/123456789012/locations/global/workloadIdentityPools/hcp-terraform-pool/providers/hcp-terraform-provider Tour d\u0026rsquo;horizon de l\u0026rsquo;UI Grâce à l\u0026rsquo;intégration VCS le plan est ensuite automatiquement déclenché lors d\u0026rsquo;un nouveau commit sur la branche cible (main dans mon cas).\nVoici le résultat obtenu :\nIl est également possible d\u0026rsquo;obtenir la liste des derniers runs :\nUne fois un plan valide, il est alors possible de l\u0026rsquo;approuver manuellement pour application. Voici le résultat une fois l\u0026rsquo;apply lancé :\nVérification Une fois la totalité des ressources créées il s\u0026rsquo;agit de vérifier le bon fonctionnement de l\u0026rsquo;architecture; pour cela rien de compliqué :\ngcloud compute ssh spoke-dev-vm --zone=europe-west1-b --tunnel-through-iap # from spoke-dev-vm ping 10.0.0.2 # hub — should succeed ping 10.2.0.2 # spoke-prod — should time out Conclusion Cette mise en place d\u0026rsquo;une architecture hub \u0026amp; spoke aura permis de mettre en application concrètement les notions vues tout au long de ma préparation à la certification Terraform Associate. Je suis également satisfait de cet essai d\u0026rsquo;HCP Terraform : la plateforme est agréable à utiliser et tout s\u0026rsquo;est déroulé sans difficulté, de la configuration du workspace jusqu\u0026rsquo;à la workload identity. On obtient une solution GitOps friendly clé en main, sans avoir à gérer soi-même un backend distant ni un pipeline CI/CD pour Terraform.\nLa version finale de l\u0026rsquo;implémentation est disponible ici.\n","date":"2026-08-31T11:08:24+02:00","image":"/posts/terraform-associate-hub-spoke-gcp/index.png","permalink":"/posts/terraform-associate-hub-spoke-gcp/","title":"Terraform Associate #8 — Mise en pratique : hub \u0026 spoke sur GCP"},{"content":"Suite de ma série sur la certification Terraform Associate, après le module sur le state. Dernier objectif de l\u0026rsquo;exam guide, \u0026ldquo;Understand HCP Terraform capabilities\u0026rdquo; : cet article couvre les workspaces, l\u0026rsquo;intégration VCS, le registre privé, Sentinel/OPA et l\u0026rsquo;exécution en automation.\nQu\u0026rsquo;est-ce que HCP Terraform HCP Terraform (anciennement Terraform Cloud) est le service managé de HashiCorp : state distant, locking, runs plan/apply centralisés, et couche de gouvernance en plus du CLI open source.\nWorkspaces : CLI vs HCP Terraform Le mot \u0026ldquo;workspace\u0026rdquo; désigne deux choses différentes selon le contexte :\nWorkspace CLI (OSS) Workspace HCP Terraform Portée Plusieurs états pour une même configuration locale (terraform workspace new) Unité complète : configuration + variables + state + historique des runs State Un fichier par workspace, même backend State propre à chaque workspace Isolation Faible, même répertoire de code Forte, généralement un repo/dossier ↔ un workspace Un workspace HCP Terraform correspond en général à un environnement (dev/staging/prod) ou un composant d\u0026rsquo;infrastructure, chacun avec ses propres variables et son propre historique.\nLes workspaces peuvent partager des informations entre eux via des outputs racine. Un workspace utilisant l\u0026rsquo;exécution distante peut accéder aux outputs d\u0026rsquo;un autre workspace via une source de données terraform_remote_state, sous réserve des contrôles d\u0026rsquo;accès par workspace. Des run triggers workspace-à-workspace permettent de faire réagir automatiquement un workspace en aval quand ses dépendances changent. Intégration VCS Un workspace peut être connecté à un dépôt Git (GitHub, GitLab, Bitbucket\u0026hellip;). Chaque push ou pull request déclenche automatiquement un run.\nGénère un plan en preview sur les pull requests, avant merge. Déclenche un apply sur push vers la branche configurée (selon le mode d\u0026rsquo;apply du workspace). Évite d\u0026rsquo;exécuter Terraform depuis un poste local : le run s\u0026rsquo;exécute sur l\u0026rsquo;infrastructure HCP Terraform. Registre privé En plus du Terraform Registry public, HCP Terraform propose un registre privé pour publier des modules et des providers internes, avec la même syntaxe source = \u0026quot;app.terraform.io/\u0026lt;org\u0026gt;/\u0026lt;nom\u0026gt;/\u0026lt;provider\u0026gt;\u0026quot; que le registre public.\nEstimation des coûts HCP Terraform peut estimer le coût mensuel des ressources créées, modifiées ou détruites par un plan, pour les providers supportés (AWS, Azure, GCP).\nL\u0026rsquo;estimation apparaît directement dans le run, avant l\u0026rsquo;apply. Elle peut être utilisée dans une policy Sentinel (ex. bloquer un plan si le delta de coût dépasse un seuil). Elle reste une estimation basée sur le pricing public du provider, pas une facture réelle (remises, réservations\u0026hellip; non prises en compte). Sentinel et OPA Sentinel (propriétaire HashiCorp) et Open Policy Agent (OPA) permettent d\u0026rsquo;appliquer du policy as code : des règles évaluées automatiquement entre le plan et l\u0026rsquo;apply, pour bloquer un changement qui ne respecte pas une politique organisationnelle.\n# exemple de règle Sentinel main = rule { all tfplan.resources.aws_instance as _, instances { all instances as _, r { r.applied.instance_type in [\u0026#34;t3.micro\u0026#34;, \u0026#34;t3.small\u0026#34;] } } } Advisory : avertit sans bloquer. Soft mandatory : bloque, mais un utilisateur autorisé peut passer outre. Hard mandatory : bloque sans possibilité de contournement. Cas d\u0026rsquo;usage typiques : imposer des tags obligatoires, restreindre les types d\u0026rsquo;instance autorisés, interdire l\u0026rsquo;ouverture de ports sensibles.\nRun tasks Les run tasks intègrent un service tiers (scan de sécurité, coût, conformité\u0026hellip;) dans le cycle de vie d\u0026rsquo;un run, à un stade précis (post-plan, pre-apply, post-apply).\nHCP Terraform envoie les détails du run à l\u0026rsquo;endpoint du service tiers via une API webhook. Le service répond avec un statut passed ou failed. Comme Sentinel/OPA, la sévérité peut être advisory (informatif) ou mandatory (bloque l\u0026rsquo;apply si le check échoue). Contrairement à Sentinel/OPA qui évalue localement le plan avec des règles écrites en Sentinel ou Rego, un run task délègue la vérification à un outil externe (ex. Snyk, Bridgecrew, Infracost).\nRBAC HCP Terraform gère les permissions à deux niveaux :\nNiveau organisation : rôles Owner, ou permissions personnalisées (gestion des membres, des teams, des paramètres). Niveau workspace : accès par team, avec des permissions fixes (Read, Plan, Write, Admin) ou personnalisées (ex. autoriser l\u0026rsquo;apply sans donner accès aux variables sensibles). Les teams regroupent des utilisateurs et se voient assigner des permissions par workspace, ce qui permet par exemple de restreindre l\u0026rsquo;apply en prod à une team dédiée.\nRun triggers et notifications Les run triggers permettent de chaîner des workspaces : un apply réussi sur un workspace peut déclencher automatiquement un run sur un autre (ex. réseau → applicatif). Les notifications (Slack, email, webhook générique) informent des événements d\u0026rsquo;un workspace (run créé, en attente, terminé, erreur). Les run triggers sont destinés aux workspaces qui dépendent d\u0026rsquo;informations ou d\u0026rsquo;infrastructure produites par d\u0026rsquo;autres workspaces. Un run déclenché par un run trigger n\u0026rsquo;est pas auto-appliqué, sauf si l\u0026rsquo;option \u0026ldquo;Auto-apply run triggers\u0026rdquo; est activée. Workspaces vs Stacks HCP Terraform Stacks est une fonctionnalité pour orchestrer plusieurs composants d\u0026rsquo;infrastructure liés comme une seule unité :\nWorkspace : une configuration, un state, un cycle de vie indépendant. Pour chaîner plusieurs workspaces (ex. réseau → applicatif), il faut des run triggers ou de l\u0026rsquo;orchestration externe. Stack : regroupe plusieurs composants (chacun avec son propre state) déployés ensemble sur plusieurs environnements ou régions, avec un ordonnancement natif des dépendances entre composants et une configuration écrite en langage Terraform Stacks (basé sur HCL, fichiers .tfcomponent.hcl). En résumé, un Stack remplace le pattern \u0026ldquo;plusieurs workspaces + run triggers\u0026rdquo; par une orchestration native quand les composants sont fortement couplés.\ngraph TD Org[Organisation] --\u003e Proj[Projet] Proj --\u003e WS1[Workspace: réseau] Proj --\u003e WS2[Workspace: applicatif] Proj --\u003e Stack[Stack] Stack --\u003e C1[Composant: state 1] Stack --\u003e C2[Composant: state 2]Health checks HCP Terraform peut vérifier périodiquement l\u0026rsquo;état d\u0026rsquo;un workspace en dehors des runs déclenchés manuellement :\nDrift detection : lance un plan régulier pour détecter si l\u0026rsquo;infrastructure réelle a divergé du state (changement fait hors Terraform). Continuous validation : réévalue en continu les blocs check de la configuration (ex. certificat encore valide, endpoint toujours joignable) même sans nouveau run. Ces contrôles remontent des alertes sans forcément déclencher d\u0026rsquo;apply, ce qui aide à repérer une dérive avant qu\u0026rsquo;elle ne cause un incident.\nExécution en automation HCP Terraform peut lancer des runs via trois mécanismes :\nVCS-driven : déclenché par un push/PR sur le dépôt connecté (voir ci-dessus). API-driven : déclenché par un appel à l\u0026rsquo;API HCP Terraform, utile pour l\u0026rsquo;intégrer à un pipeline CI/CD externe. CLI-driven : terraform plan/apply lancés localement, mais exécutés à distance sur HCP Terraform grâce au bloc cloud (voir l\u0026rsquo;article sur le state). Dans les trois cas, l\u0026rsquo;exécution du run (et l\u0026rsquo;accès aux credentials associés) a lieu sur l\u0026rsquo;infrastructure HCP Terraform, pas sur le poste ou l\u0026rsquo;agent qui déclenche l\u0026rsquo;opération.\nMigrer son state vers HCP Terraform Pour basculer un state existant (local ou backend distant type S3) vers HCP Terraform :\nAjouter un bloc cloud dans la configuration, avec l\u0026rsquo;organisation et le nom du workspace cible : terraform { cloud { organization = \u0026#34;mon-org\u0026#34; workspaces { name = \u0026#34;mon-workspace\u0026#34; } } } Lancer terraform login pour s\u0026rsquo;authentifier auprès de HCP Terraform. Lancer terraform init : Terraform détecte l\u0026rsquo;ancien backend et propose de migrer le state existant vers le workspace HCP Terraform. Confirmer la migration : le state est copié, le workspace HCP Terraform en devient la nouvelle source de vérité. Une fois migré, le mode d\u0026rsquo;exécution du workspace (remote par défaut, ou local) détermine si les plan/apply s\u0026rsquo;exécutent sur l\u0026rsquo;infrastructure HCP Terraform ou toujours en local avec state distant.\nCe que je retiens Workspace CLI ≠ workspace HCP Terraform : le premier isole un state, le second isole un environnement complet (config, variables, historique). Sentinel/OPA s\u0026rsquo;évaluent entre plan et apply : c\u0026rsquo;est le point de contrôle pour bloquer un changement non conforme avant qu\u0026rsquo;il ne soit appliqué. Trois façons de déclencher un run : VCS, API ou CLI, mais l\u0026rsquo;exécution reste toujours centralisée côté HCP Terraform. Fin de cette série sur le \u0026ldquo;learning path\u0026rdquo; Terraform Associate. La suite consistera à développer un projet pour mettre en application toutes les connaissances acquises avec le learning path.\n","date":"2026-08-18T17:30:00+02:00","image":"/posts/terraform-associate-hcp-terraform/module7.png","permalink":"/posts/terraform-associate-hcp-terraform/","title":"Terraform Associate #7 — HCP Terraform"},{"content":"Suite de ma série sur la certification Terraform Associate, après les modules. Sixième objectif de l\u0026rsquo;exam guide, \u0026ldquo;Implement and maintain state\u0026rdquo; : cet article couvre le backend local, le locking, le remote state, l\u0026rsquo;authentification et la gestion des secrets dans le state.\nQu\u0026rsquo;est-ce que le state Le state est un fichier (terraform.tfstate, au format JSON) dans lequel Terraform stocke le mapping entre les ressources déclarées dans la configuration et les objets réels de l\u0026rsquo;infrastructure. C\u0026rsquo;est ce fichier qui permet à Terraform de savoir quoi créer, modifier ou détruire à chaque plan/apply, sans avoir à interroger l\u0026rsquo;ensemble du provider à chaque fois.\nBackend local Par défaut, Terraform utilise le backend local : le state est stocké dans un fichier terraform.tfstate à la racine du répertoire de travail.\nSimple à utiliser, aucune configuration requise. Ne convient pas au travail en équipe : pas de lock partagé, pas de state partagé entre collaborateurs, secret stocké en clair sur le poste local. State locking Avant toute opération qui écrit dans le state (apply, parfois plan), Terraform tente de le verrouiller pour empêcher deux opérations concurrentes de le corrompre.\nLe locking est supporté par la plupart des backends distants (S3, Azure Blob Storage, HCP Terraform\u0026hellip;) Si le lock ne peut pas être acquis, Terraform bloque l\u0026rsquo;opération et indique qui détient le lock. L\u0026rsquo;option -lock=false permet de désactiver ce comportement, à éviter en usage normal. Backend distant Un backend distant stocke le state en dehors du poste local (bucket S3, Azure Blob Storage, GCS, HCP Terraform\u0026hellip;). Il se déclare dans un bloc backend, imbriqué dans le bloc terraform.\nterraform { backend \u0026#34;s3\u0026#34; { bucket = \u0026#34;mon-bucket-tfstate\u0026#34; key = \u0026#34;prod/network/terraform.tfstate\u0026#34; region = \u0026#34;eu-west-1\u0026#34; dynamodb_table = \u0026#34;terraform-locks\u0026#34; encrypt = true } } Avantages par rapport au backend local :\nState partagé entre plusieurs collaborateurs ou pipelines CI/CD. Locking natif. Chiffrement. Historique/versionning du state selon le backend. Changer de backend nécessite de relancer terraform init, qui propose alors de migrer le state existant vers le nouveau backend.\nHCP Terraform HCP Terraform (anciennement Terraform Cloud) peut aussi servir de backend, via un bloc cloud plutôt que backend :\nterraform { cloud { organization = \u0026#34;mon-org\u0026#34; workspaces { name = \u0026#34;prod-network\u0026#34; } } } En plus du stockage du state, HCP Terraform ajoute le locking, un historique des runs, et l\u0026rsquo;exécution du plan/apply à distance.\nAuthentification aux backends Chaque backend a son propre mécanisme d\u0026rsquo;authentification, indépendant de celui des providers :\nS3 : credentials AWS (variables d\u0026rsquo;environnement, profil CLI, rôle IAM). Azure Blob Storage : Azure CLI, service principal, managed identity. HCP Terraform : token d\u0026rsquo;API, généralement stocké dans ~/.terraform.d/credentials.tfrc.json via terraform login. Ces credentials ne doivent jamais être codés en dur dans la configuration.\nEffet de terraform refresh terraform refresh (ou l\u0026rsquo;étape de refresh implicite lors d\u0026rsquo;un plan/apply) interroge les providers pour comparer l\u0026rsquo;état réel de l\u0026rsquo;infrastructure au state enregistré, et met à jour le state en conséquence.\nNe modifie jamais l\u0026rsquo;infrastructure réelle, uniquement le fichier de state. Permet de détecter un drift (modification manuelle en dehors de Terraform). Peut être désactivé avec -refresh=false pour accélérer un plan quand on est sûr qu\u0026rsquo;il n\u0026rsquo;y a pas eu de changement externe. Secrets dans le state Le state contient les valeurs de toutes les ressources qu\u0026rsquo;il suit, y compris les attributs sensibles (mots de passe générés, clés, tokens), même si la variable ou l\u0026rsquo;output correspondant est marqué sensitive. Comme évoqué dans l\u0026rsquo;article sur la configuration, sensitive masque seulement l\u0026rsquo;affichage dans les logs et l\u0026rsquo;UI, il ne retire rien du state.\nBonnes pratiques :\nToujours utiliser un backend distant chiffré at rest plutôt que le backend local. Restreindre l\u0026rsquo;accès au state via des contrôles d\u0026rsquo;accès (IAM, permissions du backend). Activer les logs d\u0026rsquo;audit du backend pour tracer les accès au state. Préférer ephemeral/write-only quand c\u0026rsquo;est possible, pour éviter que la valeur n\u0026rsquo;atterrisse dans le state. Commandes terraform state Terraform fournit un sous-ensemble de commandes dédiées à l\u0026rsquo;inspection et la manipulation du state, sans passer par un apply :\nterraform state list # liste les ressources trackées terraform state show aws_instance.web # détail d\u0026#39;une ressource dans le state terraform state mv aws_instance.web aws_instance.app # renomme/déplace une ressource dans le state terraform state rm aws_instance.web # retire une ressource du state (sans la détruire) terraform state pull # affiche le state distant au format JSON terraform state push # écrase le state distant (usage avancé, à risque) terraform state list est généralement la première commande à lancer pour retrouver l\u0026rsquo;adresse exacte d\u0026rsquo;une ressource avant d\u0026rsquo;utiliser show, mv ou rm.\nImporter une ressource existante terraform import rattache une ressource déjà existante (créée manuellement ou par un autre outil) à un bloc resource du state, sans la recréer.\nterraform import aws_instance.web i-0abcd1234 Le bloc resource \u0026quot;aws_instance\u0026quot; \u0026quot;web\u0026quot; { ... } doit déjà exister dans la configuration : la commande ne fait qu\u0026rsquo;ajouter l\u0026rsquo;entrée au state, elle ne génère aucun HCL. Il faut ensuite compléter le bloc à la main pour que la configuration corresponde à la ressource réelle, sinon le prochain plan proposera des changements pour aligner l\u0026rsquo;un sur l\u0026rsquo;autre. Fonctionne ressource par ressource : pas d\u0026rsquo;import groupé avec la commande CLI. Logs Terraform expose des logs de debug via la variable d\u0026rsquo;environnement TF_LOG, utile pour diagnostiquer un comportement inattendu (au niveau du core ou d\u0026rsquo;un provider).\nexport TF_LOG=DEBUG terraform plan Niveaux disponibles, du plus verbeux au moins verbeux : TRACE, DEBUG, INFO, WARN, ERROR. TF_LOG_CORE et TF_LOG_PROVIDER permettent de cibler respectivement le cœur de Terraform ou les providers, indépendamment l\u0026rsquo;un de l\u0026rsquo;autre. TF_LOG_PATH redirige les logs vers un fichier plutôt que stdout/stderr : export TF_LOG=TRACE export TF_LOG_PATH=./terraform.log Penser à unset TF_LOG (et TF_LOG_PATH) une fois le diagnostic terminé, sinon chaque commande reste verbeuse. Ces logs tracent l\u0026rsquo;exécution du CLI (appels aux providers, résolution de la configuration\u0026hellip;), sans lien avec le contenu du state. Ce que je retiens Le backend local ne convient pas au travail en équipe : pas de locking partagé, secrets en clair sur le poste. sensitive ≠ absent du state : seul ephemeral garantit qu\u0026rsquo;une valeur n\u0026rsquo;y est jamais écrite. terraform refresh ne touche jamais l\u0026rsquo;infrastructure réelle, seulement le fichier de state, pour détecter un drift. terraform import rattache une ressource existante au state sans la recréer, mais ne génère pas la configuration correspondante : à compléter à la main. TF_LOG cible le core ou les providers séparément (TF_LOG_CORE/TF_LOG_PROVIDER), et n\u0026rsquo;a aucun effet sur le contenu du state. Prochaine (et dernière) étape de la série : HCP Terraform.\n","date":"2026-08-18T16:05:00+02:00","image":"/posts/terraform-associate-state-management/module6.png","permalink":"/posts/terraform-associate-state-management/","title":"Terraform Associate #6 — Le state"},{"content":"Nous en sommes à un peu plus de la moitié du learning path pour l\u0026rsquo;obtention de la certification Terraform Associate, après le module sur la configuration. Cinquième objectif de l\u0026rsquo;exam guide, \u0026ldquo;Interact with Terraform modules\u0026rdquo; : cet article couvre les sources de module, les inputs/outputs, la portée des variables et le versionning.\nQu\u0026rsquo;est-ce qu\u0026rsquo;un module Un module est un ensemble de fichiers .tf regroupés dans un même répertoire. Chaque configuration Terraform a au moins un module : le module racine, celui dans lequel on lance terraform apply. Les modules qu\u0026rsquo;il appelle sont des modules enfants, et peuvent eux-mêmes en appeler d\u0026rsquo;autres.\nRegrouper des ressources dans un module permet de les réutiliser, de les partager entre équipes et de simplifier la configuration racine.\nUtilisation module \u0026#34;consul\u0026#34; { source = \u0026#34;hashicorp/consul/aws\u0026#34; version = \u0026#34;0.1.0\u0026#34; servers = 3 } source est le seul argument obligatoire : il indique où trouver le code du module. terraform init télécharge et met en cache tout module référencé par la configuration. Les autres arguments du bloc (servers ici) correspondent aux variables d\u0026rsquo;input déclarées par le module. Sources de module L\u0026rsquo;argument source détermine où et comment Terraform va chercher le module :\nChemin local (./modules/consul) : pas de téléchargement, le module suit le cycle de vie du code parent. Ne supporte pas le versionning. Terraform Registry (hashicorp/consul/aws, format \u0026lt;namespace\u0026gt;/\u0026lt;nom\u0026gt;/\u0026lt;provider\u0026gt;) : source la plus courante pour les modules publics ou privés. GitHub / Git / HTTP (git::https://example.com/vpc.git) : utile pour des modules non publiés sur un registre. Buckets S3 / GCS : pour des modules stockés en interne sans registre dédié. Seules les sources de registre (public ou privé) supportent l\u0026rsquo;argument version.\nInputs et outputs Un module communique avec l\u0026rsquo;extérieur via des variables d\u0026rsquo;input et des outputs, comme évoqué dans l\u0026rsquo;article précédent sur les variables \u0026amp; outputs.\n# modules/vpc/variables.tf variable \u0026#34;cidr_block\u0026#34; { type = string } # modules/vpc/outputs.tf output \u0026#34;vpc_id\u0026#34; { value = aws_vpc.this.id } # racine module \u0026#34;network\u0026#34; { source = \u0026#34;./modules/vpc\u0026#34; cidr_block = \u0026#34;10.0.0.0/16\u0026#34; } resource \u0026#34;aws_subnet\u0026#34; \u0026#34;web\u0026#34; { vpc_id = module.network.vpc_id } Les inputs se passent comme des arguments du bloc module. Les outputs se référencent avec module.\u0026lt;nom\u0026gt;.\u0026lt;output\u0026gt;. Portée des variables Une variable ou un local défini dans un module n\u0026rsquo;est pas automatiquement visible dans les autres modules, parent ou enfant : chaque module a sa propre portée. Pour faire transiter une valeur :\nDu parent vers l\u0026rsquo;enfant : passer un input au bloc module. De l\u0026rsquo;enfant vers le parent : déclarer un output dans l\u0026rsquo;enfant, puis le référencer via module.\u0026lt;nom\u0026gt;.\u0026lt;output\u0026gt;. Entre deux modules enfants : faire remonter la valeur au parent via un output, puis la repasser en input à l\u0026rsquo;autre module. Il n\u0026rsquo;existe pas de variable globale partagée implicitement entre modules.\nRegistre public de modules Le Terraform Registry héberge des modules publics, identifiés par \u0026lt;NAMESPACE\u0026gt;/\u0026lt;NOM\u0026gt;/\u0026lt;PROVIDER\u0026gt; (ex. terraform-aws-modules/vpc/aws). Chaque module y expose sa documentation, ses inputs/outputs et l\u0026rsquo;historique de ses versions. Les modules verified sont validés par HashiCorp ou un partenaire.\nVersionning des modules Pour les sources de registre, l\u0026rsquo;argument version accepte des contraintes :\nOpérateur Effet = (ou rien) Version exacte != Exclut une version \u0026gt;, \u0026gt;=, \u0026lt;, \u0026lt;= Comparaison ~\u0026gt; Autorise seulement le dernier composant à incrémenter (ex. ~\u0026gt; 2.1 autorise 2.1.x mais pas 2.2) module \u0026#34;consul\u0026#34; { source = \u0026#34;hashicorp/consul/aws\u0026#34; version = \u0026#34;~\u0026gt; 0.1\u0026#34; } Fixer une version évite qu\u0026rsquo;une mise à jour du module casse la configuration lors d\u0026rsquo;un prochain terraform init. Sans contrainte, Terraform utilise la dernière version disponible.\nCe que je retiens Un module = un répertoire de fichiers .tf : la configuration racine est elle-même un module. Seules les sources de registre supportent le versionning : un chemin local ou une source git suit le cycle de vie du code parent. Pas de portée globale : toute communication entre modules passe explicitement par des inputs et des outputs. Prochaine étape de la série : le state management.\n","date":"2026-08-17T16:44:02+02:00","image":"/posts/terraform-associate-modules/module5.png","permalink":"/posts/terraform-associate-modules/","title":"Terraform Associate #5 — Les modules"},{"content":"Suite de ma série sur la certification Terraform Associate, après le module sur le workflow. Le quatrième objectif de l\u0026rsquo;exam guide, \u0026ldquo;Read and write configuration\u0026rdquo;, est le plus dense. Celui-ci couvre les resources, les data sources, le système de types et les expressions conditionnelles, ainsi que la gestion des sensitive data.\nResources Le bloc resource est l\u0026rsquo;unité de base de la configuration : c\u0026rsquo;est lui qui déclare une ressource gérée par Terraform (créée, modifiée et détruite selon le state).\nresource \u0026#34;aws_instance\u0026#34; \u0026#34;web\u0026#34; { ami = \u0026#34;ami-0abcd1234\u0026#34; instance_type = \u0026#34;t3.micro\u0026#34; } Les types de ressources disponibles dépendent des providers installés.\naws_instance est le type de ressource, web est le nom local : ensemble ils forment l\u0026rsquo;adresse aws_instance.web, utilisée pour y référer ailleurs dans la configuration ou en CLI (terraform state show aws_instance.web).\nLes méta-arguments s\u0026rsquo;appliquent à n\u0026rsquo;importe quel type de resource, indépendamment du provider.\nIls déterminent comment Terraform crée et gère la ressource, par exemple :\ncount : crée N instances de la ressource, indexées (aws_instance.web[0]). for_each : crée une instance par élément d\u0026rsquo;une map ou d\u0026rsquo;un set, indexées par clé (aws_instance.web[\u0026quot;a\u0026quot;]) — plus robuste que count quand la liste peut changer, car ça évite de décaler les index existants. depends_on : force une dépendance explicite quand Terraform ne peut pas la déduire des références entre arguments. lifecycle : bloc imbriqué pour affiner le comportement (create_before_destroy, prevent_destroy, ignore_changes). Data sources Terraform peut aller chercher des données auprès d\u0026rsquo;un large éventail de data sources : API, autres workspaces Terraform, outputs de fonctions\u0026hellip; Ce fetch dynamique rend la configuration plus flexible, mais ne crée ni ne modifie aucune ressource.\ndata \u0026#34;aws_ami\u0026#34; \u0026#34;web\u0026#34; { most_recent = true owners = [\u0026#34;amazon\u0026#34;] filter { name = \u0026#34;name\u0026#34; values = [\u0026#34;amzn2-ami-hvm-*-x86_64-gp2\u0026#34;] } } resource \u0026#34;aws_instance\u0026#34; \u0026#34;web\u0026#34; { ami = data.aws_ami.web.id instance_type = \u0026#34;t3.micro\u0026#34; } Terraform tente d\u0026rsquo;interroger les data sources dès la phase de plan, mais peut différer la lecture à la phase d\u0026rsquo;apply selon la configuration. Ajouter un argument depends_on à un bloc data force Terraform à différer la lecture jusqu\u0026rsquo;à l\u0026rsquo;apply. On peut ajouter des blocs precondition et postcondition. On peut ajouter les méta-arguments count et for_each aux blocs data pour créer plusieurs instances de la data source. La syntaxe data.\u0026lt;TYPE\u0026gt;.\u0026lt;LABEL\u0026gt;.\u0026lt;ATTRIBUTE\u0026gt; permet de référencer les attributs d\u0026rsquo;une data resource ailleurs dans la configuration. Types Terraform a son propre système de types, utilisé notamment pour contraindre les variables (variable \u0026quot;x\u0026quot; { type = ... }) et pour typer les valeurs manipulées dans la configuration.\nPrimitifs : string, number, bool. Collections (éléments d\u0026rsquo;un seul type) : list(type), set(type), map(type). Structurels (éléments de types différents, structure fixe) : object({ ... }), tuple([...]). variable \u0026#34;instance_type\u0026#34; { type = string default = \u0026#34;t3.micro\u0026#34; } variable \u0026#34;tags\u0026#34; { type = map(string) default = {} } variable \u0026#34;network\u0026#34; { type = object({ cidr_block = string az_count = number }) } Terraform effectue des conversions implicites quand c\u0026rsquo;est possible (une list sans type déclaré est inférée, un number peut être converti en string dans une interpolation), mais déclarer le type explicitement sur les variables reste la meilleure protection contre une valeur mal formée passée en entrée d\u0026rsquo;un module.\nRéférences On peut référencer les ressources dans notre configuration de plusieurs façons.\nRéférences de ressources Une adresse de ressource :\n[chemin de module][spec de ressource]\nChemin de module\nmodule.nom_module[index de module]\nChemin de module complexe\nmodule.foo[0].module.bar[\u0026ldquo;a\u0026rdquo;]\nChemin de spec de ressource\ntype_de_ressource.nom_ressource[index d\u0026rsquo;instance]\nValeurs pas encore connues Quand Terraform planifie des changements pour appliquer la configuration, certaines valeurs ne peuvent pas être renseignées immédiatement car elles sont décidées dynamiquement par le système distant.\nCes valeurs inconnues apparaissent dans la sortie de terraform plan sous la forme (known after apply).\nVariables \u0026amp; outputs Les modules Terraform communiquent entre eux via des inputs et des outputs. Les variables permettent de paramétrer un module pour que d\u0026rsquo;autres utilisateurs puissent fournir des valeurs personnalisées au runtime.\nOutputs Ils exposent des données d\u0026rsquo;un module, permettant d\u0026rsquo;exporter des informations sur l\u0026rsquo;infrastructure. La valeur d\u0026rsquo;un bloc output s\u0026rsquo;apparente à une valeur de retour dans d\u0026rsquo;autres langages de programmation.\noutput \u0026#34;instance_public_ip\u0026#34; { description = \u0026#34;IP publique de l\u0026#39;instance web\u0026#34; value = aws_instance.web.public_ip } Locals Les locals permettent de définir et réutiliser des expressions au sein d\u0026rsquo;un module.\nlocals { name_prefix = \u0026#34;${var.project}-${var.environment}\u0026#34; common_tags = { project = var.project env = var.environment } } resource \u0026#34;aws_instance\u0026#34; \u0026#34;web\u0026#34; { ami = \u0026#34;ami-0abcd1234\u0026#34; instance_type = \u0026#34;t3.micro\u0026#34; tags = local.common_tags } Types complexes Un type complexe regroupe plusieurs valeurs en une seule.\nCollection list(...) : une séquence de valeurs identifiées par des entiers consécutifs à partir de zéro. map(...) : une collection de valeurs où chacune est identifiée par un label de type string. set(...) : une collection de valeurs uniques, sans identifiant secondaire ni ordre. variable \u0026#34;azs\u0026#34; { type = list(string) default = [\u0026#34;eu-west-1a\u0026#34;, \u0026#34;eu-west-1b\u0026#34;] } variable \u0026#34;tags\u0026#34; { type = map(string) default = { env = \u0026#34;prod\u0026#34;, team = \u0026#34;infra\u0026#34; } } variable \u0026#34;allowed_ports\u0026#34; { type = set(number) default = [80, 443] } Types structurels object(...) : un ensemble d\u0026rsquo;attributs nommés ayant chacun leur propre type (ex : object({ name=string, age=number })). tuple(...) : une séquence d\u0026rsquo;éléments identifiés par des entiers consécutifs à partir de zéro, chaque élément ayant son propre type (ex : tuple([string, number, bool])). variable \u0026#34;network\u0026#34; { type = object({ cidr_block = string az_count = number }) default = { cidr_block = \u0026#34;10.0.0.0/16\u0026#34; az_count = 2 } } Any any est rarement le bon type à utiliser. Toujours spécifier le type exact, sauf en cas de données réellement dynamiques. Validation de la configuration terraform validate\nLa validation permet de vérifier que la configuration Terraform fonctionne comme prévu.\nMoyens de validation Terraform propose plusieurs moyens de valider la configuration :\nLes validations de variables d\u0026rsquo;input vérifient les paramètres de la configuration au moment du plan. Les preconditions s\u0026rsquo;assurent qu\u0026rsquo;une resource, data source ou output individuel respecte les exigences avant que Terraform ne tente de la créer. Les postconditions vérifient que Terraform a produit les resources et data sources avec les propriétés attendues. Les checks permettent de valider que l\u0026rsquo;infrastructure se comporte comme prévu sans bloquer les opérations Terraform selon leur résultat. resource \u0026#34;aws_instance\u0026#34; \u0026#34;web\u0026#34; { ami = \u0026#34;ami-0abcd1234\u0026#34; instance_type = \u0026#34;t3.micro\u0026#34; lifecycle { precondition { condition = data.aws_ami.web.architecture == \u0026#34;x86_64\u0026#34; error_message = \u0026#34;L\u0026#39;AMI doit être en x86_64.\u0026#34; } postcondition { condition = self.public_ip != \u0026#34;\u0026#34; error_message = \u0026#34;L\u0026#39;instance doit avoir une IP publique.\u0026#34; } } } check \u0026#34;site_up\u0026#34; { data \u0026#34;http\u0026#34; \u0026#34;homepage\u0026#34; { url = \u0026#34;https://${aws_instance.web.public_ip}\u0026#34; } assert { condition = data.http.homepage.status_code == 200 error_message = \u0026#34;Le site ne répond pas en HTTP 200.\u0026#34; } } Checks Le bloc check sert à valider l\u0026rsquo;infrastructure en dehors du cycle de vie habituel des ressources. Il s\u0026rsquo;exécute en dernière étape d\u0026rsquo;un plan ou d\u0026rsquo;un apply, une fois que Terraform a planifié ou provisionné l\u0026rsquo;infrastructure. Quand l\u0026rsquo;assertion d\u0026rsquo;un bloc check échoue, Terraform émet un warning et poursuit l\u0026rsquo;opération en cours.\nIl permet de :\nValider des resources, data sources, variables ou outputs de la configuration. Valider le comportement de l\u0026rsquo;infrastructure dans son ensemble. Vérifier la configuration de l\u0026rsquo;infrastructure sans bloquer les opérations. Effectuer une validation continue dans HCP Terraform. Workflow de validation Terraform valide les différents aspects de la configuration le plus tôt possible. En général, l\u0026rsquo;ordre d\u0026rsquo;exécution est le suivant :\nflowchart TD A[Validations de variables d'entrée] --\u003e B[Génération du plan] B --\u003e C[Preconditions] C --\u003e D[Création / modification de la resource, data source ou output] D --\u003e E[Postconditions] E --\u003e F[Apply terminé] F --\u003e G[Checks] G -.-\u003e|HCP Terraform| H[Health assessments périodiques]Gérer les données sensibles Terraform peut avoir besoin d\u0026rsquo;accéder à des données sensibles (credentials cloud, tokens API, autres secrets) pour provisionner l\u0026rsquo;infrastructure. Terraform stocke le state dans un fichier en clair, qui inclut toute valeur secrète définie dans la configuration.\nArgument sensitive L\u0026rsquo;argument sensitive sur un bloc variable ou output permet de masquer ces valeurs dans les logs du CLI Terraform et dans l\u0026rsquo;UI HCP Terraform.\nArgument ephemeral Les valeurs ephemeral sont disponibles au runtime d\u0026rsquo;une opération, mais Terraform les omet des fichiers de state et de plan.\nQuand les utiliser ? sensitive ephemeral Où l\u0026rsquo;utiliser Bloc variable ou output Bloc variable, output d\u0026rsquo;un module enfant, bloc ephemeral, argument write-only, provider, provisioner/connection Effet sur le state/plan Reste dans le state, mais masqué dans les logs CLI et l\u0026rsquo;UI HCP Terraform Jamais persisté dans le state ni le plan Effet sur l\u0026rsquo;affichage Valeur remplacée par (sensitive value) dans le plan/apply Valeur disponible uniquement au runtime de l\u0026rsquo;opération Portée d\u0026rsquo;usage Utilisable partout où la variable/output est référencé Restreint aux contextes ephemeral listés ci-dessus Cas d\u0026rsquo;usage typique Mot de passe affiché dans un output Token temporaire passé à un provider ou une resource Arguments write-only Les arguments write-only permettent de passer de façon sécurisée des valeurs temporaires aux managed resources de Terraform pendant une opération, sans les persister dans le state ou les fichiers de plan.\nresource \u0026#34;aws_db_instance\u0026#34; \u0026#34;db\u0026#34; { identifier = \u0026#34;app-db\u0026#34; password_wo = var.db_password password_wo_version = 1 } variable \u0026#34;db_password\u0026#34; { type = string ephemeral = true } Bonnes pratiques de sécurité Si des valeurs sensibles sont stockées dans un fichier de state, il est recommandé de mettre en place des mesures de sécurité supplémentaires :\nStocker le state à distance. Chiffrer le state au repos. Utiliser des contrôles d\u0026rsquo;accès pour limiter qui peut accéder au state. Utiliser des logs d\u0026rsquo;audit pour tracer les accès au state dans le temps. Ce que je retiens count vs for_each : for_each évite le décalage d\u0026rsquo;index quand la liste change, à privilégier dès que les éléments sont identifiables par une clé stable. Preconditions, postconditions et checks ne servent pas la même chose : les deux premières bloquent le plan/apply sur une resource précise, les checks valident l\u0026rsquo;infrastructure globalement sans bloquer. sensitive masque l\u0026rsquo;affichage, ephemeral évite la persistance : seul ephemeral garantit qu\u0026rsquo;une valeur n\u0026rsquo;atterrit jamais dans le state ou le plan. Prochaine étape de la série : les modules.\n","date":"2026-08-17T12:56:18+02:00","image":"/posts/terraform-associate-configuration/module4.png","permalink":"/posts/terraform-associate-configuration/","title":"Terraform Associate #4 — Terraform Configuration"},{"content":"Suite de ma série sur la certification Terraform Associate, après le module sur les providers et le state. Le troisième objectif de l\u0026rsquo;exam guide s\u0026rsquo;intitule \u0026ldquo;Core Terraform workflow\u0026rdquo;.\nLe cycle Write, Plan, Apply Le workflow Terraform \u0026ldquo;de base\u0026rdquo; (un seul développeur, sans HCP Terraform ni CI) tient en trois étapes qui se répètent :\nWrite : j\u0026rsquo;écris ou je modifie la configuration HCL. Plan : Terraform compare la configuration au state et à l\u0026rsquo;infrastructure réelle, puis affiche le diff des actions qu\u0026rsquo;il compte effectuer. Apply : Terraform exécute ce plan et met à jour le state en conséquence. terraform init Première commande à lancer dans un nouveau répertoire de configuration, ou après avoir ajouté un nouveau provider/module :\nTélécharge les providers déclarés dans required_providers et les modules référencés, puis met à jour le fichier de lock (vu dans le module précédent). Configure le backend (local par défaut, ou distant s\u0026rsquo;il est défini). Idempotente : la relancer sur un répertoire déjà initialisé ne casse rien, c\u0026rsquo;est même le réflexe à avoir après un git pull qui touche aux versions de providers. terraform init terraform fmt et terraform validate Deux commandes rapides à passer avant de committer, qui ne touchent ni au state ni à l\u0026rsquo;infrastructure :\nterraform fmt reformate les fichiers .tf selon la convention de style canonique (indentation, alignement des =). terraform validate vérifie que la configuration est syntaxiquement correcte et cohérente en interne (types, arguments requis, références). Elle ne contacte pas le provider et ne sait donc pas si une ressource existe déjà ou si les credentials sont valides. terraform fmt -recursive terraform validate terraform plan C\u0026rsquo;est l\u0026rsquo;étape qui rend Terraform prévisible : avant de toucher à quoi que ce soit, il annonce ce qu\u0026rsquo;il va faire.\nCompare la configuration, le state, et l\u0026rsquo;état réel de l\u0026rsquo;infrastructure via des appels en lecture au provider. Affiche un diff coloré : + création, - destruction, ~ mise à jour, et parfois -/+ (destroy puis recreate, quand un attribut ne peut pas être modifié en place). Peut être sauvegardé dans un fichier avec -out, pour garantir que l\u0026rsquo;apply qui suit exécute exactement ce plan et rien d\u0026rsquo;autre (important en CI, où le code peut changer entre les deux étapes). terraform plan -out=tfplan terraform apply Exécute les actions du plan et persiste le résultat dans le state.\nSans fichier de plan en argument, apply recalcule un plan et demande une confirmation interactive (yes) avant d\u0026rsquo;exécuter. Avec un fichier de plan (terraform apply tfplan), aucune confirmation n\u0026rsquo;est demandée : c\u0026rsquo;est le mode utilisé en CI/CD, après une revue humaine du plan affiché en amont. Terraform ordonne les créations/modifications/destructions selon le graphe de dépendances qu\u0026rsquo;il construit à partir de la configuration, pas selon l\u0026rsquo;ordre d\u0026rsquo;écriture des blocs. terraform apply tfplan terraform destroy Détruit toutes les ressources gérées par le state courant.\nFonctionne comme un apply dont le plan ne contient que des destructions. -target permet de limiter la portée à une ressource précise, mais c\u0026rsquo;est un outil de dépannage ponctuel, pas une pratique à généraliser : il casse la vision globale du plan et peut laisser des ressources dans un état incohérent avec la configuration. terraform destroy Ce qu\u0026rsquo;il faut retenir plan avant apply n\u0026rsquo;est pas optionnel : c\u0026rsquo;est la seule étape qui donne une visibilité sur ce qui va changer avant que ça ne change réellement. -out transforme un plan en contrat : ce qui a été revu est exactement ce qui sera appliqué, ce qui est indispensable dès qu\u0026rsquo;un pipeline CI s\u0026rsquo;intercale entre la revue humaine et l\u0026rsquo;exécution. fmt et validate sont des commandes de formattage et validation syntaxique, l\u0026rsquo;infrastructure sous jacente n\u0026rsquo;est pas analysée. Prochaine étape de la série : la configuration Terraform.\n","date":"2026-08-16T16:53:36+02:00","image":"/posts/terraform-associate-core-workflow/module3.png","permalink":"/posts/terraform-associate-core-workflow/","title":"Terraform Associate #3 — Core Terraform Workflow"},{"content":"Suite de ma série sur la certification Terraform Associate, après le premier module sur l\u0026rsquo;Infrastructure as Code. Le deuxième module de l\u0026rsquo;exam guide s\u0026rsquo;intitule \u0026ldquo;Terraform Fundamentals\u0026rdquo; : il s\u0026rsquo;agit de comprendre ce que sont les providers et comment le state fonctionne.\nLes providers Terraform ne parle nativement à aucun cloud. Tout se fait via des providers : des plugins qui traduisent le langage déclaratif HCL en appels API vers AWS, GCP, Azure, mais aussi Kubernetes, Cloudflare, Datadog, GitHub\u0026hellip; Le cœur de Terraform (le CLI, le state, le langage) reste identique quel que soit le provider utilisé.\nChaque provider ajoute un ensemble de types de ressources et/ou de data sources que Terraform peut gérer. Chaque type de ressource est implémenté par un provider ; sans providers, Terraform ne peut gérer aucun type d\u0026rsquo;infrastructure. Terraform exige un nom local unique pour chaque provider au sein d\u0026rsquo;un module. N\u0026rsquo;importe qui peut développer et distribuer ses propres providers Terraform Quand un provider est défini dans le module racine, Terraform transmet implicitement cette configuration à tous les modules enfants, afin que tous les modules utilisent la même configuration. Ne pas confondre required_providerset provider:\nrequired_providers\nSert uniquement à terraform init : source (adresse dans le registry) + contrainte de version, pour savoir quel plugin télécharger. Pas de credentials, pas de région, aucune logique d\u0026rsquo;exécution. Un seul bloc par module. provider\nConfigure une instance réelle du provider : région, credentials, endpoints custom, etc. Peut apparaître plusieurs fois pour le même provider via alias (ex. déployer sur plusieurs régions AWS dans le même module). Sans lui, init fonctionne mais plan/apply échouera (ou retombera sur des credentials par défaut via variables d\u0026rsquo;env/CLI). Dependency lock Le fichier lock ne suit que les dépendances des providers. Terraform crée ou met à jour automatiquement ce fichier à chaque exécution de terraform init. Ce fichier appartient à la configuration dans son ensemble, et non à chaque module pris séparément. Comportement d\u0026rsquo;installation des dépendances Quand terraform init installe les providers nécessaires à une configuration, Terraform prend en compte à la fois les contraintes de version de la configuration et celles déjà enregistrées dans le fichier de lock. Si un provider n\u0026rsquo;a pas encore de version enregistrée, Terraform choisit la version la plus récente qui respecte la contrainte, puis met à jour le fichier de lock avec ce choix. On peut forcer ce comportement avec l\u0026rsquo;option -upgrade sur terraform init. Le state : la mémoire de Terraform Le fichier terraform.tfstate est ce qui permet à Terraform d\u0026rsquo;être déclaratif : c\u0026rsquo;est lui qui fait le lien entre la configuration HCL (ce que je veux) et les ressources réellement créées chez le fournisseur (ce qui existe). Sans state, Terraform ne pourrait pas savoir qu\u0026rsquo;un aws_instance.web dans le code correspond à l\u0026rsquo;instance i-0abc123 déjà en place.\nSes rôles concrets :\nMapping vers le monde réel. Le state associe chaque ressource déclarée à un identifiant réel côté fournisseur, indispensable pour savoir quoi modifier ou détruire. Metadata. Il stocke aussi des informations que l\u0026rsquo;API du fournisseur n\u0026rsquo;expose pas toujours (dépendances entre ressources, par exemple), utiles pour calculer un plan correct. Performance. Pour de grosses infrastructures, relire l\u0026rsquo;état de centaines de ressources via l\u0026rsquo;API à chaque commande serait lent. Le state fait office de cache. Collaboration. En le stockant à distance (S3, GCS, Terraform Cloud\u0026hellip;) avec un mécanisme de lock, plusieurs personnes peuvent exécuter des apply sans risquer d\u0026rsquo;écraser le travail des autres. C\u0026rsquo;est un point que j\u0026rsquo;avais déjà creusé en pratique dans mes articles sur the-forge-infra. State distant (remote state) Terraform écrit les données de state dans un espace de stockage distant, qui peut ensuite être partagé entre tous les membres d\u0026rsquo;une équipe. Terraform prend en charge le stockage du state dans HCP Terraform, HashiCorp Consul, Amazon S3, Azure Blob Storage, Google Cloud Storage, Alibaba Cloud OSS, et d\u0026rsquo;autres. Le state distant est implémenté par un backend, ou par HCP Terraform, tous deux configurables dans le module racine de la configuration. La data source terraform_remote_state utilise le dernier snapshot de state d\u0026rsquo;un backend donné pour récupérer les valeurs de sortie (outputs) du module racine d\u0026rsquo;une autre configuration Terraform. Pour les backends distants les plus complets, Terraform peut aussi verrouiller le state afin d\u0026rsquo;empêcher des exécutions concurrentes sur le même state. Ce que je retiens required_providers vs provider : le premier dit à terraform init quoi télécharger (source, version), le second configure une instance réelle (région, credentials) et peut se dupliquer via des alias. Le state n\u0026rsquo;est pas un détail d\u0026rsquo;implémentation : c\u0026rsquo;est lui qui fait le lien entre le code HCL et les ressources réelles, sert de cache de métadonnées pour construire les plans, et permet le travail en équipe une fois stocké à distance avec un lock. Le fichier de lock ne concerne que les providers : il garantit des versions reproductibles à init, indépendamment des modules. Prochaine étape de la série : le workflow Terraform.\n","date":"2026-08-16T13:48:17+02:00","image":"/posts/terraform-associate-fundamentals/module2.png","permalink":"/posts/terraform-associate-fundamentals/","title":"Terraform Associate #2 — Terraform fundamentals"},{"content":"Comme annoncé précédemment, je prépare la certification Terraform Associate. Premier objectif de l\u0026rsquo;exam guide : \u0026ldquo;Understand Infrastructure as Code (IaC) concepts\u0026rdquo;. Avant de parler syntaxe HCL ou providers, il s\u0026rsquo;agit de poser les bases : qu\u0026rsquo;est-ce que l\u0026rsquo;IaC, et pourquoi ça a remplacé la gestion manuelle d\u0026rsquo;infrastructure.\nQu\u0026rsquo;est-ce que l\u0026rsquo;Infrastructure as Code L\u0026rsquo;Infrastructure as Code, c\u0026rsquo;est décrire son infrastructure (serveurs, réseau, bases de données, permissions IAM\u0026hellip;) sous forme de fichiers texte versionnables, plutôt que de la construire à la main via une console web ou des commandes ad hoc.\nCes fichiers deviennent la source de vérité : un outil (Terraform, mais aussi Pulumi, CloudFormation, Ansible\u0026hellip;) les lit et se charge de créer, modifier ou supprimer les ressources correspondantes chez le fournisseur cloud.\nDeux façons d\u0026rsquo;écrire cette description :\nImpératif : on décrit la suite d\u0026rsquo;étapes pour arriver au résultat (\u0026ldquo;crée un VPC, puis crée un subnet dedans, puis attache une route table\u0026hellip;\u0026rdquo;). C\u0026rsquo;est l\u0026rsquo;approche d\u0026rsquo;un script bash ou d\u0026rsquo;un playbook Ansible classique. Déclaratif : on décrit l\u0026rsquo;état final voulu (\u0026ldquo;je veux un VPC avec ce CIDR et ces subnets\u0026rdquo;), et l\u0026rsquo;outil calcule lui-même les actions nécessaires pour y parvenir. C\u0026rsquo;est l\u0026rsquo;approche de Terraform. L\u0026rsquo;approche déclarative est centrale pour comprendre Terraform : on ne lui dit jamais \u0026ldquo;crée ceci\u0026rdquo;, on lui dit \u0026ldquo;voici l\u0026rsquo;état que je veux\u0026rdquo;, et c\u0026rsquo;est le moteur (via le state et le plan) qui détermine le diff à appliquer.\nIdempotence Une propriété qui découle directement du déclaratif : réappliquer la même configuration plusieurs fois de suite ne doit rien changer si l\u0026rsquo;infrastructure correspond déjà à l\u0026rsquo;état décrit. terraform apply sur une configuration inchangée doit annoncer \u0026ldquo;No changes.\u0026rdquo; C\u0026rsquo;est ce qui rend l\u0026rsquo;IaC fiable en CI/CD : on peut la rejouer sans craindre de dupliquer des ressources ou de casser l\u0026rsquo;existant.\nPourquoi l\u0026rsquo;IaC plutôt que le clic dans une console C\u0026rsquo;est la deuxième partie de l\u0026rsquo;objectif de l\u0026rsquo;exam guide : \u0026ldquo;Describe advantages of IaC patterns\u0026rdquo;. Ce qui m\u0026rsquo;a personnellement convaincu, avant même de penser à une certification :\nVersioning et historique. Chaque changement d\u0026rsquo;infra passe par un commit Git, avec un auteur, une date et (idéalement) une revue de code. Impossible d\u0026rsquo;obtenir ça avec des clics dans une console. Collaboration. Plusieurs personnes peuvent travailler sur la même infrastructure via des pull requests, sans se marcher dessus ni se demander \u0026ldquo;qui a changé ce security group hier soir\u0026rdquo;. Reproductibilité. Recréer un environnement identique (staging, nouvelle région, nouveau client) devient une question de minutes, pas de jours de configuration manuelle. Documentation vivante. Le code Terraform décrit l\u0026rsquo;infrastructure telle qu\u0026rsquo;elle est réellement, contrairement à un wiki qui devient obsolète dès qu\u0026rsquo;on oublie de le mettre à jour. Réduction de l\u0026rsquo;erreur humaine. Un plan Terraform montre le diff avant application ; un clic malheureux dans une console, lui, ne prévient pas. Ce que je retiens Ce premier module reste conceptuel, mais il pose un vocabulaire que je retrouverai tout au long de la certification : déclaratif, idempotence, state. Le module suivant s\u0026rsquo;attaque aux providers et au state.\n","date":"2026-08-16T13:44:02+02:00","image":"/posts/terraform-associate-infrastructure-as-code/iac.png","permalink":"/posts/terraform-associate-infrastructure-as-code/","title":"Terraform Associate #1 — Infrastructure as Code"},{"content":"Terraform, je l\u0026rsquo;utilise quasiment au quotidien : que ce soit sur TheForge ou en entreprise, c\u0026rsquo;est l\u0026rsquo;outil que je manipule le plus après Kubernetes. Mais jusqu\u0026rsquo;ici, mes connaissances se sont construites au fil des besoins, projet après projet, sans jamais couvrir l\u0026rsquo;ensemble des concepts de façon structurée.\nJe me lance donc dans la préparation de la certification HashiCorp Certified: Terraform Associate. L\u0026rsquo;objectif n\u0026rsquo;est pas tant le badge en lui-même que de combler les angles morts : provider versioning, state management avancé, modules, workspaces, sont autant de sujets que j\u0026rsquo;utilise sans toujours en maîtriser toutes les subtilités.\nPourquoi cette certification Trois raisons principales m\u0026rsquo;ont poussé à me lancer :\nStructurer des connaissances acquises en pratique. Apprendre \u0026ldquo;sur le tas\u0026rdquo; laisse forcément des trous : on maîtrise ce dont on a eu besoin, pas nécessairement ce qui est important. Valider mes acquis avec un référentiel externe. Un examen impose un niveau d\u0026rsquo;exigence que l\u0026rsquo;auto-formation seule n\u0026rsquo;impose pas toujours. Une certification reconnue dans l\u0026rsquo;écosystème DevOps/Cloud, utile aussi bien pour mes projets personnels que pour mon activité professionnelle. Comment je vais m\u0026rsquo;organiser Mon plan de préparation :\nReprendre la documentation officielle Terraform et l\u0026rsquo;exam guide point par point. Manipuler chaque concept dans un contexte concret, en m\u0026rsquo;appuyant sur mes projets existants (notamment the-forge-infra) plutôt que sur des exemples jetables. M\u0026rsquo;entraîner sur des questions type examen pour identifier mes points faibles avant de passer le test. La suite Je partagerai mon parcours d\u0026rsquo;apprentissage dans de prochains articles : les notions qui m\u0026rsquo;ont surpris, les pièges classiques de l\u0026rsquo;examen, et ce que je retiens une fois la certification passée. Rendez-vous dans les prochains articles pour la suite de cette préparation.\n","date":"2026-08-15T18:25:02+02:00","image":"/posts/certification-terraform-associate/projet_certif.png","permalink":"/posts/certification-terraform-associate/","title":"Objectif certification Terraform Associate"},{"content":"Contexte En configurant un PodDisruptionBudget sur le chart Helm d\u0026rsquo;Apache Superset, helm template plantait dès que je passais podDisruptionBudget.enabled: true. Pas seulement la ressource PDB : tout le rendu du chart s\u0026rsquo;arrêtait.\nCe que j\u0026rsquo;ai appris Le chart livrait à la fois minAvailable: 1 et maxUnavailable: 1 par défaut, alors que ses propres templates fail le rendu dès que les deux sont renseignés en même temps.\nDétails Reproduction sur le chart publié :\nhelm repo add superset https://apache.github.io/superset helm repo update cat \u0026gt; pdb-repro.yaml \u0026lt;\u0026lt;\u0026#39;YAML\u0026#39; supersetNode: podDisruptionBudget: enabled: true YAML helm template superset superset/superset --version 0.22.4 -f pdb-repro.yaml # Error: execution error at (superset/templates/pdb.yaml:22:4): # Only one of minAvailable or maxUnavailable should be set Le garde-fou incriminé, dans templates/pdb.yaml :\n{{- if and .minAvailable .maxUnavailable }} {{- fail \u0026#34;Only one of minAvailable or maxUnavailable should be set\u0026#34; }} {{- end}} \u0026hellip; alors que values.yaml livrait les deux champs à 1 pour les six blocs concernés (supersetNode, supersetWorker, supersetCeleryBeat, supersetCeleryFlower, supersetWebsockets, supersetMcp).\nJ\u0026rsquo;ai ouvert une issue documentant le problème et la reproduction, puis une pull request qui met maxUnavailable à null par défaut dans les six blocs (le chart rend alors un PDB avec son propre minAvailable: 1, sans y toucher pour qui utilise déjà maxUnavailable explicitement), garde le garde-fou pour le cas où les deux sont vraiment renseignés, améliore son message d\u0026rsquo;erreur pour qu\u0026rsquo;il nomme le champ et le composant concernés, et ajoute une suite de tests helm-unittest sur les six templates PDB.\nPourquoi c\u0026rsquo;est utile Au-delà du fix lui-même, ça m\u0026rsquo;a fait passer par tout le cycle d\u0026rsquo;une contribution à un projet Apache : isoler un bug reproductible indépendamment de mon propre usage, l\u0026rsquo;écrire pour des mainteneurs qui ne connaissent pas mon contexte, proposer un correctif non cassant plutôt que juste signaler le problème, et l\u0026rsquo;accompagner de tests qui prouvent la régression.\nSources Issue apache/superset#42997 PR apache/superset#42995 ","date":"2026-08-15T11:39:26+02:00","image":"/posts/premiere-pr-open-source/superset.png","permalink":"/posts/premiere-pr-open-source/","title":"Ma première contribution open source"},{"content":"Introduction ingress-nginx est en fin de vie : le projet est en mode maintenance et son remplacement officiel est la Gateway API. NGINX propose sa propre implémentation, NGINX Gateway Fabric (NGF), qui remplace ingress-nginx sur le même principe (reverse proxy L7 devant vos services) mais avec un modèle de ressources différent (Gateway, HTTPRoute, GRPCRoute\u0026hellip;).\nCe guide couvre la migration d\u0026rsquo;un cluster qui tourne déjà en production avec ingress-nginx, pas une installation neuve. L\u0026rsquo;objectif : passer d\u0026rsquo;Ingress à HTTPRoute sans coupure de service, avec une porte de sortie à chaque étape.\nÀ la fin, vous saurez :\nmapper vos ressources Ingress (et leurs annotations) vers Gateway/HTTPRoute, faire coexister les deux contrôleurs le temps de la bascule, migrer route par route avec un rollback immédiat en cas de souci, éviter les pièges les plus courants (namespaces, TLS, load balancer, cert-manager). Prérequis Un cluster Kubernetes avec ingress-nginx déjà en place. Accès cluster-admin (installation de CRDs). kubectl et idéalement helm. cert-manager si vous gérez du TLS automatique Connaissance basique des ressources Ingress de votre cluster (kubectl get ingress -A). Étapes 1. Faire l\u0026rsquo;inventaire avant de toucher à quoi que ce soit Listez tout ce qui dépend d\u0026rsquo;ingress-nginx avant de migrer, sinon vous découvrirez les cas particuliers en prod.\nkubectl get ingress -A -o wide kubectl get ingress -A -o json | jq -r \u0026#39;.items[] | .metadata.namespace + \u0026#34;/\u0026#34; + .metadata.name + \u0026#34;: \u0026#34; + (.metadata.annotations | keys | join(\u0026#34;,\u0026#34;))\u0026#39; Notez en particulier :\nles annotations nginx.ingress.kubernetes.io/* utilisées (rewrite, redirect, whitelist, rate-limit\u0026hellip;), les IngressClass en jeu si vous en avez plusieurs, les certificats TLS gérés par cert-manager (kubectl get certificate -A), l\u0026rsquo;IP du LoadBalancer actuel de ingress-nginx (kubectl get svc -n ingress-nginx) : elle sera différente pour NGF, donc DNS à revoir. 2. Installer la Gateway API et NGINX Gateway Fabric en parallèle Pas de big bang : NGF s\u0026rsquo;installe à côté d\u0026rsquo;ingress-nginx, sans y toucher. Les deux peuvent tourner en même temps sur le même cluster.\n# CRDs de la Gateway API (standard channel) kubectl apply -f kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.1/standard-install.yaml Puis NGF via Helm :\nhelm install nginx-gateway-fabric oci://ghcr.io/nginx/charts/nginx-gateway-fabric \\ --create-namespace -n nginx-gateway Vérifiez qu\u0026rsquo;il tourne et que son Service reçoit bien une IP externe distincte de celle d\u0026rsquo;ingress-nginx :\nkubectl get pods -n nginx-gateway kubectl get svc -n nginx-gateway 3. Créer la ressource Gateway ingress-nginx n\u0026rsquo;a pas d\u0026rsquo;équivalent explicite pour l\u0026rsquo;entrypoint: tout était implicite dans l\u0026rsquo;IngressClass. Avec la Gateway API, il devient une ressource à part entière : la Gateway. C\u0026rsquo;est elle qui porte les listeners (ports, TLS) ; les HTTPRoute viennent s\u0026rsquo;y attacher.\napiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: main-gateway namespace: nginx-gateway spec: gatewayClassName: nginx listeners: - name: http port: 80 protocol: HTTP - name: https port: 443 protocol: HTTPS hostname: \u0026#34;*.mondomaine.com\u0026#34; tls: mode: Terminate certificateRefs: - name: wildcard-cert allowedRoutes: namespaces: from: All allowedRoutes.namespaces.from: All évite de recréer un ReferenceGrant pour chaque namespace applicatif pendant la migration. Vous pourrez restreindre après coup si besoin.\n4. Convertir vos Ingress en HTTPRoute, un par un C\u0026rsquo;est le cœur de la migration. Voici les correspondances les plus fréquentes.\nUn Ingress simple :\napiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-app annotations: kubernetes.io/ingress.class: nginx spec: rules: - host: app.mondomaine.com http: paths: - path: / pathType: Prefix backend: service: name: my-service port: number: 80 devient :\napiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: my-app namespace: default spec: parentRefs: - name: main-gateway namespace: nginx-gateway hostnames: - \u0026#34;app.mondomaine.com\u0026#34; rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: my-service port: 80 Les cas génériques (path, host, rewrite, redirect HTTP→HTTPS) sont couverts nativement par la Gateway API. Les cas avancés (rate limiting, whitelist IP, body size) passent par les policies spécifiques à NGF (ClientSettingsPolicy), pas par un standard commun : c\u0026rsquo;est le principal piège de la migration, prévoyez du temps pour ces routes.\n5. Basculer le DNS route par route, pas tout d\u0026rsquo;un coup Comme les deux contrôleurs tournent en parallèle avec deux IP différentes, vous pouvez migrer service par service :\nCréez la HTTPRoute équivalente pour un service (sans supprimer l\u0026rsquo;Ingress). Testez en direct sur l\u0026rsquo;IP de NGF, sans passer par le DNS : curl -H \u0026#34;Host: app.mondomaine.com\u0026#34; https://\u0026lt;IP_NGF\u0026gt;/ -k Une fois validé, changez l\u0026rsquo;enregistrement DNS pour pointer vers l\u0026rsquo;IP de NGF. Laissez tourner l\u0026rsquo;Ingress équivalent quelques jours avant de le supprimer : c\u0026rsquo;est votre rollback immédiat si un problème apparaît (retour DNS en arrière, zéro redéploiement). 6. Nettoyer une fois la migration validée Quand toutes les routes sont basculées et stables :\nkubectl delete ingress --all -A --selector=\u0026lt;vos anciens labels\u0026gt; helm uninstall ingress-nginx -n ingress-nginx kubectl delete ingressclass nginx Ne faites cette étape qu\u0026rsquo;après avoir confirmé qu\u0026rsquo;aucune Ingress résiduelle n\u0026rsquo;est encore active (kubectl get ingress -A doit être vide).\nVérification kubectl get gateway -A kubectl get httproute -A kubectl get pods -n nginx-gateway Chaque HTTPRoute doit afficher une condition Accepted: True et ResolvedRefs: True :\nkubectl describe httproute my-app -n default Testez chaque hostname migré en HTTP et en HTTPS, avec certificat valide, avant de désactiver l\u0026rsquo;ancien Ingress correspondant.\nProchaines étapes Restreindre allowedRoutes sur la Gateway une fois la migration terminée (au lieu de From: All). Explorer les GRPCRoute si vous exposez du gRPC. Mettre en place des ReferenceGrant ciblés plutôt qu\u0026rsquo;un accès large entre namespaces. Si vous gérez plusieurs équipes, envisager plusieurs Gateway (une par domaine/équipe) plutôt qu\u0026rsquo;une seule partagée. Ressources utiles Documentation officielle Gateway API NGINX Gateway Fabric Guide de migration ingress-nginx → Gateway API (Kubernetes blog) ingress-nginx : statut du projet et fin de vie ","date":"2026-08-15T09:51:08+02:00","image":"/posts/migration-ingress-gatewayapi/ingress-gateway-api-migration.png","permalink":"/posts/migration-ingress-gatewayapi/","title":"Migrer de NGINX Ingress vers la Gateway API avec NGINX Gateway Fabric"},{"content":"Contexte Je voulais un certificat Let\u0026rsquo;s Encrypt \u0026ldquo;vrai\u0026rdquo; (pas auto-signé) pour une URL interne, jamais exposée sur Internet, servie par un ingress Kubernetes piloté par cert-manager. Avec l\u0026rsquo;Issuer/ClusterIssuer ACME par défaut (challenge HTTP01), la validation échoue systématiquement : Let\u0026rsquo;s Encrypt essaie de joindre http://\u0026lt;mon-url-interne\u0026gt;/.well-known/acme-challenge/... et ne peut évidemment pas l\u0026rsquo;atteindre.\nCe que j\u0026rsquo;ai appris Le challenge HTTP01 nécessite que l\u0026rsquo;URL soit accessible publiquement ; le challenge DNS01 non : il vérifie uniquement qu\u0026rsquo;un enregistrement TXT _acme-challenge.\u0026lt;domaine\u0026gt; existe dans la zone DNS publique du domaine. Comme mon domaine est géré sur Cloudflare, cert-manager peut créer/supprimer ce TXT automatiquement via l\u0026rsquo;API Cloudflare, sans que l\u0026rsquo;hôte lui-même ne soit joignable depuis l\u0026rsquo;extérieur.\nDétails Un token API Cloudflare scopé (pas la Global API Key), avec la permission Zone:DNS:Edit sur la zone concernée, stocké en Secret :\nkubectl create secret generic cloudflare-api-token \\ --namespace cert-manager \\ --from-literal=api-token=\u0026lt;TOKEN_SCOPE_ZONE_DNS_EDIT\u0026gt; Un ClusterIssuer qui utilise ce secret pour le challenge DNS01 :\napiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-dns01-cloudflare spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: moi@example.com privateKeySecretRef: name: letsencrypt-dns01-cloudflare solvers: - dns01: cloudflare: apiTokenSecretRef: name: cloudflare-api-token key: api-token selector: dnsZones: - \u0026#34;example.com\u0026#34; Puis un Certificate (ou une annotation cert-manager.io/cluster-issuer sur l\u0026rsquo;Ingress) pour le sous-domaine interne :\napiVersion: cert-manager.io/v1 kind: Certificate metadata: name: interne-example-com namespace: mon-app spec: secretName: interne-example-com-tls issuerRef: name: letsencrypt-dns01-cloudflare kind: ClusterIssuer dnsNames: - interne.example.com Point important : interne.example.com n\u0026rsquo;a besoin d\u0026rsquo;aucun enregistrement A/CNAME public : seule la zone example.com doit être déléguée à Cloudflare, pour que le TXT temporaire _acme-challenge.interne.example.com y soit visible le temps de la validation. La résolution réelle du sous-domaine reste purement interne (DNS privé, /etc/hosts, split-horizon\u0026hellip;).\nPourquoi c\u0026rsquo;est utile Ça permet d\u0026rsquo;avoir un certificat reconnu par tous les navigateurs pour des services internes (dashboards, VPN-only, ingress privés) sans certificat auto-signé à distribuer manuellement, et sans jamais exposer le service sur Internet : seule la zone DNS publique du domaine est sollicitée, pas le service lui-même.\nSources cert-manager - ACME DNS01 Challenge Provider: Cloudflare Cloudflare - Create an API token ","date":"2026-08-09T19:00:45+02:00","image":"/posts/dns01/dns01.png","permalink":"/posts/dns01/","title":"cert-manager et le challenge DNS01 Cloudflare"},{"content":"Contexte En mettant en place FluxCD sur un cluster, je me suis retrouvé avec deux façons de déclarer ce que je veux déployer : Kustomization et HelmRelease. Les deux apparaissent dans quasiment tous les repos GitOps Flux, mais elles ne font pas la même chose.\nCe que j\u0026rsquo;ai appris Kustomization (la CR Flux, pas l\u0026rsquo;objet Kustomize natif) applique des manifests YAML bruts depuis un GitRepository, en passant éventuellement par des overlays Kustomize. HelmRelease déploie un chart Helm depuis une HelmRepository/OCIRepository, avec ses values et son cycle de vie (install/upgrade/rollback) géré par le Helm controller. Une bonne partie des repos Flux utilisent en réalité les deux : une Kustomization qui, elle-même, applique des fichiers HelmRelease.yaml.\nDétails Deux ressources Flux distinctes, avec des controllers différents (kustomize-controller vs helm-controller) :\n# Kustomization : applique des manifests bruts (ou un overlay Kustomize) apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: apps-infra namespace: flux-system spec: interval: 10m path: ./clusters/prod/infra prune: true sourceRef: kind: GitRepository name: flux-system --- # HelmRelease : déploie un chart Helm avec ses values apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: ingress-nginx namespace: ingress-nginx spec: interval: 10m chart: spec: chart: ingress-nginx version: \u0026#34;4.11.x\u0026#34; sourceRef: kind: HelmRepository name: ingress-nginx values: controller: replicaCount: 2 Organisation qui fonctionne bien en pratique :\nclusters/\u0026lt;env\u0026gt;/ : une ou deux Kustomization racines par cluster, qui pointent vers apps/ et infra/. infra/\u0026lt;outil\u0026gt;/ : un dossier par outil tiers packagé en chart (ingress-nginx, cert-manager, prometheus\u0026hellip;) contenant sa HelmRelease.yaml + une HelmRepository/OCIRepository associée. apps/\u0026lt;app\u0026gt;/base + apps/\u0026lt;app\u0026gt;/overlays/\u0026lt;env\u0026gt; : manifests maison, gérés en Kustomize natif, appliqués via une Kustomization Flux par environnement. Règle simple : si ça vient d\u0026rsquo;un chart Helm publié par un tiers, HelmRelease ; si c\u0026rsquo;est du YAML qu\u0026rsquo;on écrit et qu\u0026rsquo;on veut décliner par environnement, Kustomization + overlays.\nQue réconcilier après une modification ? J\u0026rsquo;ai mis à jour la chart (nouvelle version dans HelmRelease.spec.chart.spec, ou nouvelle version poussée dans le repo Helm/OCI) → Flux ne voit la nouvelle version qu\u0026rsquo;au prochain polling de la HelmRepository/OCIRepository. Il faut réconcilier la source, puis le HelmChart généré, puis la HelmRelease — le flag --with-source fait les trois d\u0026rsquo;un coup :\nflux reconcile helmrelease ingress-nginx -n ingress-nginx --with-source J\u0026rsquo;ai mis à jour les values de la HelmRelease → ces values vivent dans le manifest Git de la HelmRelease, appliqué par une Kustomization Flux. Pas de nouvelle version de chart à récupérer, juste le commit à relire : réconcilier la Kustomization suffit, le helm-controller détecte le changement de génération sur la HelmRelease et relance l\u0026rsquo;upgrade tout seul :\nflux reconcile kustomization apps-infra --with-source Inutile d\u0026rsquo;appeler flux reconcile helmrelease dans ce second cas : dès que le nouveau commit est appliqué, le bump de génération suffit à déclencher le reconcile.\nUne Kustomization par app ou par environnement ? Plutôt une Kustomization par app (ou par composant infra) qu\u0026rsquo;une seule grosse par environnement. Avec une Kustomization par unité, chaque app a son propre health check, son propre prune et peut être reconciliée ou rollback indépendamment ; avec une seule Kustomization pour tout un env, un healthcheck qui échoue ou un prune: true mal maîtrisé impacte tout le lot d\u0026rsquo;un coup, et Flux ne peut pas ordonner finement les dépendances entre apps.\nEn pratique : une Kustomization par composant infra (cert-manager, ingress-nginx\u0026hellip;) et une par app métier, reliées par dependsOn quand l\u0026rsquo;ordre compte (les apps dépendent souvent de l\u0026rsquo;infra) :\napiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: apps-checkout namespace: flux-system spec: interval: 10m path: ./apps/checkout/overlays/prod prune: true dependsOn: - name: infra-ingress-nginx sourceRef: kind: GitRepository name: flux-system Ne grouper que ce qui est vraiment couplé et doit vivre/mourir ensemble, pas juste \u0026ldquo;tout ce qui tourne dans cet environnement\u0026rdquo;.\nPourquoi c\u0026rsquo;est utile Ça évite deux pièges : réécrire en YAML brut des charts tiers complexes (perdre les hooks, les valeurs par défaut du chart, les mises à jour de version), et à l\u0026rsquo;inverse forcer des manifests maison dans un chart Helm juste pour \u0026ldquo;faire comme Flux\u0026rdquo;. Séparer clairement infra/ (HelmRelease) et apps/ (Kustomization + overlays) rend le repo GitOps beaucoup plus lisible.\nSources Flux - Kustomization CRD Flux - HelmRelease CRD Flux - Guide \u0026ldquo;Manage Helm Releases\u0026rdquo; ","date":"2026-08-05T19:02:44+02:00","image":"/posts/helmrelease-vs-kustomization/helmrelease-vs-kustomization.png","permalink":"/posts/helmrelease-vs-kustomization/","title":"HelmRelease vs Kustomization dans FluxCD"},{"content":"Introduction Les Large Language Models (LLM) ont révolutionné le domaine de l\u0026rsquo;intelligence artificielle, il existe désormais tout un tas de modèles opensource pouvant être déployés localement. C\u0026rsquo;est ce que nous allons voir dans cet article, nous allons déployer un LLM dans un cluster Kubernetes local en utilisant Minikube, ce qui permet de tester différents modèles sans coûts cloud et faire un premier pas dans l\u0026rsquo;IA.\nPré-requis Avant de commencer, assurez-vous d\u0026rsquo;avoir les éléments suivants installés :\nMinikube kubectl Docker Helm 16 Go de RAM minimum 20 Go d\u0026rsquo;espace disque disponible minikube version kubectl version --client docker --version Étape 1 : Configuration de Minikube pour les LLM Les LLM nécessitent des ressources importantes. Nous devons configurer Minikube avec suffisamment de mémoire et de CPU.\nDémarrage de Minikube avec des ressources adaptées # Démarrer Minikube avec 16 Go de RAM et 8 CPU minikube start --memory=8192 --cpus=4 --disk-size=20g # Vérifier le statut minikube status # Vérifier les ressources allouées kubectl get nodes kubectl describe node minikube | grep -A 3 \u0026#34;Allocatable\u0026#34; Étape 2 : Choix du modèle et de l\u0026rsquo;image container Plusieurs options s\u0026rsquo;offrent à vous pour déployer un LLM :\nOption 1 : vLLM vLLM est un framework optimisé pour le déploiement et le serving de LLM, développé par les créateurs de Ray. Il offre :\nPagedAttention : Optimisation mémoire pour gérer des séquences longues Support multi-GPU Compatibilité avec HuggingFace API OpenAI compatible Option 2 : Ollama Ollama est une solution légère et facile à utiliser pour exécuter des LLM localement. C\u0026rsquo;est l\u0026rsquo;option la plus simple pour commencer avec Minikube.\nPour cet article, nous utiliserons Ollama pour sa simplicité et son efficacité.\nÉtape 3 : Déploiement d\u0026rsquo;Ollama dans Minikube Les ressources nécessaires namespace : pour y déployer toutes les ressources liées à ce projet pvc : pour le stockage du modèle deployment: pour servir l\u0026rsquo;application service: pour pouvoir exposer l\u0026rsquo;application Helm chart \u0026amp; installation Pour faciliter le déploiement j\u0026rsquo;ai écris un chart Helm qui configure toutes ces ressources. Le repository se trouve ici.\nClonez le repository puis faites :\nhelm install ollama . #Vérifier que les pods sont Running kubectl get pods -n ollama Les pods peuvent mettre un certain temps à démarrer car il faut télécharger le modèle ~1.3Go, j\u0026rsquo;utilise pour cela un initContainer qui va le télécharger et le stocker dans un volume que le pod pourra ensuite récupérer.\nÉtape 4 : Utilisation en ligne de commande La chart déploie l\u0026rsquo;application et l\u0026rsquo;expose sur le port 11434.\nPour appeler le modèle :\nkubectl -n ollama port-forward svc/ollama 11434:11434 #puis curl http://localhost:11434/api/generate -d \u0026#39;{ \u0026#34;model\u0026#34;: \u0026#34;llama3.2.1b\u0026#34;, \u0026#34;prompt\u0026#34;: \u0026#34;Explique Kubernetes en une phrase.\u0026#34;, \u0026#34;stream\u0026#34;: false }\u0026#39; Vous devriez avoir un résultat de ce type : Étape 5 : Utilisation en web UI J\u0026rsquo;ai volontairement omis de mentionner que la chart déploie 2 ressources supplémentaires : un déploiement et un service \u0026ldquo;open-webui\u0026rdquo; pour exposer l\u0026rsquo;API d\u0026rsquo;Ollama à travers une interface web. Pour l\u0026rsquo;utiliser, rien de plus simple :\nkubectl -n ollama port-forward svc/open-webui 8080:8080 Ouvrez votre navigateur.\nVous devriez voir une interface de ce type :\nCliquez sur démarrer, après un process de login vous devriez obtenir une page sensiblement ressemblante à celle de ChatGPT :\nEnfin, écrivez votre prompt et constatez le résultat :\nConclusion Déployer un LLM dans Kubernetes avec Minikube se fait sans trop de difficultés, un modèle est finalement exposé via une API comme le serait un webservice classique. Il faut garder en tête que les modèles sont lourds et nécessitent beaucoup de ressources pour les faire tourner.\nRessources supplémentaires Documentation Ollama vLLM Documentation Minikube Documentation ","date":"2026-08-04T19:00:00+02:00","image":"/posts/ollama-minikube/ollama.png","permalink":"/posts/ollama-minikube/","title":"Déploiement d'un LLM dans Kubernetes avec Minikube"},{"content":"Suite à l\u0026rsquo;achat de mon Mac dédié à mes projets pros je vous propose un liste d\u0026rsquo;outils à installer pour démarrer rapidement vos activités DevSecOps / Cloud.\nLes classiques IDE : VsCode, GoLand, PyCharm Docker : évidemment Agents IA : Claude, Copilot, Codex, Mistral, choisissez celui avec lequel vous êtes à l\u0026rsquo;aise Utilitaires Maccy Maccy est un gestionnaire de clipboard (presse papier en français), il permet via Ctrl + Shift + Cmd de retrouver d\u0026rsquo;anciens copier/coller.\nbrew install maccy\nRectangle Je travaille avec un écran déporté et j\u0026rsquo;aime pouvoir réarranger mon espace de travail sans avoir à bouger mes mains du clavier.\nRectangle répond justement à ce besoin et permet de changer la disposition des fenêtres via des raccourcis clavier.\nTerminal et CLIs Dans la mesure où nous passons une quantité non négligeable dans le terminal, il est important d\u0026rsquo;avoir une configuration optimisée. Voici les outils que je recommande.\nHomeBrew Homebrew est un gestionnaire de package qui permet d\u0026rsquo;installer et de mettre à jour de nombreux outils.\nC\u0026rsquo;est notamment celui ci que j\u0026rsquo;utilise pour installer mes CLI.\nInstallation Une commande suffit :\n/bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; Utilisation brew install kubectl Il est également possible d\u0026rsquo;installer des clients lourds via les cask :\nbrew install --cask ghostty Ghostty J\u0026rsquo;ai longtemps utilisé le terminal par défaut de MacOs qui répond à la majorité de mes besoins. Je suis néanmoins récemment passé sur Ghostty qui est écrit en Rust et semble avoir la côte ces derniers temps notamment grâce à ses performances.\nInstallation Soit via brew, cf. la commande juste au dessus.\nSoit via cette page.\nOhMyZsh + Power10k Là encore il s\u0026rsquo;agit d\u0026rsquo;un couple d\u0026rsquo;outils largement reconnus. OhMyZsh permet de personnaliser le terminal avec des thèmes et plugins qui améliorent l\u0026rsquo;expérience du développeur.\nOhMyzsh Installation sh -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; Si tout se passe bien vous devriez avoir le message suivant :\nConfiguration Le fichier de configuration est accessible via\nvi ~/.zshrc\nPlugins autosuggesions zsh-syntax-highlighting zsh-fast-syntax-highlighting zsh-autocomplete git clone https://github.com/zsh-users/zsh-autosuggestions.git $ZSH_CUSTOM/plugins/zsh-autosuggestions git clone https://github.com/zsh-users/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting git clone https://github.com/zdharma-continuum/fast-syntax-highlighting.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/fast-syntax-highlighting git clone --depth 1 -- https://github.com/marlonrichert/zsh-autocomplete.git $ZSH_CUSTOM/plugins/zsh-autocomplete Dans ~/.zshrc chercher la ligne :\nplugins=(git).\nEt remplacer la par :\nplugins=(git zsh-autosuggestions zsh-syntax-highlighting fast-syntax-highlighting zsh-autocomplete)\nUne dernière commande pour recharger la configuration :\nsource ~/.zshrc Tapez désormais votre commande bash favorite, vous devriez avoir des suggestions d\u0026rsquo;autocomplétion :\nGit OhMyZsh est installé avec un plugin git que je trouve surpuissant, voici mes commandes favorites :\nglog #git log gc -m #git commit gsta #git stash add gstp #git stash pop grbm #git rebase $(git_main_branch) gl #git pull gpsup #git push --set-upstream origin $(git_current_branch) Plus d\u0026rsquo;informations ici.\nPower10k git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \u0026#34;${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k\u0026#34; Dans ~/.zshrc cherchez ZSH_THEME et remplacer la ligne par :\nZSH_THEME=\u0026ldquo;powerlevel10k/powerlevel10k\u0026rdquo;\nPuis encore une fois rechargez la configuration\nsource ~/.zshrc Power10k va vous guider à travers la configuration du plugin pour avoir le terminal qui répond à vos goûts.\nJQ \u0026amp; YQ jq et yq sont deux utilitaires que j\u0026rsquo;affectionne particulièrement pour manipuler du JSON et du YAML (très utile pour les fichiers de configuration Kubernetes).\nInstallation brew install jq yq Utilisation Les deux commandes ont une syntaxe identique.\nVoici quelques exemples très utiles :\n# Lister le nom de tous les pods kubectl get pods -A -o json | yq \u0026#39;.items[].metadata.name\u0026#39; # Afficher l\u0026#39;image d\u0026#39;un pod kubectl get deploy my-app -o json | yq \u0026#39;.spec.template.spec.containers[0].image\u0026#39; # Extraire toutes les images utilisées dans le cluster kubectl get pods -A -o yaml | yq \u0026#39;.items[].spec.containers[].image\u0026#39; | sort -u Terraform Pour gérer l\u0026rsquo;infrastructure, la norme est l\u0026rsquo;infrastructure as code (IaC). Le leader sur ce segment est bien évidemment Terraform, installable avec \u0026hellip; brew !\nbrew tap hashicorp/tap #Repo officiel des packages Hashicorp brew install hashicorp/tap/terraform Si vous utilisez OpenTofu ou Pulumi, le principe est le mêmme :\nbrew install opentofu brew install pulumi AWS / Azure / GCP Il y a de grandes chances que vous travaillez sur l\u0026rsquo;un des 3 si ce n\u0026rsquo;est plusieurs d\u0026rsquo;entre eux.\n#AWS CLI brew install awscli #Azure CLI brew install azure-cli #GCP CLI brew install --cask gcloud-cli Mes alias Enfin, voici quelques alias que j\u0026rsquo;utilise quotidiennement\nalias k=\u0026#39;kubectl\u0026#39; alias kpf=\u0026#39;kubectl port-forward\u0026#39; alias kctx=\u0026#39;kubectl config get-contexts\u0026#39; alias kswitch=\u0026#39;kubectl config use-context\u0026#39; alias kdebug=\u0026#39;kubectl run -i --tty debug-busybox --image=busybox --restart=Never --rm -- sh\u0026#39; Administration Kubernetes k9s k9s pour administrer les clusters kubernetes, c\u0026rsquo;est un outil à l\u0026rsquo;apparence geek mais très puissant lorsqu\u0026rsquo;on connait ses raccourcis.\nbrew install k9s Headlamp Je m\u0026rsquo;essaie à l\u0026rsquo;utilisation de Headlamp dernièrement, son interface est agréable et il commence à avoir un paquet de plugins sympas : FluxCD, cert manager, Gateway API, etc.\nJe suis moins à l\u0026rsquo;aise avec Headlamp qu\u0026rsquo;avec k9s mais je me force à l\u0026rsquo;utiliser car bien plus complet que son homologue.\nbrew install --cask headlamp Conclusion Voilà mon starter pack en 2026 pour pouvoir travailler efficacement sur du Cloud / Kubernetes sur MacOs.\nJ\u0026rsquo;essaierai de mettre à jour cet article au fur et à mesure.\n","date":"2026-07-30T19:15:33+02:00","image":"/posts/mon-setup-mac/mac-setup.png","permalink":"/posts/mon-setup-mac/","title":"Setup DevOps / Cloud MacOs 2026"},{"content":"Dans la continuité du post précédent sur mon outil CLI d\u0026rsquo;analyse de coûts à partir d\u0026rsquo;export FOCUS, je vous propose aujourd\u0026rsquo;hui de découvrir Infracost.\nIl permet de Shift Left (terme souvent utilisé en FinOps) en intégrant la prévision des coûts directement dans le workflow de développement, en particulier lors de l\u0026rsquo;utilisation d\u0026rsquo;Infrastructure as Code (IaC) comme Terraform.\nMise en place Installation Windows\nchoco install infracost\nMac/Linux\nbrew install infracost\nValidation\ninfracost \u0026ndash;version #retourne 2.9.x\nAPI Key Il est possible d\u0026rsquo;expérimenter gratuitement l\u0026rsquo;outil en récupèrant une clé d\u0026rsquo;API.\ninfracost setup\nSuivez les instructions pour créer un compte et obtenir votre clé d\u0026rsquo;API.\nPremier scan Pour tester l\u0026rsquo;outil je l\u0026rsquo;ai implémenté dans mon projet fil rouge TheForge en CLI pour démarrer.\nPour avoir plus de détails :\ninfracost inspect --failing Failing policies (1 policy · 1 resource) ⚙️ Compute - consider using current generation machine types google_container_node_pool.primary_nodes · gke.tf:25 N1s are first-generation general purpose VMs. For most use-cases (excluding GPU workloads), Google recommends one of their second-generation types such as: - **E2** offers the lowest total cost of ownership with up to 31% savings compared to N1. - **N2** uses 2nd Gen Intel Xeon Scalable Processors (Cascade Lake) and offers a greater than 20% price-performance improvement for many workloads and support up to 25% more memory per vCPU. - **N2D** uses latest 2nd Gen AMD EPYC (Rome) CPUs, and support the highest core count and memory of any general-purpose Compute Engine VM. Le message est explicite, le template Terraform utilisé pour créer le cluster GKE est obsolète et il est recommandé d\u0026rsquo;utiliser un type de machine plus récent pour réduire les coûts. Fixons cette erreur.\nEn utilisant le calculateur de côuts GCP j\u0026rsquo;obtiens un tarif de 39$/mois pour une machine n1-standard-1 (1 vCPU, 3.75 GB RAM) et 27$/mois pour une machine e2-medium (1 vCPU, 4 GB RAM) et jusqu\u0026rsquo;à 13$/mois en basculant sur des machines \u0026ldquo;spot\u0026rdquo;.\nC\u0026rsquo;est cette dernière option que j\u0026rsquo;ai choisie car il s\u0026rsquo;agit d\u0026rsquo;un cluster de test, nous verrons un peu plus loin le résultat.\nQuelques features CLI Sa CLI est plutôt bien fournie et dispose notamment de nombreuses fonctionnalités telles que :\ninfracost inspect --group-by resource Show every resource sorted by cost infracost inspect --failing Show only failing policies infracost inspect --group-by policy Group results by policy infracost inspect --policy \u0026lt;name\u0026gt; Drill into a specific policy infracost inspect --diagnostics Show warning-severity diagnostics from this scan infracost inspect --json Re-run with full JSON output Vous noterez que certaines commandes sont similaires à celles de l\u0026rsquo;outil FOCUS que j\u0026rsquo;ai présenté dans le post précédent, la grande différence étant qu\u0026rsquo;Infracost fait des estimations de coûts à partir de l\u0026rsquo;IaC là où ma CLI se base sur les coûts réels. Les deux outils ont des similarités mais sont complémentaires.\nDashboard Infracost propose également une interface web dashboard pour visualiser les coûts et vos politiques FinOps.\nAgents IA Infracost s\u0026rsquo;intègre avec vos agents IA, vous pouvez ainsi donner accès à Claude / Copilot accès à votre organisation pour qu\u0026rsquo;ils puissent vous aider à analyser vos coûts et vos politiques FinOps.\nClaude\nclaude plugin marketplace add infracost/agent-skills claude plugin install infracost@infracost\nCopilot\ncopilot plugin marketplace add infracost/agent-skills copilot plugin install infracost@infracost\npuis Give me a full cost breakdown of this project and highlight the most expensive resources.\nExtension IDE VsCode : ext install Infracost.infracost JetBrains : https://plugins.jetbrains.com/plugin/24761-infracost Integration CI/CD Enfin, il est possible d\u0026rsquo;intégrer ces scans dans des pipelines de CI/CD et d\u0026rsquo;intégrer le rapport dans vos revues de PR. Infracost bénéficie d\u0026rsquo;une intégration native avec GitHub Actions, GitLab CI/CD et Azure Pipelines si vous avez suivi l\u0026rsquo;étape de setup vous devriez en bénéficier par défaut.\nSinon il est également possible d\u0026rsquo; implémenter manuellement les scans dans vos pipelines.\nEnfin, le fichier de configuration infracost.yml permet de personnaliser le comportement de l\u0026rsquo;outil et des scans.\nRésultat après avoir corrigé les remontées du scan précédent et poussé une PR : Pour aller plus loin Comme nous l\u0026rsquo;avons vu, Infracost est un outil complet pour mettre en place une stratégie FinOps et \u0026ldquo;Shift Left\u0026rdquo; dans vos projets IaC. Il est possible d\u0026rsquo;aller plus loin et d\u0026rsquo;avoir une vision globale de vos coûts cloud à travers vos différents repository. Infracost est aussi capable de vous accompagner sur la stratégie de tagging de vos ressources et de mener des itérations FinOps \u0026ldquo;Campaigns\u0026rdquo; dans une optique d\u0026rsquo;amélioration continue.\nN\u0026rsquo;hésitez pas à consulter la documentation officielle pour découvrir toutes les fonctionnalités et possibilités offertes par Infracost.\n","date":"2026-07-16T19:00:39+02:00","image":"/posts/infracost/infracost.png","permalink":"/posts/infracost/","title":"Infracost : anticipez vos coûts cloud avant le terraform apply"},{"content":"Depuis quelques années, la spécification FOCUS (FinOps Open Cost and Usage Specification) s\u0026rsquo;impose comme le format commun pour normaliser les exports de facturation des majors comme AWS, Azure et GCP. Un seul modèle de données pour analyser ses coûts cloud, quel que soit le fournisseur.\nIl existe déjà des outils qui exploitent ce format pour mettre en place une stratégie FinOps, mais souvent sous forme de Saas. Je n\u0026rsquo;ai pas trouvé d\u0026rsquo;alternative Open Source répondant à mes besoins, c\u0026rsquo;est pourquoi j\u0026rsquo;ai décidé de le développer moi même.\nLe projet Objectif Obtenir un diagnostic rapide de la maturité FinOps d\u0026rsquo;une organisation, en m\u0026rsquo;appuyant sur des exports FOCUS. A partir de ce diagnostic il est ensuite plus simple de prioriser les actions à mener pour réduire le gaspillage et améliorer la visibilité des coûts.\nUtilisation Le script se lance en ligne de commande avec un export FOCUS (csv, parquet ou JSON) en entrée et produit un rapport console, un export JSON et un rapport HTML.\nuv run focus_analyzer my_focus_export.csv\nou après un uv tool install --editable .\nfocus-analyzer my_focus_export.csv\nLes features Concentration (Pareto) : quelles ressources concentrent 80 % de la facture ? Commitments sous-utilisés : quelle part des Reserved Instances / CUDs est billée comme Unused ? Hygiène de tagging : quel pourcentage du coût est billé sur des ressources sans tag exploitable ? Anomalies de coût : détection de pics/chutes journaliers par service via un z-score sur moyenne mobile. Le rapport Aperçu du rapport HTML généré en sortie du script :\nLa stack technique Même si ma stack de prédilection est plutot NodeJS j\u0026rsquo;ai choisi de développer cet outil en Python (avec l\u0026rsquo;aide de Claude Code) pour me familiariser avec l\u0026rsquo;écosystème Python grandissant.\nJe suis parti sur les derniers standard d\u0026rsquo;Astral :\nuv pour la gestion des dépendances et l\u0026rsquo;environnement virtuel ruff pour le linting et le formatage pytest pour les tests unitaires python-semantic-release pour la release sémantique automatique Limitations FOCUS 1.2 : Je me suis limité à l\u0026rsquo;analyse FOCUS 1.2 qui est la version actuellement supportée par GCP, AWS et Azure. J\u0026rsquo;envisage de faire évoluer le projet pour supporter les prochaines versions.\nFormat des données : selon le provider le script peut encore être capricieux, en effet, les majors respectent FOCUS mais se permettent d\u0026rsquo;ajouter des propriétés supplémentaires.\nVolume de données : Je n\u0026rsquo;ai pas encore l\u0026rsquo;occasion de tester le script sur plusieurs millions de lignes.\nPyPi : le projet n\u0026rsquo;est pas encore publié sur PyPi, je le distribue pour l\u0026rsquo;instant via GitHub.\nCe que ce projet montre Au-delà de l\u0026rsquo;outil lui-même, c\u0026rsquo;est un exercice complet : compréhension d\u0026rsquo;un référentiel métier (FOCUS), architecture modulaire pensée pour évoluer sans dette, et un pipeline de qualité outillé de bout en bout.\nLe code est disponible sur GitHub.\n","date":"2026-07-14T17:30:00+02:00","image":"/posts/focus-analyzer/focus-analyzer.png","permalink":"/posts/focus-analyzer/","title":"FinOps FOCUS Analyzer : construire une CLI d'analyse de facturation cloud"},{"content":"Même si je n\u0026rsquo;ai pas encore eu l\u0026rsquo;occasion d\u0026rsquo;écrire un article sur Claude Code, je l\u0026rsquo;utilise depuis quelques mois sur des projets privés et également depuis plusieurs semaines en entreprise.\nDans cet article je vous partage mes tips \u0026amp; tricks pour utiliser Claude Code pour un repo Terraform, nous utiliserons encore une fois le projet the-forge-infra comme exemple.\nCLAUDE.md : le point de départ La première chose à faire lorsque vous avez accès à Claude Code est de créer un fichier CLAUDE.md à la racine de votre projet qui servira de point d\u0026rsquo;entrée pour Claude Code et contiendra toutes les informations nécessaires pour comprendre le projet. C\u0026rsquo;est un peu le README.md de Claude.\nCe fichier se doit d\u0026rsquo;être le plus complet possible, il doit contenir toutes les informations nécessaires pour comprendre le projet, son architecture, ses dépendances, ses contraintes, etc. mais aussi d\u0026rsquo;être relativement concis. Si votre fichier contient 500 lignes, ces mêmes 500 lignes seront ingérées par Claude à chaque prompt et consommera ainsi beaucoup de tokens.\nVoici un exemple minimal de structure pour écrire votre premier fichier (pour un repo Terraform) :\n# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project overview ### Repository structure - `main.tf`: Main Terraform configuration file. - `variables.tf`: File containing all Terraform variables. - `outputs.tf`: File containing all Terraform outputs. - `modules/`: Directory containing reusable Terraform modules. ### Code conventions Resources must be named using the following convention: `\u0026lt;resource_type\u0026gt;_\u0026lt;resource_name\u0026gt;`. Commits must follow the conventional commit format: `\u0026lt;type\u0026gt;(\u0026lt;scope\u0026gt;): \u0026lt;description\u0026gt;`. ## Forbidden actions * Never push directly to the main branch. Always create a feature branch and submit a pull request for review./ * Never use hardcoded values in Terraform files. Always use variables or data sources. * Never store sensitive information in the repository. Use a secure secrets management solution instead./ Les skills Cette feature permet de décrire à Claude comment interagir avec notre repo pour cette tâche, en fonction du contexte il sera capable de déterminer si il doit utiliser un skill ou non, et si oui lequel.\nVoici quelques exemples que j\u0026rsquo;utilise sur le repo d\u0026rsquo;infra du projet TheForge.\nCreate-module Il n\u0026rsquo;est pas rare d\u0026rsquo;avoir à créer des modules Terraform, c\u0026rsquo;est pourquoi j\u0026rsquo;ai créé ce skill pour automatiser la partie rébarbative : création des fichiers de bases, variables, outputs, README, etc. Claude me fournit aussi la structure de base du module sur laquelle je peux ensuite faire du fine tuning.\n.claude/skills/create-module/SKILL.md\n--- name: create-module description: Create a new Terraform module. Use this skill when user asks to create a new Terraform module with a standard file structure. --- Create a new Terraform module scaffold. The module name comes from `$ARGUMENTS` (kebab-case). If empty, ask the user for the name before proceeding. Create the directory `modules/\u0026lt;name\u0026gt;/` with exactly these five files: **`modules/\u0026lt;name\u0026gt;/main.tf`** **`modules/\u0026lt;name\u0026gt;/variables.tf`** **`modules/\u0026lt;name\u0026gt;/outputs.tf`** **`modules/\u0026lt;name\u0026gt;/README.md`** Check https://registry.terraform.io/ to fill those files with the appropriate content for a Terraform module. You must use the latest version for the given provider if appropriate, pin the exact version (wildcard versions are not allowed). Enrich the `README.md` with a description of the module, usage instructions, and examples. Do not add any other files or directories beyond these five. After creating the files, list them with their relative paths, then remind the user to: - Ensure every variable in `variables.tf` has both `description` and `type` set. Résultat lorsqu\u0026rsquo;on demande à Claude de créer un module via un prompt :\ncrées un module google_storage_bucket Create PR Ce skill a pour but de créer la PR à partir d\u0026rsquo;une branche et faire un summary des changements pour faciliter la revue de mes collègues. J\u0026rsquo;ai également ajouté une checklist pour m\u0026rsquo;assurer que je n\u0026rsquo;ai rien oublié avant de créer la PR, accélérant une fois encore la boucle de feedback.\n.claude/skills/create-pr/SKILL.md\n--- name: create-pr description: Create a new Github Pull Request. Use this skill when user asks to create a new Github Pull Request. --- Pre-requisites before creating a PR : * Ensure no sensitive information is present in the code changes (e.g., passwords, API keys, secrets). * Run terraform fmt, terraform validate to ensure the code is properly formatted and valid. * Ensure each commit message follows the conventional commit format (e.g., feat: add new feature, fix: fix bug, docs: update documentation). 1. Create a new Github Pull Request for the current branch. The title of the PR comes from `$ARGUMENTS`. If empty, ask the user for the title before proceeding. 2. The PR description should be generated based on the changes in the current branch. If `$ARGUMENTS` contains a description, use it instead. 3. The PR should be created against the default branch of the repository (usually `main` or `master`). 4. After creating the PR, output the URL of the newly created PR. crées moi la Pull request \u0026ldquo;add claude config\u0026rdquo;\nConclusion Comme nous l\u0026rsquo;avons vu, ces fichiers de configuration agentic sont de simples fichiers markdown et sont facilement partageables. A ce titre, il existe de nombreux repository pour partager des templates de fichiers CLAUDE.md, notamment pour Terraform. Je ne les utilisent pas encore pour le moment parce que je ne souhaite pas m\u0026rsquo;imposer de contraintes supplémentaires, je préfère mettre en place ma code base et ma stratégie et expliquer à l\u0026rsquo;agent comment je souhaite qu\u0026rsquo;il interagisse avec mon code.\nVous pouvez retrouver le repo d\u0026rsquo;exemple que j\u0026rsquo;ai utilisé pour cet article sur GitHub.\n","date":"2026-07-02T19:17:37+02:00","image":"/posts/terraform-claude-code/claude-code-terraform.png","permalink":"/posts/terraform-claude-code/","title":"Générer du code Terraform avec Claude Code"},{"content":"Dans les articles précédents de la série TheForge, nous avons vu comment déployer un cluster GKE + VPC + ArgoCD sur GCP via Terraform.\nCette stack est fonctionnelle mais elle a ses limites : le state Terraform est local, l\u0026rsquo;authentification GCP repose sur gcloud et il n\u0026rsquo;y a aucun workflow CI/CD.\nComment passer d\u0026rsquo;un workflow PoC local à une configuration CI/CD complète applicable en production ?\nAuthentification GCP via Workload Identity Federation (WIF) pour GitHub Actions Remote state Terraform sur GCS Workflows GitHub Actions Workload Identity Federation Workload Identity Federation (WIF) est une fonctionnalité de GCP qui permet à des applications externes (comme GitHub Actions) d\u0026rsquo;obtenir des identités temporaires pour accéder aux ressources GCP, sans avoir besoin de stocker des clés JSON de service account.\nVariables shell à exporter Remplacez les valeurs par celles de votre projet GCP et de votre repo GitHub.\nexport PROJECT_ID=\u0026#34;deft-accord-496812-k9\u0026#34; export REGION=\u0026#34;europe-west1\u0026#34; export GITHUB_ORG=\u0026#34;Bernedotcom2312\u0026#34; export GITHUB_REPO=\u0026#34;theforge-infra\u0026#34; export SA_NAME=\u0026#34;github-actions-terraform\u0026#34; export SA_EMAIL=\u0026#34;${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\u0026#34; export WIF_POOL=\u0026#34;github-wif-pool\u0026#34; export WIF_PROVIDER=\u0026#34;github-actions-provider\u0026#34; export TF_STATE_BUCKET=\u0026#34;theforge-infra-tfstate-${PROJECT_ID}\u0026#34; Activer les APIs GCP nécessaires Dans la suite de cet article nous allons avoir besoin d\u0026rsquo;activer plusieurs APIs GCP pour que Terraform et GitHub Actions puissent interagir avec GCP, notamment l\u0026rsquo;IAM, GKE, Compute Engine et Cloud Storage.\ngcloud services enable cloudresourcemanager.googleapis.com iam.googleapis.com \\ iamcredentials.googleapis.com sts.googleapis.com container.googleapis.com \\ compute.googleapis.com storage.googleapis.com --project=\u0026#34;${PROJECT_ID}\u0026#34; Créer le bucket GCS pour le state Terraform gcloud storage buckets create \u0026#34;gs://${TF_STATE_BUCKET}\u0026#34; \\ --project=\u0026#34;${PROJECT_ID}\u0026#34; --location=\u0026#34;${REGION}\u0026#34; \\ --uniform-bucket-level-access --public-access-prevention gcloud storage buckets update \u0026#34;gs://${TF_STATE_BUCKET}\u0026#34; --versioning Créer le Service Account gcloud iam service-accounts create \u0026#34;${SA_NAME}\u0026#34; \\ --project=\u0026#34;${PROJECT_ID}\u0026#34; \\ --display-name=\u0026#34;GitHub Actions Terraform SA\u0026#34; Attribuer les rôles IAM minimaux gcloud projects add-iam-policy-binding \u0026#34;${PROJECT_ID}\u0026#34; \\ --member=\u0026#34;serviceAccount:${SA_EMAIL}\u0026#34; --role=\u0026#34;roles/container.admin\u0026#34; gcloud projects add-iam-policy-binding \u0026#34;${PROJECT_ID}\u0026#34; \\ --member=\u0026#34;serviceAccount:${SA_EMAIL}\u0026#34; --role=\u0026#34;roles/compute.networkAdmin\u0026#34; gcloud projects add-iam-policy-binding \u0026#34;${PROJECT_ID}\u0026#34; \\ --member=\u0026#34;serviceAccount:${SA_EMAIL}\u0026#34; --role=\u0026#34;roles/viewer\u0026#34; gcloud storage buckets add-iam-policy-binding \u0026#34;gs://${TF_STATE_BUCKET}\u0026#34; \\ --member=\u0026#34;serviceAccount:${SA_EMAIL}\u0026#34; --role=\u0026#34;roles/storage.objectAdmin\u0026#34; gcloud iam service-accounts add-iam-policy-binding \u0026#34;${SA_EMAIL}\u0026#34; \\ --project=\u0026#34;${PROJECT_ID}\u0026#34; \\ --member=\u0026#34;serviceAccount:${SA_EMAIL}\u0026#34; --role=\u0026#34;roles/iam.serviceAccountUser\u0026#34; Créer le pool WIF et le provider OIDC GCP recommande de créer un pool WIF par environnement (dev, staging, prod) pour limiter l\u0026rsquo;accès aux ressources GCP. Dans cet exemple, nous allons créer un pool WIF unique pour le repo GitHub.\ngcloud iam workload-identity-pools create \u0026#34;${WIF_POOL}\u0026#34; \\ --project=\u0026#34;${PROJECT_ID}\u0026#34; --location=\u0026#34;global\u0026#34; \\ --display-name=\u0026#34;GitHub Actions WIF Pool\u0026#34; gcloud iam workload-identity-pools providers create-oidc \u0026#34;${WIF_PROVIDER}\u0026#34; \\ --project=\u0026#34;${PROJECT_ID}\u0026#34; --location=\u0026#34;global\u0026#34; \\ --workload-identity-pool=\u0026#34;${WIF_POOL}\u0026#34; \\ --issuer-uri=\u0026#34;https://token.actions.githubusercontent.com\u0026#34; \\ --attribute-mapping=\u0026#34;google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner\u0026#34; \\ --attribute-condition=\u0026#34;assertion.repository_owner == \u0026#39;${GITHUB_ORG}\u0026#39;\u0026#34; L\u0026rsquo;attribute-condition est ce qui garantit que seul mon organisation GitHub peut s\u0026rsquo;authentifier, pas n\u0026rsquo;importe qui avec un token OIDC GitHub.\nLier le SA au pool export WIF_POOL_NAME=$(gcloud iam workload-identity-pools describe \u0026#34;${WIF_POOL}\u0026#34; \\ --project=\u0026#34;${PROJECT_ID}\u0026#34; --location=\u0026#34;global\u0026#34; --format=\u0026#34;value(name)\u0026#34;) gcloud iam service-accounts add-iam-policy-binding \u0026#34;${SA_EMAIL}\u0026#34; \\ --project=\u0026#34;${PROJECT_ID}\u0026#34; \\ --role=\u0026#34;roles/iam.workloadIdentityUser\u0026#34; \\ --member=\u0026#34;principalSet://iam.googleapis.com/${WIF_POOL_NAME}/attribute.repository/${GITHUB_ORG}/${GITHUB_REPO}\u0026#34; Récupérer la valeur du provider WIF pour GitHub gcloud iam workload-identity-pools providers describe \u0026#34;${WIF_PROVIDER}\u0026#34; \\ --project=\u0026#34;${PROJECT_ID}\u0026#34; --location=\u0026#34;global\u0026#34; \\ --workload-identity-pool=\u0026#34;${WIF_POOL}\u0026#34; --format=\u0026#34;value(name)\u0026#34; Configurer les variables GitHub Variable Valeur GCP_PROJECT_ID deft-accord-496812-k9 GCP_REGION europe-west1 GCP_SA_EMAIL github-actions-terraform@deft-accord-496812-k9.iam.gserviceaccount.com GCP_WIF_PROVIDER sortie de la commande 1.7 TF_STATE_BUCKET theforge-infra-tfstate-deft-accord-496812-k9 Créer l\u0026rsquo;environnement GitHub production Settings → Environments → New environment → production\nRequired reviewers : soi-même (ou le lead) Deployment branches : main uniquement Migration du state vers GCS Maintenant que les pré-requis de gestion de droits sont en place, nous allons migrer le state Terraform local vers GCS.\nCréer backend.tf terraform { backend \u0026#34;gcs\u0026#34; { bucket = \u0026#34;lenomdevotrebucket\u0026#34; prefix = \u0026#34;terraform/state\u0026#34; } } Migrer le state local → GCS gcloud auth application-default login terraform init -migrate-state # répondre \u0026#34;yes\u0026#34; à la question de migration terraform plan -var-file=terraform.tfvars # vérifier que tout fonctionne, il ne doit pas y avoir de changement rm terraform.tfstate terraform.tfstate.backup git add backend.tf git commit -m \u0026#34;chore: migrate Terraform state backend to GCS\u0026#34; git push origin main Workflows GitHub Actions Dernière étape : mettre en place les workflows GitHub Actions pour CI/CD pour valider notre configuration dans une PR et appliquer les changements sur main après approbation.\nFichiers à créer .github/ └── workflows/ ├── terraform-ci.yml (PR → validate + plan commenté) └── terraform-cd.yml (merge main → apply) terraform-ci.yml\nname: Terraform CI on: pull_request: branches: - main paths: - \u0026#34;**.tf\u0026#34; - \u0026#34;**.tfvars\u0026#34; - \u0026#34;.github/workflows/terraform-ci.yml\u0026#34; permissions: contents: read id-token: write pull-requests: write jobs: terraform-ci: name: Validate \u0026amp; Plan runs-on: ubuntu-26.04 defaults: run: shell: bash steps: - name: Setup uses: ./.github/actions/terraform-setup with: terraform_wrapper: \u0026#39;true\u0026#39; - name: Terraform fmt id: fmt run: terraform fmt -check -recursive continue-on-error: true - name: Terraform init id: init run: | terraform init \\ -backend-config=\u0026#34;bucket=${{ vars.TF_STATE_BUCKET }}\u0026#34; \\ -input=false - name: Terraform validate id: validate run: terraform validate -no-color - name: Setup TFLint uses: terraform-linters/setup-tflint@v6.2.2 with: tflint_version: v0.63.1 - name: TFLint init run: tflint --init - name: TFLint run id: tflint run: tflint --format=compact --no-color continue-on-error: true - name: Checkov scan id: checkov uses: bridgecrewio/checkov-action@v12.1347.0 with: directory: . framework: terraform output_format: cli soft_fail: false continue-on-error: true - name: Terraform plan id: plan run: | terraform plan \\ -var=\u0026#34;project_id=${{ vars.GCP_PROJECT_ID }}\u0026#34; \\ -var=\u0026#34;region=${{ vars.GCP_REGION }}\u0026#34; \\ -out=plan.tfplan \\ -no-color \\ -input=false 2\u0026gt;\u0026amp;1 | tee plan-output.txt echo \u0026#34;exitcode=${PIPESTATUS[0]}\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; continue-on-error: true - name: Upload plan artifact if: steps.plan.outcome == \u0026#39;success\u0026#39; uses: actions/upload-artifact@v7.0.1 with: name: plan-${{ github.sha }} path: plan.tfplan retention-days: 1 Si vous avez suivi mon article précédent sur les pre-commit pour terraform voici les contrôles que je vais appliquer : terraform fmt, terraform validate, tflint et checkov pour la sécurité.\nterraform-cd.yml\nname: Terraform CD on: push: branches: - main paths: - \u0026#34;**.tf\u0026#34; - \u0026#34;**.tfvars\u0026#34; - \u0026#34;.github/workflows/terraform-cd.yml\u0026#34; permissions: contents: read id-token: write actions: read jobs: terraform-apply: name: Apply to production runs-on: ubuntu-26.04 environment: production defaults: run: shell: bash steps: - name: Setup uses: ./.github/actions/terraform-setup - name: Terraform init run: | terraform init \\ -backend-config=\u0026#34;bucket=${{ vars.TF_STATE_BUCKET }}\u0026#34; \\ -input=false - name: Find CI run for this commit id: find-ci-run uses: actions/github-script@v7.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { data } = await github.rest.actions.listWorkflowRuns({ owner: context.repo.owner, repo: context.repo.repo, workflow_id: \u0026#39;terraform-ci.yml\u0026#39;, head_sha: context.sha, status: \u0026#39;success\u0026#39;, per_page: 1, }); const run = data.workflow_runs[0]; if (!run) core.setFailed(`No successful CI run found for ${context.sha}`); core.setOutput(\u0026#39;run-id\u0026#39;, String(run.id)); - name: Download plan artifact uses: actions/download-artifact@v8.0.1 with: name: plan-${{ github.sha }} run-id: ${{ steps.find-ci-run.outputs.run-id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Terraform apply run: | terraform apply \\ -auto-approve \\ -input=false \\ plan.tfplan Ce fichier sera déclenché uniquement sur le merge de la branche main et après approbation de l\u0026rsquo;environnement production, c\u0026rsquo;est le seul endroit où j\u0026rsquo;autorise le terraform apply pour respecter les principes GitOps\nVérification Pour valider la chaine compplète il faut désormais appliquer un changement mineur (ex: ajout d\u0026rsquo;un tag sur une ressource).\nVous devriez avoir un résultat semblable à celui-ci dans la PR : Une fois merged, vous devriez voir le workflow terraform-cd.yml s\u0026rsquo;exécuter et appliquer les changements sur GCP : Il suffira ensuite d\u0026rsquo;approuver le déploiement dans l\u0026rsquo;environnement production pour que le terraform apply soit exécuté.\nConclusion Nous avons vu comment passer d\u0026rsquo;un workflow Terraform local à une configuration CI/CD complète sur GCP applicable en entreprise en respectant les principes GitOps et en optimisant la boucle de feedback pour les développeurs.\nL\u0026rsquo;ensemble du code est disponible sur mon repo GitHub : the-forge-infra.\n","date":"2026-06-28T16:45:55+02:00","image":"/posts/the-forge-cicd/the-forge-cicd.png","permalink":"/posts/the-forge-cicd/","title":"CI/CD Terraform complète sur GCP en 3 étapes"},{"content":"J\u0026rsquo;ai dernièrement publié une série d\u0026rsquo;articles TheForge où l\u0026rsquo;objectif était de monter une stack Kubernetes complète en mode GitOps. Par souci de facilité j\u0026rsquo;ai choisi d\u0026rsquo;aller à l\u0026rsquo;essentiel en évitant de rentrer dans les détails de chaque outil.\nJ\u0026rsquo;ai aussi volontairement omis certaines bonnes pratiques qui auraient pu rendre la stack plus robuste, plus performante ou plus sécurisée.\nA présent, j\u0026rsquo;ai un peu plus de temps, je vais reprendre cette série pour améliorer la stack et corriger les oublis.\nUn onboarding plus simple La stack étant un clone d\u0026rsquo;un repo proposé par HashiCorp, il n\u0026rsquo;y a qu\u0026rsquo;une configuration minimale pour que la stack fonctionne (en même temps c\u0026rsquo;est ce qu\u0026rsquo;on lui demande \u0026hellip;).\nIci nous allons adopter quelques bonnes pratiques pour rendre l\u0026rsquo;onboarding plus simple et plus rapide pour un nouvel arrivant ou pour reprendre la main sur le repo après plusieurs années d\u0026rsquo;abandon.\nAjout d\u0026rsquo;un Makefile pour simplifier les commandes Je suis un peu paresseux et je trouve les commandes terraform assez pénibles à taper, en solo j\u0026rsquo;utilise beaucoup d\u0026rsquo;alias pour me simplifier la vie. En équipe il est préférable d\u0026rsquo;avoir un moyen uniformisé pour centraliser les commandes et éviter les drifts de configuration, j\u0026rsquo;ai choisi ici d\u0026rsquo;utiliser un Makefile.\nInstallation\nPage officielle de Make\nSinon :\nscoop install main/make #Windows brew install make #MacOS make --version Ensuite créez un fichier Makefile à la racine du repo et ajoutez-y les commandes terraform que vous utilisez le plus souvent.\nExemple minimal\nTFVARS_FILE := terraform.tfvars .PHONY: init validate fmt plan apply destroy output clean install: scoop install terraform init: terraform init validate: init terraform validate plan: init terraform plan -var-file=$(TFVARS_FILE) apply: init terraform apply -var-file=$(TFVARS_FILE) Notez ici la présence de la commande install qui permet d\u0026rsquo;installer terraform sur votre machine.\nUtilisation\nmake install make plan make apply Vous pouvez trouver un exemple complet dans le repo TheForge Si vous préférez le yaml vous pouvez aussi utiliser Task qui fonctionne de la même manière.\nMise à jour du README C\u0026rsquo;est LE point d\u0026rsquo;entrée pour un nouvel arrivant sur le projet. Il est donc important de le tenir à jour et de le rendre le plus clair possible. Je me suis focalisé sur les éléments primordiaux :\nPre-requis Outils à installer (terraform, kubectl, helm, \u0026hellip;) Accès aux différents comptes cloud (AWS, GCP, Azure, \u0026hellip;) Architecture Diagramme d\u0026rsquo;architecture Description des différents composants et de leurs interactions Usage Comment installer le nécessaire pour le repo Comment appliquer les changements La liste est bien entendu exhaustive mais il s\u0026rsquo;agit d\u0026rsquo;un bon début.\nUne boucle de feedback plus rapide Une fois que vous avez installer l\u0026rsquo;essentiel et que vous vous êtes familiarisé avec la stack, il est temps de passer à l\u0026rsquo;étape suivante : améliorer la boucle de feedback pour gagner du temps et éviter les erreurs.\nPre-commit : le dernier garde fou avant la CI J\u0026rsquo;affectionne particulièrement pre-commit, un outil qui permet d\u0026rsquo;exécuter des hooks avant chaque commit et qui est très pratique pour vérifier la qualité du code, le formatage, la sécurité, etc.\nPour l\u0026rsquo;installer je m\u0026rsquo;appuie sur mon Makefile en rajoutant la configuration suivante :\ninstall: scoop install terraform pip install pre-commit pre-commit install Mon choix d\u0026rsquo;outils pour le pre-commit J\u0026rsquo;aime beaucoup m\u0026rsquo;appuyer sur pre-commit-terraform qui proposent une série de hooks pré-configurés pour vérifier la qualité du code, le formatage, la sécurité, etc.\nUtilisation\n.precommit-config.yaml\n- repo: https://github.com/antonbabenko/pre-commit-terraform rev: v1.106.0 hooks: - id: terraform_fmt - id: terraform_validate - id: terraform_tflint - id: terraform_checkov Ici l\u0026rsquo;idée est d\u0026rsquo;optimiser les étapes pour que le développeur puisse avoir un feedback rapide sur son code avant de pousser ses changements vers la CI/CD et d\u0026rsquo;aller plus loin dans le process de développement. On détecte les erreurs le plus tôt possible pour éviter de perdre du temps et de l\u0026rsquo;énergie à corriger des erreurs qui auraient pu être évitées.\nVoici typiquement un scénario GitOps approprié :\nflowchart TD A[Changement locaux] --\u003e|commit| B[Validation avec pre-commit] B --\u003e |push git + PR| C[Revue de code + CI/CD] C --\u003e|merge| D[Application des changements sur le cluster]Les hooks\nLa stratégie peut varier en fonction de l\u0026rsquo;équipe et du projet, mais voici quelques hooks que j\u0026rsquo;utilise personnellement pour mes projets Terraform :\nterraform_fmt: pour formater le code Terraform terraform_validate: pour valider la syntaxe et la structure du code Terraform tflint: pour détecter les erreurs et les problèmes de style dans le code Terraform checkov: pour détecter les problèmes de sécurité dans le code Terraform N\u0026rsquo;hésitez pas à consulter la liste des hooks disponibles sur le repo pre-commit-terraform.\nConclusion Dans cet article nous avons fait un pas supplémentaire vers une implémentation state of the art de GitOps dans un repository Terraform. Dans la prochaine partie nous allons nous intéresser à la CI/CD.\n","date":"2026-06-21T19:00:00+02:00","image":"/posts/the-forge-infra-improved/the-forge-infra-precommit.png","permalink":"/posts/the-forge-infra-improved/","title":"The Forge Infra - Améliorations et bonnes pratiques"},{"content":"Je vous ai précédemment présenté Scratch qui permet d\u0026rsquo;éditer ses notes en Markdown et de les versionner sur Git.\nAujourd\u0026rsquo;hui, je vous propose de découvrir Marp.\nQu\u0026rsquo;est-ce que c\u0026rsquo;est ? Marp est un outil open-source qui permet de créer des présentations à partir de fichiers Markdown.\nPourquoi choisir Marp ? Son principal atout est de s\u0026rsquo;appuyer sur le langage Markdown ce qui le rend accessible à la grange majorité des développeurs. Pour ma part c\u0026rsquo;est mon langage de prédilection pour rédiger mes notes et documentations. Ce site est d\u0026rsquo;ailleurs entièrement rédigé en Markdown.\nIntégration dans Visual Studio Code : Marp propose une extension pour Visual Studio Code qui permet de créer et d\u0026rsquo;éditer des présentations directement depuis l\u0026rsquo;éditeur.\nExportation en PDF : Marp CLI permet d\u0026rsquo;exporter vos présentations au format PDF, ce qui est pratique pour les partager ou les imprimer.\nThèmes personnalisables : Marp offre la possibilité de personnaliser l\u0026rsquo;apparence de vos présentations grâce à des thèmes prédéfinis ou en créant vos propres styles CSS. Mon thème favori étant \u0026ldquo;pine-rose-moon\u0026rdquo; :\nLive preview\nMarpit : Marp est un écosystème (core, cli, marp for vscode) basé sur Marpit, un framework qui permet de créer des présentations en HTML et CSS à partir de Markdown. Cela signifie que vous pouvez utiliser toutes les fonctionnalités de Marpit pour personnaliser vos présentations.\nExemple Si comme moi vous avez installé l\u0026rsquo;extension pour VsCode, vous pouvez créer un fichier .md et commencer à rédiger votre présentation.\n--- marp: true --- Sources\nMarp site officiel Marpit site officiel Pine Rose Moon thème ","date":"2026-06-14T15:00:08+02:00","image":"/posts/marp/marp.png","permalink":"/posts/marp/","title":"Marp : des présentations en Markdown, sans PowerPoint"},{"content":"Qu\u0026rsquo;est-ce que c\u0026rsquo;est ? Scratch est un outil de prise de notes que j\u0026rsquo;ai découvert récemment via un article sur Korben. Il permet l\u0026rsquo;édition de notes en mode WYSIWYG tout en étant markdown based.\nParmi les principales features on trouve :\nSupport des charts Mermaid Integration git Support IA : Claude Code, Codex, etc. Qu\u0026rsquo;est ce que j\u0026rsquo;en pense ? Je l\u0026rsquo;utilise depuis quelques jours et c\u0026rsquo;est vraiment agréable à utiliser. Son interface est très similaire à l\u0026rsquo;application \u0026ldquo;Notes\u0026rdquo; d\u0026rsquo;Apple, ce qui le rend très intuitif. Les fonctionnalités sont assez basiques mais suffisent amplement pour mes besoins de prise de notes, j\u0026rsquo;ai pu essayer Notion et Obsidian mais je les trouvent trop complexes pour ce que je veux faire, je passe plus de temps à configurer l\u0026rsquo;outil et organiser mes notes.\nJe vais continuer de l\u0026rsquo;utiliser pour voir comment il évolue, la seule chose que j\u0026rsquo;ai à lui reprocher à ce jour c\u0026rsquo;est que c\u0026rsquo;est un énième outil à installer. J\u0026rsquo;aurai aimé une intégration native dans les IDE (VSCode, JetBrains, etc.) pour pouvoir prendre des notes directement dans mon environnement de travail.\nSources\nArticle de Korben Scratch repo ","date":"2026-06-13T19:00:00+02:00","image":"/posts/scratch/scratch.png","permalink":"/posts/scratch/","title":"Scratch : des notes en Markdown avec Mermaid et Git intégré"},{"content":"Le problème des secrets en GitOps Dans une approche GitOps, tout passe par le repo Git : les manifests Kubernetes, les configurations, et idéalement les secrets aussi. Mais comment gérer les secrets de manière sécurisée dans un repo Git ?\nSOPS : Secrets OPerationS Principe : chiffrement côté fichier SOPS s\u0026rsquo;intègre aux KMS, par exemple sur GCP Cloud Key Management via le principe d\u0026rsquo;Envelope Encryption : il génère une clé symétrique éphémère (DEK) pour chiffrer les secrets, et délègue à GCP KMS le chiffrement de cette DEK.\nLe KMS ne voit jamais les secrets eux-mêmes, et le fichier commité dans Git contient les valeurs chiffrées ainsi que la DEK chiffrée.\nEn local, un gcloud auth application-default login suffit alors qu\u0026rsquo;en production sur GKE il est plutot préférable d\u0026rsquo;utiliser le Workload Identity pour lier le ServiceAccount Kubernetes au compte de service GCP.\nChaque opération est enfin tracée dans Cloud Audit Logs.\nLimitations Nécessite une infrastructure de gestion de clés externe (KMS ou PGP/Age). Cela introduit une dépendance sur AWS KMS, GCP KMS, etc. La rotation de clé implique de re-chiffrer tous les fichiers concernés (sops updatekeys). SOPS ne crée pas de Secret Kubernetes directement. Il faut un outil intermédiaire : Helm Secrets, ArgoCD avec plugin, Kustomize + plugin, ou un job de déchiffrement en pipeline CI/CD. Le déchiffrement se fait souvent dans le pipeline CI/CD ou sur le poste du développeur. La valeur en clair transite quelque part, ce qui élargit la surface d\u0026rsquo;attaque. Contrairement à Sealed Secrets + un opérateur GitOps, SOPS seul ne met pas à jour les secrets dans le cluster automatiquement. C\u0026rsquo;est l\u0026rsquo;outil que j\u0026rsquo;ai le plus utilisé dans un contexte professionnel, je trouve que c\u0026rsquo;est un bon compromis pour gérer les secrets en GitOps à condition d\u0026rsquo;accepter la complexité de gestion des clés et de mettre en place les bonnes pratiques pour éviter les erreurs humaines (ex: commit de secrets en clair) qui spoiler finiront par arriver\nSOPS + ArgoCD Sops s\u0026rsquo;intègre très bien avec ArgoCD via le plugin argocd-vault-plugin.\nSinon il est aussi possible d\u0026rsquo;installer Helm Secrets comme Config Management Plugin.\nExemple d\u0026rsquo;utilisation # Chiffrer un fichier de secrets sops -e secrets.yaml \u0026gt; secrets.enc.yaml # Déchiffrer un fichier de secrets sops -d secrets.enc.yaml \u0026gt; secrets.yaml # Mettre à jour les clés de chiffrement sops updatekeys secrets.enc.yaml # Editer un fichier de secrets chiffré sops edit secrets.enc.yaml Les alternatives et leurs limites Sealed Secrets : côté cluster, clé non portable Les Sealed Secrets(GitHub) de Bitnami permettent de chiffrer les secrets côté cluster.\nSealed Secrets est composé en 2 parties:\nUn controlleur côté cluster (installable via Helm) Une CLI côté client : kubeseal (brew install kubeseal) kubeseal utilise une clé asymétrique pour chiffrer les secrets que seul le contrôleur peut déchiffrer. Ces secrets chiffrés sont ensuite encodés dans une ressource SealedSecret.\nLimitations\nVoici selon moi les principales limitations de Sealed Secrets :\nLa clé de chiffrement est stockée dans le cluster, ce qui rend les secrets non portables entre clusters. En cas de compromission du cluster, les secrets peuvent être à risque. En cas de disaster recovery, la restauration du cluster doit inclure la restauration de la clé de chiffrement pour pouvoir accéder aux secrets. Les secrets sont uniquement utilisable dans un contexte Kubernetes, pas de possibilité de les utiliser pour d\u0026rsquo;autres usages (CI/CD, etc). Je n\u0026rsquo;ai pas eu l\u0026rsquo;occasion d\u0026rsquo;utiliser Sealed Secrets dans un milieu professionnel, mais j\u0026rsquo;ai pu l\u0026rsquo;expérimenter dans des projets personnels.\nVault : puissant mais lourd à opérer Hashicorp Vault est un serveur centralisé de gestion de secrets. Ils sont stockés, chiffrés et distribués dynamiquement via une API et les applications les récupèrent au runtime.\nLimitations\nComplexité opérationnelle élevée : Vault est un service à part entière qu\u0026rsquo;il faut déployer, configurer, superviser et maintenir. Single point of failure potentiel : Si Vault est down, toutes les applications qui récupèrent leurs secrets dynamiquement peuvent être impactées. C\u0026rsquo;est un outil (trop) complet : Les concepts sont nombreux et nécessitent un temps de montée en compétence réel avant d\u0026rsquo;opérer Vault sereinement. C\u0026rsquo;est un outil que j\u0026rsquo;ai eu l\u0026rsquo;occasion d\u0026rsquo;utiliser mais je n\u0026rsquo;ai jamais eu la \u0026ldquo;chance\u0026rdquo; de le déployer et l\u0026rsquo;opérer moi même de A à Z, je ne me sens pas légitime pour en parler plus en détail dans cet article.\nA Retenir SOPS est un outil de chiffrement de secrets qui peut s\u0026rsquo;intégrer dans une approche GitOps. Comme tout outil, il vient avec ses avantages et ses inconvénients, il est important de les connaître pour faire un choix éclairé en fonction de son contexte et de ses besoins.\nPour résumer très grossièrement préférez Sealed Secrets pour démarrer et les cas d\u0026rsquo;usage simples, SOPS pour les cas d\u0026rsquo;usage plus complexes et les équipes qui ont déjà une bonne maîtrise de la gestion de clés, et Vault pour les besoins les plus avancés et les équipes qui ont les ressources pour l\u0026rsquo;opérer sereinement.\nSources\nSOPS GitHub Blog de Stéphane Robert - Sealed Secrets Vault Documentation ArgoCD Vault Plugin ","date":"2026-06-08T19:00:00+02:00","image":"/posts/sops/sops.png","permalink":"/posts/sops/","title":"SOPS - Comment gérer les secrets dans un repo GitOps?"},{"content":"Qu\u0026rsquo;est-ce que c\u0026rsquo;est ? CMCTL est un outil en ligne de commande pour interagir avec Cert Manager. CMCTL permet de gérer les ressources Cert Manager, de diagnostiquer les problèmes et d\u0026rsquo;automatiser certaines tâches liées à la gestion des certificats. Puisque nous l\u0026rsquo;avons installé dans le précédent article il me parassait intéressant de partager mes commandes préférées pour interagir avec Cert Manager via cmctl.\nInstallation brew install cert-manager/cert-manager/cmctl Sinon pour Windows via scoop\nscoop install main/cmctl Utilisation Commandes préférées Afficher les certificats gérés par Cert Manager :\nkubectl get certificates -A Afficher les certificate requests en cours :\nkubectl get certificaterequest -A Renouveler un certificat manuellement :\ncmctl renew \u0026lt;nom-du-certificat\u0026gt; Approuver/Désapprouver une demande de certificat manuellement :\n$ cmctl approve -n istio-system mesh-ca --reason \u0026#34;pki-team\u0026#34; --message \u0026#34;this certificate is valid\u0026#34; Approved CertificateRequest \u0026#39;istio-system/mesh-ca\u0026#39; $ cmctl deny -n my-app my-app --reason \u0026#34;example.com\u0026#34; --message \u0026#34;violates policy\u0026#34; Denied CertificateRequest \u0026#39;my-app/my-app\u0026#39; Inspecter un certificat en détail :\ncmctl inspect \u0026lt;nom-du-certificat\u0026gt; Conclusion C\u0026rsquo;est un article volontairement cours pour partager cet outil et les commandes que j\u0026rsquo;utilise le plus souvent pour manipuler les ressources de Cert Manager.\nSources\nhttps://cert-manager.io/docs/reference/cmctl/ ","date":"2026-06-04T19:00:00+02:00","image":"/posts/cmctl/cmctl.png","permalink":"/posts/cmctl/","title":"Cmctl : piloter Cert Manager en ligne de commande"},{"content":"Ce qu\u0026rsquo;on va faire Nous allons installer Nginx Gateway Fabric pour exposer nos services Kubernetes sur internet, ainsi que Cert Manager pour gérer les certificats TLS.\nArchitecture cible Je possède un nom de domaine chez Cloudflare, je vais donc utiliser Cloudflare comme DNS provider pour gérer les enregistrements DNS de mon domaine. Nginx Gateway Fabric sera configuré pour utiliser un LoadBalancer de type External, ce qui me permettra d\u0026rsquo;obtenir une adresse IP publique pour exposer mes services.\nVoici un schéma pour une meilleur compréhension :\ngraph TD A[Internet] --\u003e B[Cloudflare DNS + Proxy] B --\u003e C[Enregistrement DNS pointant vers l'IP du LoadBalancer GCP] C --\u003e D[LoadBalancer GCP] D --\u003e E[Nginx Gateway Fabric sur Kubernetes] E --\u003e F[HTTPRoute -\u003e Services Kubernetes] F --\u003e G[Cert Manager pour TLS] Étapes 1. Installation de la CRD gateway API Les ingress controller ne sont plus supportés, il faut désormais ce tourner vers les gateway API. Il existe plusieurs implémentations, j\u0026rsquo;ai choisi d\u0026rsquo;installer Nginx Gateway Fabric qui est une solution robuste et bien supportée.\nkubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml ou via ArgoCD (la sync-wave à 0 permet de s\u0026rsquo;assurer que les CRDs sont installées avant les autres ressources) :\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: gateway-api-crds namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io annotations: argocd.argoproj.io/sync-wave: \u0026#34;0\u0026#34; spec: project: default source: repoURL: https://github.com/nginx/nginx-gateway-fabric.git targetRevision: v2.6.3 path: config/crd/gateway-api/standard destination: server: https://kubernetes.default.svc syncPolicy: automated: prune: true selfHeal: true syncOptions: - ServerSideApply=true 2. Installation de Nginx Gateway Fabric Je vais aller assez vite sur l\u0026rsquo;installation de Nginx Gateway Fabric, si vous avez suivi les articles précédents cela ne devrait pas poser de problème.\nOn ajoute dans notre repo de CD la configuration suivante :\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: nginx-gateway-fabric namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io annotations: argocd.argoproj.io/sync-wave: \u0026#34;1\u0026#34; spec: project: default source: repoURL: ghcr.io/nginx/charts chart: nginx-gateway-fabric targetRevision: 2.6.3 helm: releaseName: nginx-gateway-fabric destination: server: https://kubernetes.default.svc namespace: nginx-gateway syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true Après quelques secondes les ressources sont créées et nous avons notre gateway qui tourne.\nkubectl get pods -n nginx-gateway NAME READY STATUS RESTARTS AGE nginx-gateway-fabric-77c559bb4f-c7zlb 1/1 Running 0 93s 3. Installation de Cert Manager Le principe est le même, voici la configuration à appliquer :\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: cert-manager namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io annotations: argocd.argoproj.io/sync-wave: \u0026#34;0\u0026#34; spec: project: default source: repoURL: https://charts.jetstack.io chart: cert-manager targetRevision: v1.20.2 helm: releaseName: cert-manager values: | crds: enabled: true keep: true destination: server: https://kubernetes.default.svc namespace: cert-manager syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true - ServerSideApply=true Après quelques secondes les ressources sont créées et nous avons notre cert manager qui tourne.\nkubectl get pods -n cert-manager NAME READY STATUS RESTARTS AGE cert-manager-65dd558d9c-qzjw9 1/1 Running 0 5m6s cert-manager-cainjector-77bd875bf4-6bcpn 1/1 Running 0 5m5s cert-manager-startupapicheck-fhsng 0/1 ContainerCreating 0 2m29s cert-manager-webhook-69666f4d4c-7wccg 1/1 Running 0 5m6s 4. Configuration Cloudflare Récupération de l\u0026rsquo;IP du LoadBalancer GCP Maintenant que ma gateway est en place, je vais configurer Cloudflare pour pointer mon domaine vers l\u0026rsquo;IP publique de mon LoadBalancer GCP. Je vais créer un enregistrement A dans Cloudflare qui pointe vers l\u0026rsquo;IP du LoadBalancer, et je vais activer le proxy de Cloudflare pour bénéficier de la protection DDoS et du CDN.\nPour cela il faut récupérer l\u0026rsquo;IP du LoadBalancer GCP qui a été créé pour exposer Nginx Gateway Fabric. Vous pouvez le faire avec la commande suivante :\nkubectl get svc -n nginx-gateway # Noter l\u0026#39;EXTERNAL-IP → à mettre dans Cloudflare DNS (enregistrement A) Création d\u0026rsquo;un token API Cloudflare Pour automatiser la configuration de Cloudflare, je vais créer un token API avec les permissions nécessaires pour gérer les enregistrements DNS. Dans Cloudflare → My Profile → API Tokens → Create Token :\nTemplate : Edit zone DNS Permissions : Zone / DNS / Edit Zone : mon domaine (ici valerian-pyckaert.dev)\nkubectl create secret generic cloudflare-api-token \\ --namespace cert-manager \\ --from-literal=api-token=\u0026lt;TOKEN_CF\u0026gt; 5. Configuration de Cert Manager pour Cloudflare Il s\u0026rsquo;agit maintenant d\u0026rsquo;ajouter une ressource ClusterIssuer pour utiliser le token API Cloudflare et gérer les certificats TLS automatiquement.\napiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt spec: acme: email: ton@email.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-key solvers: - dns01: cloudflare: apiTokenSecretRef: name: cloudflare-api-token key: api-token Une fois cette configuration appliquée, Cert Manager pourra automatiquement obtenir et renouveler les certificats TLS pour les domaines gérés via Cloudflare. Pour vérifier le bon fonctionnement :\nkubectl get clusterissuer -A NAME READY AGE letsencrypt-prod True 17h kubectl get certificate -A default wildcard-cert True wildcard-cert 54m 6. Configuration d\u0026rsquo;une HTTPRoute pour exposer un service Maintenant que tout est en place, nous allons créer une ressource HTTPRoute pour exposer un service Kubernetes via Nginx Gateway Fabric et positionner le certificat généré par Cert Manager.\napiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: main-gateway namespace: default annotations: cert-manager.io/cluster-issuer: letsencrypt-prod spec: gatewayClassName: nginx listeners: - name: https hostname: \u0026#34;*.mondomaine.com\u0026#34; port: 443 protocol: HTTPS tls: mode: Terminate certificateRefs: - name: wildcard-cert --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: my-app spec: parentRefs: - name: main-gateway hostnames: - \u0026#34;app.mondomaine.com\u0026#34; rules: - backendRefs: - name: my-service port: 80 --- Résultat Si vous avez suivi chaque étapes vous devriez avoir un résultat similaire à celui-ci :\n❯ kubectl get gateway -A NAMESPACE NAME CLASS ADDRESS PROGRAMMED AGE default main-gateway nginx 34.156.85.195 True 29m ❯ kubectl get httproute -A NAMESPACE NAME HOSTNAMES AGE default nginx-app [\u0026#34;nginx.valerian-pyckaert.dev\u0026#34;] 29m ❯ kubectl get certificate -A NAMESPACE NAME READY SECRET AGE default wildcard-cert True wildcard-cert 21m Votre service devrait être accessible via l\u0026rsquo;URL https://nginx.mondomaine.com et le certificat TLS devrait être valide, en l\u0026rsquo;occurence pour moi c\u0026rsquo;était https://nginx.valerian-pyckaert.dev avec un certificat pour valerian-pyckaert.dev généré par Let\u0026rsquo;s Encrypt.\nPour ce faire j\u0026rsquo;ai enregistré un enregistrement A dans Cloudflare qui pointe vers l\u0026rsquo;IP du LoadBalancer GCP : Obstacles rencontrés La gateway ne peut pas accéder au namespace demo et au service nginx J\u0026rsquo;avais installé ma ressource HTTPRoute dans le namespace default alors que mon service nginx était dans le namespace demo. Par défaut, la gateway n\u0026rsquo;a pas accès aux ressources d\u0026rsquo;autres namespaces, il faut donc lui accorder les permissions nécessaires.\nSoit via un ReferenceGrant :\napiVersion: gateway.networking.k8s.io/v1beta1 kind: ReferenceGrant metadata: name: allow-gateway-to-demo namespace: demo spec: from: - group: gateway.networking.k8s.io kind: HTTPRoute namespace: default to: - group: \u0026#34;\u0026#34; kind: Service Soit installé la ressource HTTPRoute dans le même namespace que le service nginx (ici demo).\nPods en pending Par péché de rapidité j\u0026rsquo;ai volontairement omis de spécifier les ressources CPU et memory de mes workloads, ce qui a pour conséquence que les pods sont restés en pending car le scheduler ne pouvait pas les placer sur un nœud. Il faut donc veiller à toujours spécifier les ressources de vos workloads pour éviter ce genre de problème.\nPour la suite Dans le prochain et dernier article de cette série The Forge je vous parlerai de SOPS et de comment gérer vos secrets de manière sécurisée dans Kubernetes. Nous verrons comment chiffrer vos secrets avec SOPS et les stocker dans Git pour une gestion centralisée et sécurisée de vos secrets.\n","date":"2026-06-03T19:00:00+02:00","image":"/posts/the-forge-gateway/the-forge-gateway.png","permalink":"/posts/the-forge-gateway/","title":"TheForge #4 — Installation de Nginx Gateway Fabric \u0026 Cert manager pour exposer les services sur Kubernetes"},{"content":" Pré-requis Un cluster GKE up and running Connaissance de base de Kubernetes et GitOps ArgoCD installé et configuré Étapes 1. Qu\u0026rsquo;est ce qu\u0026rsquo;on va installer ? Outil Description Prometheus Système de surveillance open-source qui collecte et stocke des métriques sous forme de séries temporelles en interrogeant périodiquement des endpoints HTTP. Kube State Metrics Service Kubernetes qui expose l\u0026rsquo;état des objets du cluster (pods, deployments, nodes…) sous forme de métriques consommables par Prometheus. Grafana Plateforme de visualisation qui permet de créer des dashboards interactifs à partir de sources de données comme Prometheus. Alertmanager Composant de l\u0026rsquo;écosystème Prometheus qui gère le routage, le regroupement et l\u0026rsquo;envoi des alertes vers des canaux de notification (Slack, e-mail, PagerDuty…). Loki Système d\u0026rsquo;agrégation de logs inspiré de Prometheus, conçu pour indexer uniquement les métadonnées des logs afin de réduire les coûts de stockage. Alloy Agent de collecte de données de Grafana Labs, successeur de Grafana Agent, capable de collecter métriques, logs et traces pour les acheminer vers différents backends. 1. Installation de la kube-prometheus-stack (Prometheus + Grafana + Alertmanager) Si vous avez suivi l\u0026rsquo;article précédent, rien de plus simple : il suffit d\u0026rsquo;ajouter une nouvelle application dans apps/ dans notre repo de CD.\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: kube-prometheus-stack namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: project: default source: repoURL: https://prometheus-community.github.io/helm-charts chart: kube-prometheus-stack targetRevision: 86.0.1 helm: releaseName: kube-prometheus-stack valueFiles: [] values: | grafana: enabled: true adminPassword: admin service: type: ClusterIP prometheus: prometheusSpec: retention: 7d resources: requests: cpu: 200m memory: 512Mi limits: memory: 2Gi storageSpec: {} alertmanager: enabled: true kubeStateMetrics: enabled: true nodeExporter: enabled: true destination: server: https://kubernetes.default.svc namespace: monitoring syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true - ServerSideApply=true On push ce fichier kube-prometheus-stack.yaml et Argo se charge de déployer la stack de monitoring dans notre cluster.\nAprès quelques minutes d\u0026rsquo;attente, vous devriez voir les pods de Prometheus, Grafana et Alertmanager en cours d\u0026rsquo;exécution dans le namespace monitoring :\nkubectl get pods -n monitoring NAME READY STATUS RESTARTS AGE alertmanager-kube-prometheus-stack-alertmanager-0 2/2 Running 0 2m30s kube-prometheus-stack-admission-create-ljf79 0/1 Pending 0 41s kube-prometheus-stack-grafana-6ddb85bf55-j5shg 3/3 Running 0 2m35s kube-prometheus-stack-kube-state-metrics-5fc57bf8f9-vhfhq 1/1 Running 0 2m35s kube-prometheus-stack-operator-585c4957f7-2r772 1/1 Running 0 2m35s prometheus-kube-prometheus-stack-prometheus-0 2/2 Running 0 2m30s --- Grafana est désormais accessible via un port-forwarding :\nkubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80 Rendez-vous sur http://localhost:3000 et connectez-vous avec les identifiants admin/admin.\nCliquez ensuite sur \u0026ldquo;Dashboards\u0026rdquo; pour voir la liste des dashboards disponibles par défaut avec la chart : 3. Collecte des Logs avec Loki Là encore il suffit de créer une nouvelle application ArgoCD qui pointe vers le chart Helm de Loki pour déployer un stack de collecte de logs dans notre cluster.\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: loki namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: project: default source: repoURL: https://grafana.github.io/helm-charts chart: loki targetRevision: 7.0.0 helm: releaseName: loki values: | deploymentMode: SingleBinary loki: auth_enabled: false commonConfig: replication_factor: 1 storage: type: filesystem schemaConfig: configs: - from: \u0026#34;2024-01-01\u0026#34; store: tsdb object_store: filesystem schema: v13 index: prefix: index_ period: 24h singleBinary: replicas: 1 persistence: size: 10Gi read: replicas: 0 write: replicas: 0 backend: replicas: 0 gateway: enabled: true chunksCache: enabled: false resultsCache: enabled: false destination: server: https://kubernetes.default.svc namespace: monitoring syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true - ServerSideApply=true Enfin il faut indiquer à Grafana où trouver Loki pour pouvoir visualiser les logs dans notre dashboard.\nPour cela, on ajoute une datasource Loki dans Grafana en utilisant un ConfigMap Kubernetes :\nspec: source: helm: values: | grafana: additionalDataSources: - name: Loki type: loki access: proxy url: http://loki-gateway.monitoring.svc.cluster.local jsonData: maxLines: 1000 Et voilà le résultat :\n4. Collecte des logs de notre application Pour l\u0026rsquo;heure, les logs de notre application nginx ne sont pas collectés par Loki. Nous allons déployer Alloy en tant que DaemonSet dans notre cluster pour collecter les logs de tous les pods et les envoyer à Loki, il s\u0026rsquo;agit ici encore d\u0026rsquo;ajouter une nouvelle application ArgoCD dans notre repo de CD :\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: alloy namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: project: default source: repoURL: https://grafana.github.io/helm-charts chart: alloy targetRevision: 1.16.1 helm: releaseName: alloy values: | alloy: configMap: content: | discovery.kubernetes \u0026#34;pods\u0026#34; { role = \u0026#34;pod\u0026#34; } discovery.relabel \u0026#34;pods\u0026#34; { targets = discovery.kubernetes.pods.targets rule { source_labels = [\u0026#34;__meta_kubernetes_namespace\u0026#34;] target_label = \u0026#34;namespace\u0026#34; } rule { source_labels = [\u0026#34;__meta_kubernetes_pod_name\u0026#34;] target_label = \u0026#34;pod\u0026#34; } rule { source_labels = [\u0026#34;__meta_kubernetes_pod_container_name\u0026#34;] target_label = \u0026#34;container\u0026#34; } rule { source_labels = [\u0026#34;__meta_kubernetes_pod_label_app\u0026#34;] target_label = \u0026#34;app\u0026#34; } } loki.source.kubernetes \u0026#34;pods\u0026#34; { targets = discovery.relabel.pods.output forward_to = [loki.write.default.receiver] } loki.write \u0026#34;default\u0026#34; { endpoint { url = \u0026#34;http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push\u0026#34; } } destination: server: https://kubernetes.default.svc namespace: monitoring syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true La configuration utilise la syntaxe River d\u0026rsquo;Alloy pour :\nDécouvrir tous les pods Kubernetes Extraire les labels namespace, pod, container et app Envoyer les logs vers votre Loki gateway Alloy sera déployé comme DaemonSet et collectera les logs de votre nginx. Dans Grafana, vous pourrez les consulter avec : {namespace=\u0026ldquo;demo\u0026rdquo;, app=\u0026ldquo;nginx\u0026rdquo;}\nDe retour sur Grafana, la liste des filtres disponibles est désormais bien plus fournie : Résultat Dans cet article nous avons vu comment installer une stack de monitoring complète sur Kubernetes avec Prometheus, Grafana et Loki, le tout déployé en GitOps via ArgoCD. Nous avons également vu comment collecter les logs de nos pods sur notre cluster avec Alloy et les visualiser dans Grafana.\nSi vous avez suivi toutes les étapes de cet article vous devriez avoir les workloads suivantes :\nkubectl get pods -n monitoring NAME READY STATUS RESTARTS AGE alertmanager-kube-prometheus-stack-alertmanager-0 2/2 Running 0 21m alloy-jb2nf 2/2 Running 0 5m22s kube-prometheus-stack-grafana-bf74d765f-4hzlt 3/3 Running 0 23m kube-prometheus-stack-kube-state-metrics-6b747b7c5f-hjjwd 1/1 Running 0 23m kube-prometheus-stack-operator-59c5d9f8c-q6phj 1/1 Running 0 23m kube-prometheus-stack-prometheus-node-exporter-zhmfp 1/1 Running 0 23m loki-0 2/2 Running 0 32m loki-canary-55ccd5d799-wkc7t 1/1 Running 0 32m loki-gateway-6df7c96bc9-l92d2 1/1 Running 0 32m prometheus-kube-prometheus-stack-prometheus-0 2/2 Running 0 21m kubectl get pods -n demo NAME READY STATUS RESTARTS AGE nginx-7489fb554f-hffjv 1/1 Running 0 33m L\u0026rsquo;essentiel des travaux est disponible sur mon repo GitHub\nCe qui vient ensuite Nous verrons comment exposer notre application à travers la Gateway API dans le prochain article de la série, stay tuned !\nSources :\nkube-prometheus-stack Helm Chart Loki Helm Chart Alloy Helm Chart ","date":"2026-05-31T09:35:18+02:00","image":"/posts/the-forge-monitoring/the-forge-monitoring.png","permalink":"/posts/the-forge-monitoring/","title":"TheForge #3 — Monitoring Kubernetes avec Prometheus, Grafana et Loki"},{"content":"Ce qu\u0026rsquo;on fait Installation d\u0026rsquo;ArgoCD pour le déploiement continu sur Kubernetes via Terraform et Helm.\nPré-requis Cluster GKE up and running Connaissance de base de Kubernetes et GitOps Outils : terraform, gcloud, kubectl, helm\u0026hellip; Étapes 1. Installation d\u0026rsquo;ArgoCD Pour cette partie j\u0026rsquo;ai choisi d\u0026rsquo;installer ArgoCD via Terraform en utilisant le provider Helm. Cela nous permettra de gérer l\u0026rsquo;installation d\u0026rsquo;ArgoCD de manière déclarative et reproductible. Argo sera ensuite en charge de déployer les workloads sur notre cluster Kubernetes.\nAjout des providers nécessaires dans notre configuration Terraform :\nprovider \u0026#34;kubernetes\u0026#34; { host = \u0026#34;https://${google_container_cluster.primary.endpoint}\u0026#34; token = data.google_client_config.default.access_token cluster_ca_certificate = base64decode(google_container_cluster.primary.master_auth.0.cluster_ca_certificate) } provider \u0026#34;helm\u0026#34; { kubernetes = { host = \u0026#34;https://${google_container_cluster.primary.endpoint}\u0026#34; token = data.google_client_config.default.access_token cluster_ca_certificate = base64decode(google_container_cluster.primary.master_auth.0.cluster_ca_certificate) } } Ne pas oublier terraform init pour installer les providers. Ensuite, nous allons ajouter une ressource Helm pour installer ArgoCD :\nresource \u0026#34;helm_release\u0026#34; \u0026#34;argocd\u0026#34; { name = \u0026#34;argocd\u0026#34; repository = \u0026#34;https://argoproj.github.io/argo-helm\u0026#34; chart = \u0026#34;argo-cd\u0026#34; version = \u0026#34;9.5.15\u0026#34; namespace = \u0026#34;argocd\u0026#34; create_namespace = true set = [ { name = \u0026#34;dex.enabled\u0026#34; value = \u0026#34;false\u0026#34; }, { name = \u0026#34;notifications.enabled\u0026#34; value = \u0026#34;false\u0026#34; }, { name = \u0026#34;applicationSet.enabled\u0026#34; value = \u0026#34;false\u0026#34; }, { name = \u0026#34;server.extraArgs[0]\u0026#34; value = \u0026#34;--insecure\u0026#34; } ] depends_on = [google_container_node_pool.primary_nodes] } Ici nous installons ArgoCD dans le namespace argocd en désactivant certaines fonctionnalités que nous n\u0026rsquo;utiliserons pas pour le moment (Dex, notifications, ApplicationSet) et en configurant le serveur ArgoCD pour qu\u0026rsquo;il fonctionne en mode non sécurisé (insecure) pour simplifier les choses dans un environnement de développement.\n2. Création de la Root Application ArgoCD (App of Apps Pattern) Le pattern \u0026ldquo;App of Apps\u0026rdquo; est une approche recommandée pour organiser les applications dans ArgoCD. Il consiste à créer une application principale (root application) qui référence d\u0026rsquo;autres applications (child applications).\nCela permet de structurer les déploiements de manière hiérarchique et de faciliter la gestion des dépendances entre les applications.\nresource \u0026#34;kubernetes_manifest\u0026#34; \u0026#34;argocd_app\u0026#34; { manifest = { apiVersion = \u0026#34;argoproj.io/v1alpha1\u0026#34; kind = \u0026#34;Application\u0026#34; metadata = { name = \u0026#34;root-app\u0026#34; namespace = \u0026#34;argocd\u0026#34; } spec = { project = \u0026#34;default\u0026#34; source = { repoURL = \u0026#34;https://github.com/Bernedotcom2312/theforge-cd.git\u0026#34; #Changez pour le votre targetRevision = \u0026#34;main\u0026#34; path = \u0026#34;apps\u0026#34; } destination = { server = \u0026#34;https://kubernetes.default.svc\u0026#34; namespace = \u0026#34;default\u0026#34; } syncPolicy = { automated = { prune = true selfHeal = true } } } } depends_on = [helm_release.argocd] } Cette configuration crée une application ArgoCD nommée root-app qui pointe vers un dépôt Git contenant les définitions des applications à déployer. Le champ syncPolicy est configuré pour permettre le déploiement automatique et de réparer les ressources qui ne sont pas conformes à la configuration (selfHeal).\n3. Application de la configuration Terraform Maintenant que nous avons ajouté la configuration pour installer ArgoCD et créer la root application, nous pouvons appliquer notre configuration Terraform :\nterraform apply Cette commande va provisionner ArgoCD sur notre cluster GKE et créer la root application qui référencera les applications à déployer depuis notre dépôt Git.\nRésultat ArgoCD est maintenant installé sur notre cluster GKE et prêt à être utilisé pour le déploiement continu de nos applications.\nVous pouvez vérifier que les pods ArgoCD sont en cours d\u0026rsquo;exécution avec la commande suivante :\nkubectl get pods -n argocd NAME READY STATUS RESTARTS AGE argocd-application-controller-0 1/1 Running 0 94s argocd-applicationset-controller-6c6b88d4bc-krjc5 1/1 Running 0 95s argocd-dex-server-79fc985b7b-rbz6w 1/1 Running 2 (77s ago) 96s argocd-notifications-controller-6c44f9dbbf-v4777 1/1 Running 0 96s argocd-redis-7979d55d5b-ft6rc 1/1 Running 0 96s argocd-repo-server-584c965f87-tzvsg 1/1 Running 0 95s argocd-server-5f7d4885d5-vkccj 1/1 Running 0 95s Accès au dashboard ArgoCD :\nkubectl port-forward svc/argocd-server -n argocd 8080:80 Rendez vous sur http://localhost:8080. Vous devriez avoir un warning de sécurité concernant le mode \u0026ldquo;insecure\u0026rdquo; que nous avons configuré pour le serveur ArgoCD, mais c\u0026rsquo;est acceptable pour un environnement de développement, nous verrons comment sécuriser cela dans une prochaine étape.\nPour se connecter, utilisez les identifiants suivants :\nUsername : admin Password : le mot de passe est généré automatiquement et stocké dans le secrets argocd-initial-admin-secret dans le namespace argocd. Vous pouvez le récupérer avec la commande suivante : kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=\u0026#34;{.data.password}\u0026#34; | base64 -d Notre application d\u0026rsquo;exemple (Nginx) devrait également être déployée et visible dans l\u0026rsquo;interface d\u0026rsquo;ArgoCD.\nkubectl get pods -n demo NAME READY STATUS RESTARTS AGE nginx-78b9f9966-bh7vb 1/1 Running 0 110s Un dernier port-forward pour accéder à l\u0026rsquo;application Nginx :\nkubectl port-forward svc/nginx -n demo 8081:80 Et voilà le résultat en accèdant au port 8081 de votre localhost :\nCe qui vient ensuite La prochaine brique de notre plateforme Kubernetes : le monitoring.\nSources\nArgoCD Documentation Terraform Helm Provider Blog Stéphane Robert Mon Repo Infra Mon Repo CD ","date":"2026-05-28T19:00:00+02:00","image":"/posts/the-forge-cd/the-forge-cd.png","permalink":"/posts/the-forge-cd/","title":"TheForge #2 — Installation d'ArgoCD pour le déploiement continu sur Kubernetes"},{"content":"Pré-requis Un compte GCP avec les permissions nécessaires pour créer des ressources. API Kubernetes Engine activée Billing account configuré Terraform installé sur votre machine Quelques connaissances sur Git Étapes 1. Creation du repository Git Ici pas question de réinventer la roue, j\u0026rsquo;utilise un template fournit par Hashicorp(Terraform) pour le bootstrap de mon cluster.\ngit clone https://github.com/hashicorp-education/learn-terraform-provision-gke-cluster mv learn-terraform-provision-gke-cluster theforge-infra cd theforge-infra git remote set-url origin git@github.com:mon-repo.git Nous ferons le tour d\u0026rsquo;horizon des ressources juste après.\n2. Configuration GCLOUD brew install --cask google-cloud-sdk gcloud init gcloud auth application-default login 3. Architecture du repo Fichier Rôle vpc.tf Déclare le VPC et un subnet pour isoler le cluster, ainsi que les variables project_id / region et le provider Google. gke.tf Crée le cluster GKE (version 1.27.x, default node pool supprimé) et un node pool séparé de 2 nœuds n1-standard-1. versions.tf Fixe le provider Google à la version 7.33.0 et Terraform \u0026gt;= 0.14. outputs.tf Expose la région, le project ID, le nom et l\u0026rsquo;endpoint du cluster. terraform.tfvars Variables d\u0026rsquo;entrée (project_id à remplacer, région us-central1). kubernetes-dashboard-admin.rbac.yaml Manifest RBAC Kubernetes pour un accès admin au dashboard (à appliquer post-déploiement). 4. Fine tuning de la configuration Terraform Ajout du Project ID et de la région Quelques modifications à apporter à la configuration de base, notamment le project_id et la region dans le fichier terraform.tfvars :\nproject_id = \u0026#34;MON_PROJECT_ID\u0026#34; region = \u0026#34;europe-west1\u0026#34; Puis on valide que Terraform est correctement configuré et peut communiquer avec GCP :\nterraform init Terraform has been successfully initialized!\nMise à jour de la version de Terraform et des providers Le template utilise une version de Terraform et du provider Google qui sont un peu anciennes, il est recommandé de les mettre à jour pour bénéficier des dernières fonctionnalités et corrections de bugs. Dans versions.tf, on peut mettre à jour la version de Terraform et du provider Google :\nterraform { required_version = \u0026#34;\u0026gt;= 1.15\u0026#34; required_providers { google = { source = \u0026#34;hashicorp/google\u0026#34; version = \u0026#34;~\u0026gt; 7.33.0\u0026#34; } } } Sources :\nVersion provider Google Version Terraform Mise à jour de la version de GKE Par défaut, le template utilise aussi une version de GKE qui n\u0026rsquo;est plus supportée (1.27.x). Il faut donc mettre à jour la version du cluster dans gke.tf pour une version plus récente, par exemple 1.36.x :\nresource \u0026#34;google_container_cluster\u0026#34; \u0026#34;primary\u0026#34; { name = \u0026#34;theforge-cluster\u0026#34; location = var.region # Autres configurations... # Mise à jour de la version du cluster min_master_version = \u0026#34;1.36\u0026#34; } Ensuite, on peut relancer terraform plan pour vérifier que la configuration est valide avec la nouvelle version.\nSources :\nVersions GKE disponibles Séparation des ressources dans des fichiers Pour une meilleure organisation, j\u0026rsquo;ai choisi de remanier quelque peu la structure du repo en séparant les ressources dans des fichiers dédiés (variables.tf, providers.tf). J\u0026rsquo;envisage en effet de réutiliser ce repository à l\u0026rsquo;avenir pour d\u0026rsquo;autres projets, à ce titre je souhaite le rendre plus modulaire et facilement maintenable.\nRésultat Une fois satisfait de votre configuration il ne reste qu\u0026rsquo;à lancer la commande terraform apply pour provisionner le cluster sur GCP. Après quelques minutes d\u0026rsquo;attente, le cluster devrait être opérationnel et vous devriez voir les outputs suivants :\nConnexion au cluster Google Cloud SDK (gcloud) est nécessaire pour récupérer les credentials du cluster et pouvoir interagir avec lui via kubectl.\ngcloud container clusters get-credentials \u0026#34;moncluster\u0026#34; --region europe-west1 --project \u0026#34;monprojet\u0026#34; #\u0026gt; kubeconfig entry generated for moncluster. Verification du status des pods Une fois le cluster déployé vous devriez voir les pods du cluster en cours d\u0026rsquo;exécution :\nkubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE gke-managed-cim kube-state-metrics-0 2/2 Running 0 35m gmp-system collector-jc4t8 2/2 Running 0 30m gmp-system collector-jdjzw 2/2 Running 0 30m Repo disponible sur GitHub L\u0026rsquo;ensemble de la configuration Terraform utilisée pour ce projet est disponible sur GitHub : theforge-infra.\nErreurs rencontrées Pas de billing account configuré ERROR: (gcloud.services.enable) FAILED_PRECONDITION: Billing account for project \u0026lsquo;X\u0026rsquo; is not found\nRenseignez un billing account valide pour votre projet et c\u0026rsquo;est réglé.\nAPI Kubernetes désactivée Error: Error retrieving available container cluster versions: googleapi: Error 403: Kubernetes Engine API has not been used in project deft-accord-496812-k9 before or it is disabled.\nElle était assez explicite : l\u0026rsquo;API Kubernetes Engine n\u0026rsquo;était pas activée sur le projet GCP. Je l\u0026rsquo;ai activée via la console GCP, puis relancé terraform plan pour valider que tout est bon. Vous pouvez aussi l\u0026rsquo;activer avec la CLI :\ngcloud services enable container.googleapis.com Problème de provisioning du cluster Google Compute Engine: Not all instances running in IGM after 35m11.660099086s. Expected 1, running 0, transitioning 1. Current errors: [GCE_STOCKOUT]: Instance \u0026lsquo;gke-deft-accord-496812-k-default-pool-19ca1c15-p6ls\u0026rsquo; creation failed: The zone \u0026lsquo;projects/deft-accord-496812-k9/zones/europe-west1-c\u0026rsquo; does not have enough resources available to fulfill the request. Try a different zone, or try again later.\nRésolution : changer la zone de déploiement du cluster dans gke.tfs pour une zone moins sollicitée, par exemple europe-west1-b ou europe-west1-d.\nresource \u0026#34;google_container_cluster\u0026#34; \u0026#34;primary\u0026#34; { name = \u0026#34;${var.project_id}-gke\u0026#34; location = var.region node_locations = [\u0026#34;europe-west1-b\u0026#34;, \u0026#34;europe-west1-d\u0026#34;] # Autres configurations... } Plugin GKE introuvable couldn\u0026rsquo;t get current server API group list: Get \u0026ldquo;https://35.233.30.210/api?timeout=32s\u0026rdquo;: getting credentials: exec: executable gke-gcloud-auth-plugin.exe not found\nSolution : installer le plugin d\u0026rsquo;authentification GKE pour kubectl, qui est nécessaire pour se connecter au cluster GKE.\ngcloud components install gke-gcloud-auth-plugin ","date":"2026-05-27T13:00:00+02:00","image":"/posts/the-forge-cluster/the-forge-cluster.png","permalink":"/posts/the-forge-cluster/","title":"TheForge #1 — Création d'un cluster Kubernetes sur GCP avec Terraform"},{"content":" Stack : GCP · GKE · Terraform · Helm · ArgoCD · SOPS · Kube-Prom-Stack · Nginx Gateway Fabric · cert-manager\nPourquoi ce projet ? Je me lance en freelance DevOps / Platform Engineer / Cloud Engineer. Pour montrer ce que je sais faire (et documenter ce que j\u0026rsquo;apprends en chemin), je démarre un projet fil rouge public : construire une plateforme Kubernetes complète sur GCP, from scratch, avec les outils que j\u0026rsquo;estime indispensables en 2026.\nChaque article de la série documente une étape, les obstacles rencontrés et comment je les ai résolus. Format court, orienté pratique.\nJe vais tenter de garder une approche pragmatique orientée \u0026ldquo;best practices\u0026rdquo; (mais pas dogmatique) pour que ce soit utile à d\u0026rsquo;autres personnes qui veulent se lancer dans la même aventure.\nCe qu\u0026rsquo;on va construire Un cluster GKE avec la stack suivante, déployée en GitOps :\nComposant Rôle Terraform Provisionning du cluster et bootstrap Helm + ArgoCD GitOps, tout passe par là après le bootstrap SOPS Gestion des secrets chiffrés dans Git kube-prometheus-stack Monitoring \u0026amp; alerting (Prometheus, Grafana, Alertmanager) Nginx Gateway Fabric Ingress via la Gateway API cert-manager + Let\u0026rsquo;s Encrypt TLS automatique Je me focalise sur ce qui me semble essentiel pour adresser la majorité des besoins. A termes j\u0026rsquo;envisage d\u0026rsquo;ajouter d\u0026rsquo;autres composants destinés à des usages plus complexes : KEDA, Karpenter, Dapr, \u0026hellip;\nLes étapes prévues Création du cluster GKE via Terraform Bootstrap Helm \u0026amp; ArgoCD via Terraform Déploiement de kube-prometheus-stack via ArgoCD Déploiement de Nginx Gateway Fabric / Gateway API via ArgoCD Déploiement de cert-manager + Let\u0026rsquo;s Encrypt via ArgoCD Mise en place de SOPS pour la gestion des secrets J\u0026rsquo;ajouterai probablement d\u0026rsquo;autres étapes au fur et à mesure* (ex: gestion des node pools, autoscaling, etc.) selon les besoins et les obstacles rencontrés*\nPrincipes de base Tout dans Git — aucune modification manuelle sur le cluster après le bootstrap Least privilege — les SA GCP auront le minimum requis Les secrets ne sont jamais en clair dans le repo KISS — Keep It Simple Stupid. Pas de sur-optimisation, pas de microservices inutiles, pas de complexité superflue Prochaine étape → Création du cluster GKE avec Terraform (à venir)\n","date":"2026-05-22T08:00:00+02:00","image":"/posts/the-forge-project/theforge.png","permalink":"/posts/the-forge-project/","title":"TheForge : construire une plateforme Kubernetes complète sur GCP"},{"content":"Qu\u0026rsquo;est-ce que c\u0026rsquo;est ? Renovate est un outil d\u0026rsquo;automatisation des mises à jour de dépendances. Il permet de garder les dépendances à jour en créant automatiquement des pull requests pour les nouvelles versions, les correctifs de sécurité et les mises à jour majeures.\nExemple de PR avec Renovate\nComment ça fonctionne ? Renovate fonctionne en scannant à intervalles réguliers les dépôts Git pour détecter les mises à jour de dépendances. Lorsqu\u0026rsquo;une mise à jour est disponible, il crée automatiquement une pull request avec les changements nécessaires, y compris un changelog et des notes de version.\nEnsuite, la pull request peut être automatiquement fusionnée si les contrôles de CI sont verts, ou elle peut nécessiter une revue humaine avant d\u0026rsquo;être fusionnée.\nParfois une image vaut milles mots, alors voici une illustration du workflow de Renovate :\nflowchart TD A([Planificateur cron]) --\u003e B[Scan du dépôt Git / Lecture des fichiers de config] B --\u003e C[Détection des mises à jour] C --\u003e D{Mises à jour disponibles ?} D -- Non --\u003e E[Aucune PR] D -- Oui --\u003e F[Création de la PR Branche + changelog] F --\u003e G[CI / Tests automatiques] G --\u003e H[Automerge] G -.ou.- I[Review humaine]Ce schéma a été généré avec Mermaid. J\u0026rsquo;en ferai certainement un article dédié dans le futur.\nUn changement de paradigme Dans le monde du développement logiciel, la gestion des dépendances est un défi constant. Les bibliothèques et les frameworks évoluent rapidement, et il est crucial de maintenir les dépendances à jour pour bénéficier des dernières fonctionnalités, des correctifs de sécurité et des améliorations de performance.\nAutrefois (et encore aujourd\u0026rsquo;hui dans de nombreux projets), la mise à jour des dépendances n\u0026rsquo;est pas maitrisée et se fait de manière manuelle, souvent de manière ad-hoc. Cela peut entraîner des retards dans l\u0026rsquo;adoption des nouvelles versions, des risques de sécurité et une dette technique croissante.\nRenovate change complètement la donne en automatisant ce processus. Plutôt que de dépendre d\u0026rsquo;une intervention humaine pour surveiller les mises à jour, Renovate prend en charge cette tâche de manière proactive. Il scanne régulièrement les dépôts, détecte les mises à jour disponibles et crée automatiquement des pull requests pour les intégrer.\nSupply chain attacks Ces derniers mois, nous avons vu une augmentation significative des attaques de la chaîne d\u0026rsquo;approvisionnement (supply chain attacks). Ces attaques ciblent les dépendances utilisées par les projets, en injectant du code malveillant dans des librairies souvent méconnues mais utilisées par tous.\nEn voici quelques unes :\nXZ Utils Axios Github Actions La fin des wildcards pour les versions L\u0026rsquo;un des avantages (ou inconvénients) majeurs de Renovate est qu\u0026rsquo;il encourage une gestion plus rigoureuse des versions. Plutôt que d\u0026rsquo;utiliser des wildcards (comme ^1.0.0 ou ~1.0.0) qui peuvent introduire des changements inattendus, Renovate propose de spécifier des versions exactes pour les dépendances.\nCela permet de mieux contrôler les mises à jour et de réduire les risques liés à l\u0026rsquo;introduction de changements non désirés.\nLes concurrents Dependabot Dependabot est un outil similaire à Renovate qui propose également une automatisation des mises à jour de dépendances. Cependant, il est principalement intégré à GitHub et ne supporte pas autant de plateformes que Renovate. De plus, Renovate offre une plus grande flexibilité en termes de configuration et de personnalisation des règles de mise à jour.\nC\u0026rsquo;est toutefois un excellent choix pour les projets hébergés sur GitHub qui souhaitent une solution simple et intégrée, sans avoir à maintenir soit même un outil supplémentaire.\nExemple d\u0026rsquo;une pull request générée par Dependabot\nVous remarquerez que le résultat est très similaire à celui de Renovate.\nLe mot de la fin Dans cet article, nous avons vu comment Renovate révolutionne la gestion des dépendances en automatisant les mises à jour et en encourageant une approche plus rigoureuse du versioning. C\u0026rsquo;est un maillon supplémentaire dans une chaîne de CI/CD qui permet à la fois de réduire les risques liés à l\u0026rsquo;introduction de changements non désirés et de réduire la friction liée à la maintenance des dépendances.\n","date":"2026-05-15T10:16:01+02:00","image":"/posts/renovate/renovate.png","permalink":"/posts/renovate/","title":"Renovate : automatiser la mise à jour de vos dépendances"},{"content":"Cocogitto est un outil de gestion de commit qui facilite la rédaction de messages de commit conformes à la convention Conventional Commit. Il propose notamment une CLI pour rédiger des messages de commit structurés et standardisés, automatiser certaines tâches liées aux commits (génération de changelog, bump de versions \u0026hellip;).\nSi vous ne savez pas ce qu\u0026rsquo;est la convention Conventional Commit, je vous invite à lire le premier article de cette série à ce sujet : Conventional Commit.\nInstallation Mac/Linux :\nbrew install cocogitto Windows :\nchoco install cocogitto Vérification de l\u0026rsquo;installation :\ncog --version cog 7.0.0\nUtilisation Pour committer Cocogitto propose via sa CLI la possibilité de rédiger des messages de commit conformes à la convention Conventional Commit de manière interactive.\nLa commit cog commit remplace alors le bon vieux git commit :\ncog commit feat \u0026#34;ma nouvelle fonctionnalite\u0026#34; Initialisation d\u0026rsquo;un projet Tout commence avec la commande cog init qui va créer un fichier de configuration cog.toml à la racine du projet.\ncog init Ce fichier de configuration permet de personnaliser le comportement de Cocogitto, notamment les types de commit autorisés, les scopes, les préfixes, etc.\nIgnorer certains type du changelog La propriété omit_from_changelog permet d\u0026rsquo;ignorer certains types de commit dans le changelog généré par Cocogitto.\nVoici un exemple de configuration pour ignorer les types chore, ci et perf dans le changelog.\n[commit_types] chore = { changelog_title = \u0026#34;\u0026#34;, omit_from_changelog = true } ci = { changelog_title = \u0026#34;\u0026#34;, omit_from_changelog = true } perf = { changelog_title = \u0026#34;\u0026#34;, omit_from_changelog = true } Git hooks Il est également possible d\u0026rsquo;utiliser Cocogitto pour gérer les git hooks, notamment le hook commit-msg qui permet de vérifier que les messages de commit respectent la convention Conventional Commit avant de les valider.\nExemple :\n[git_hooks.commit-msg] script = \u0026#34;\u0026#34;\u0026#34;#!/bin/sh set -e cog verify --file $1 cog check Une fois votre hook configuré, il suffit de l\u0026rsquo;installer dans votre projet pour qu\u0026rsquo;il soit pris en compte par Git.\ncog install-hook --all Generation du changelog Cocogitto permet de générer un changelog à partir des messages de commit conformes à la convention Conventional Commit. Admettons que nous avons les commits suivants dans notre projet :\nfdeaa9d feat: Initial commit\nIl suffit alors de lancer la commande suivante pour générer un changelog à partir de ces commits :\ncog changelog ## Unreleased (fdeaa9d..1ba231e) #### Features - Initial commit - (fdeaa9d) - Bernedotcom2312 Depuis un tag spécifique :\ncog changelog --at v1.0.0 Entre deux versions :\ncog changelog v1.0.0..v2.0.0 Il est également possible de personnaliser le format du changelog généré en utilisant un template personnalisé.\nExemple :\n[changelog] path = \u0026#34;CHANGELOG.md\u0026#34; template = \u0026#34;remote\u0026#34; # template prédéfini : \u0026#34;default\u0026#34;, \u0026#34;remote\u0026#34;, \u0026#34;full_hash\u0026#34; remote = \u0026#34;github.com\u0026#34; repository = \u0026#34;mon-repo\u0026#34; owner = \u0026#34;mon-pseudo\u0026#34; Bump de version Ceci est pour moi la feature principale de cocogitto qui permet de faire du versionning sémantique (SemVer) à partir de messages de commit.\nVoici comment Cocogitto calcule la prochaine version à partir des messages de commit :\nflowchart TD A[Calcul de la prochaine version] --\u003e B[Exécution des pre-bump hooks] B --\u003e C[Ajout des changements dans CHANGELOG.md] C --\u003e D[Création d'un commit de version] D --\u003e E[Création d'un tag git sur le commit de version] E --\u003e F[Exécution des post-bump hooks]La commande permettant de faire un bump de version est la suivante :\ncog bump [version]\nVersion pouvant avoir pour valeur :\n--auto: calcul automatique de la prochaine version à partir des messages de commit. --major: incremente la version majeure. --minor: incremente la version mineure. --patch: incremente la version de correctif. --version \u0026lt;version\u0026gt;: définit la version manuellement ( ex: cog bump \u0026ndash;version 3.2.1). --pre \u0026lt;metadata\u0026gt;: définit ou incrémente la version pré-release. --build \u0026lt;metadata\u0026gt;: définit les métadonnées de build. Par défaut seuls les commits de type fix, feat et BREAKING CHANGE sont pris en compte pour le calcul de la prochaine version.\nEn reprenant mon exemple de commit précédent, si je lance la commande cog bump --auto alors la prochaine version calculée sera la suivante :\nv0.1.0\nOn vérifie ?\ncog bump --auto Found feature commit fdeaa9d Bumped version: ... -\u0026gt; 0.1.0 Le commit alors généré par Cocogitto pour le bump de version est le suivant :\n1ba231e chore(version): 0.1.0\nLe mot de la fin Dans cet article nous avons vu comment installer et utiliser Cocogitto pour gérer les messages de commit, générer les changelogs et les numéros de version de manière automatisée à partir de messages de commit conformes à la convention Conventional Commit. Dans le prochain article nous verrons comment intégrer Cocogitto avec Renovate dans une chaine de CI/CD.\nSources\nCocogitto Documentation ","date":"2026-05-14T08:00:00+02:00","image":"/posts/cocogitto/cocogitto.png","permalink":"/posts/cocogitto/","title":"Cocogitto : automatiser vos releases avec les Conventional Commits"},{"content":"Je démarre ici une nouvelle série d\u0026rsquo;articles sur les bonnes pratiques de développement, en commençant par une convention de commit que j\u0026rsquo;utilise au quotidien.\nIntroduction Conventional Commit est une convention pour formater les messages de commit de manière structurée et standardisée. Elle permet de faciliter la lecture et la compréhension des messages de commit, ainsi que d\u0026rsquo;automatiser certaines tâches comme la génération de changelogs ou la gestion des versions.\nExemple de commit suivant la convention Conventional Commit\nfeat: add new feature X fix: correct bug Y Dans cet exemple, feat indique qu\u0026rsquo;il s\u0026rsquo;agit d\u0026rsquo;une nouvelle fonctionnalité, tandis que fix indique qu\u0026rsquo;il s\u0026rsquo;agit d\u0026rsquo;une correction de bug.\nPourquoi choisir Conventional Commit ? Conventional est l\u0026rsquo;une si ce n\u0026rsquo;est la seule convention de commit largement utilisée dans la communauté open source et dans l\u0026rsquo;industrie. Elle est supportée par de nombreux outils et intégrations, ce qui en fait un choix populaire pour les projets de toutes tailles.\nQuelques exemples d\u0026rsquo;acteurs majeurs qui utilisent Conventional Commit :\nAngular Node.js En pratique Les types de commit possibles Voici quelques types standard que j\u0026rsquo;utilise au quotidien dans mes projets (trier par ordre d\u0026rsquo;utilisation) :\nfeat : ajout d\u0026rsquo;une fonctionnalité fix : correction de bug chore : tâches diverses (ex : MAJ de dépendances) docs : documentation uniquement refactor : modification du code sans ajout de fonctionnalité ni correction de bug test : ajout ou modification de tests Le scope Il est également possible d\u0026rsquo;ajouter un scope pour préciser la partie du code concernée par le commit.\nPar exemple : feat(auth): add login functionality\nDans cet exemple, auth est le scope qui indique que le commit concerne la partie d\u0026rsquo;authentification du projet.\nBonnes pratiques Utiliser des verbes à l\u0026rsquo;infinitif (ex : \u0026ldquo;add\u0026rdquo;, \u0026ldquo;fix\u0026rdquo;, \u0026ldquo;update\u0026rdquo;, etc.) Ne pas dépasser 72 caractères pour la ligne de titre du commit Ajouter une description plus détaillée dans le corps du commit si nécessaire Utiliser des préfixes pour indiquer les types de commit (ex : \u0026ldquo;feat:\u0026rdquo;, \u0026ldquo;fix:\u0026rdquo;, etc.) Un pas vers l\u0026rsquo;automatisation Les conventional commits sont la première étape vers une chaîne de CI/CD plus automatisée. En utilisant des messages de commit structurés, il devient possible d\u0026rsquo;automatiser la génération de changelogs, la gestion des versions et même le déploiement en production.\nWorkflow CI/CD avec Conventional Commit flowchart TD A[Développement] --\u003e B[Conventional Commit] B --\u003e C[CI/CD] C --\u003e D[Tests automatiques] D --\u003e E{Tests réussis ?} E -- Oui --\u003e F[Génération Changelog + Versioning] E -- Non --\u003e H[Correction du code et nouveau commit] F --\u003e I{Déploiement ?} I -- Version mineur ou patch --\u003e J[Déploiement automatique] I -- Version majeure --\u003e K[Déploiement manuel]Stratégie de versioning En utilisant les types de commit, il est possible d\u0026rsquo;adopter une stratégie de versioning sémantique (SemVer).\nVoici un exemple de politique de versioning basée sur les types de commit :\nUn commit de type feat peut déclencher une nouvelle version mineure (ex : 1.0.0 -\u0026gt; 1.1.0) Un commit de type fix peut déclencher une nouvelle version patch (ex : 1.0.0 -\u0026gt; 1.0.1) Un commit de type refactor ou chore peut ne pas déclencher de changement de version, ou déclencher une nouvelle version patch selon la politique adoptée. Pour réaliser ce type d\u0026rsquo;automatisation, il existe de nombreux outils comme semantic-release ou encore Cocogitto que j\u0026rsquo;apprécie beaucoup qui peuvent analyser les messages de commit et gérer automatiquement la génération de changelogs et le versioning.\nSources\nConventional Commit Blog Stéphane Robert ","date":"2026-05-08T08:00:00+02:00","image":"/posts/conventional-commit/conventional-commit.png","permalink":"/posts/conventional-commit/","title":"Conventional Commits : structurer ses messages (et automatiser le reste)"},{"content":"Si vous ne savez pas ce qu\u0026rsquo;est Dependency Track, je vous invite à lire mon article d\u0026rsquo;introduction à Dependency Track ici.\nConfiguration Les éléments à déployer sont très simples :\nUn namespace pour isoler les ressources de Dependency Track Un HelmRepository pour indiquer à FluxCD où trouver les charts de Dependency Track Un HelmRelease pour déployer Dependency Track avec les valeurs de configuration souhaitées dependency-track.yaml\napiVersion: v1 kind: Namespace metadata: name: dependency-track --- apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: name: dependency-track namespace: dependency-track spec: interval: 1h url: https://dependencytrack.github.io/helm-charts --- apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: dependency-track namespace: dependency-track spec: interval: 1h timeout: 15m chart: spec: chart: dependency-track version: \u0026#34;0.44.0\u0026#34; sourceRef: kind: HelmRepository name: dependency-track namespace: dependency-track values: frontend: image: tag: \u0026#34;4.14.1\u0026#34; apiServer: image: tag: \u0026#34;4.14.1\u0026#34; persistentVolume: enabled: true size: 20Gi resources: requests: cpu: \u0026#34;500m\u0026#34; memory: 1Gi limits: memory: 5Gi On applique la configuration avec :\nkubectl apply -f dependency-track.yaml Verification de l\u0026rsquo;installation On contrôle que l\u0026rsquo;installation s\u0026rsquo;est bien passée avec :\nkubectl get deployment -n dependency-track # dependency-track-frontend 1/1 1 1 47h kubectl get statefulset -n dependency-track # dependency-track-api-server 1/1 47h Accès au service Puis on lance le port forward pour accéder au service :\nkubectl port-forward -n dependency-track svc/dependency-track-api-server 8080:8080 # Dans un autre terminal kubectl port-forward -n dependency-track svc/dependency-track-frontend 8081:8080 Je vous laisse constater le résultat (login : admin / password : admin).\nProblèmes rencontrés Manque de droits pour FluxCD\nMon FluxCD n\u0026rsquo;avait pas les droits nécessaires pour créer des ressources dans le namespace dependency-track ce qui a conduit à des erreurs lors du déploiement. J\u0026rsquo;ai du spécifier les rbac suivantes :\nrbac.yaml\napiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: flux-pv-manager-dependency-track roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: flux-pv-manager subjects: - kind: ServiceAccount name: flux-applier namespace: dependency-track --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: helm-release-manager namespace: dependency-track rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;secrets\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;configmaps\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;services\u0026#34;, \u0026#34;serviceaccounts\u0026#34;, \u0026#34;pods\u0026#34;, \u0026#34;persistentvolumeclaims\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] - apiGroups: [\u0026#34;apps\u0026#34;] resources: [\u0026#34;deployments\u0026#34;, \u0026#34;statefulsets\u0026#34;, \u0026#34;replicasets\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] - apiGroups: [\u0026#34;networking.k8s.io\u0026#34;] resources: [\u0026#34;ingresses\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] - apiGroups: [\u0026#34;rbac.authorization.k8s.io\u0026#34;] resources: [\u0026#34;roles\u0026#34;, \u0026#34;rolebindings\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: flux-helm-release-manager namespace: dependency-track roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: helm-release-manager subjects: - kind: ServiceAccount name: flux-applier namespace: dependency-track Manque de ressource J\u0026rsquo;ai toujours pour réflexe lorsque je déploie une outil de jeter un oeil aux ressources nécessaires pour le faire tourner. La chart officielle de Dependency Track réclame 2 CPU et 5Gi de RAM pour l\u0026rsquo;api server ce qui me semblait énorme pour mon petit cluster.\nJ\u0026rsquo;ai donc commencé par réduire les ressources demandées à 500m de CPU et 1Gi de RAM pour les tests, mais cela a conduit à des lenteurs extrêmes et des timeouts dans les logs de l\u0026rsquo;api server.\nLa solution, laisser la configuration RAM par défaut (5Go requested), 500m de cpu suffise par contre pour un petit cluster de test.\nresources: requests: cpu: \u0026#34;500m\u0026#34; Le mot de la fin Pour aller plus loin vous pouvez configurer un ingress(déprécié) ou httpRoute pour accéder à l\u0026rsquo;interface de Dependency Track sans passer par un port forward, ou encore configurer une base de données externe pour la persistance des données.\n","date":"2026-05-07T08:00:00+02:00","image":"/posts/dependency-track-flux/dependency-track.png","permalink":"/posts/dependency-track-flux/","title":"Dependency Track - Déploiement en GitOps avec FluxCD"},{"content":"Si vous ne savez pas ce qu\u0026rsquo;est Dependency Track, je vous invite à lire mon article d\u0026rsquo;introduction à Dependency Track ici.\nPrerequis Un cluster Kubernetes fonctionnel (Cloud (AWS, GCP, Azure) ou local (Kind, Minikube, K3s) Helm installé et configuré Une instance ArgoCD pour le déploiement en GitOps Pattern App of Apps Le pattern App of Apps consiste à créer une application principale (appelée \u0026ldquo;App of Apps\u0026rdquo;) qui référence d\u0026rsquo;autres applications (appelées \u0026ldquo;child apps\u0026rdquo;). Cela permet de regrouper plusieurs applications sous une même hiérarchie et de les gérer de manière centralisée.\nArchitecture d\u0026rsquo;un repo GitOps avec App of Apps ├── apps/ │ ├── monapp.yaml (Ici dependency-track dans notre cas) ├── root-app.yaml/ Dans cet exemple, root-app.yaml est l\u0026rsquo;application principale (App of Apps) qui référence monapp.yaml (child app) qui contient la configuration pour déployer Dependency Track.\nSi je souhaite déployer d\u0026rsquo;autres applications je n\u0026rsquo;ai qu\u0026rsquo;à créer une nouvelle ressource Application dans le dossier apps/.\nConfiguration Mon fichier root-app.yaml :\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: root-app namespace: argocd spec: project: default source: repoURL: https://github.com/votre-user/votre-repo targetRevision: main path: apps destination: server: https://kubernetes.default.svc namespace: argocd syncPolicy: automated: prune: true selfHeal: true Mon dependency-track.yaml (apps/dependency-track.yaml) :\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: dependency-track namespace: argocd spec: project: default source: repoURL: https://github.com/votre-user/votre-repo targetRevision: main path: dependency-track helm: valueFiles: - values.yaml destination: server: https://kubernetes.default.svc namespace: dependency-track syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true - ServerSideApply=true On applique la configuration avec (seule étape manuelle):\nkubectl apply -f root-app.yaml A ce stade vous devriez voir la chart se déployer dans ArgoCD et après quelques minutes l\u0026rsquo;application devrait être en état \u0026ldquo;Healthy\u0026rdquo; et \u0026ldquo;Synced\u0026rdquo;.\nPour y accéder, vous pouvez faire un port forward sur le service frontend ou api server comme vu dans la section FluxCD.\nkubectl port-forward -n dependency-track svc/dependency-track-api-server 8080:8080 # Dans un autre terminal kubectl port-forward -n dependency-track svc/dependency-track-frontend 8081:8080 Et voilà le résultat (login : admin / password : admin). ","date":"2026-05-06T08:00:00+02:00","image":"/posts/dependency-track-argo/dependency-track.png","permalink":"/posts/dependency-track-argo/","title":"Dependency Track - Déploiement en GitOps avec ArgoCD"},{"content":"Voici la première partie d\u0026rsquo;une série d\u0026rsquo;articles dédiés à Dependency Track et à son déploiement en mode GitOps avec ArgoCD ou FluxCD sur un cluster Kubernetes.\nQu\u0026rsquo;est ce que c\u0026rsquo;est ? C\u0026rsquo;est une plateforme qui permet de suivre les composants logiciels utilisés dans nos applications, d\u0026rsquo;identifier les vulnérabilités associées et de gérer les risques liés à ces dépendances.\nC\u0026rsquo;est un outil particulièrement utile pour répondre aux exigences de sécurité et de conformité de nos systèmes comme le Cyber Resilience Act (CRA) en Europe ou l\u0026rsquo;IEC 81001-5-1 pour les dispositifs médicaux.\nComment ça fonctionne ? flowchart LR subgraph Sources[\"Sources SBOM\"] CI[CI/CD Pipeline] CLI[cdxgen / syft] end subgraph DT[\"Dependency-Track\"] API[API Server] ANA[Analyzers] DB[(Base de données\\nH2 / PostgreSQL)] API --\u003e|stocke| DB API --\u003e|déclenche| ANA end subgraph Vuln[\"Bases de vulnérabilités\"] NVD[NVD] OSV[OSV] GHSA[GitHub Advisory] OSS[OSS Index] end subgraph Alerting[\"Alerting\"] NOTIF[Notifications] POLICY[Policy Violations] end CI --\u003e|POST /api/v1/bom| API CLI --\u003e|POST /api/v1/bom| API ANA \u003c--\u003e|sync CVEs| NVD ANA \u003c--\u003e|sync CVEs| OSV ANA \u003c--\u003e|sync CVEs| GHSA ANA \u003c--\u003e|sync CVEs| OSS ANA --\u003e|vulnérabilités détectées| NOTIF ANA --\u003e|violation de politique| POLICYDependency Track ingère des SBOM et analyse en continu les composants logiciels pour détecter les vulnérabilités connues. Il s\u0026rsquo;appuie sur des bases de données de vulnérabilités telles que le NVD (National Vulnerability Database) pour fournir des informations à jour sur les risques liés aux composants logiciels.\nLa SBOM Une SBOM (Software Bill of Materials) est un fichier qui liste tous les composants logiciels utilisés dans un projet, y compris leurs versions et leurs dépendances. C\u0026rsquo;est un peu comme une liste d\u0026rsquo;ingrédients pour une recette de cuisine, mais pour les logiciels.\nJ\u0026rsquo;y ai dédié un article complet que vous pouvez consulter ici.\nPourquoi c\u0026rsquo;est important ? Dans un monde où les applications sont de plus en plus complexes et dépendent de nombreux composants tiers, il est crucial de pouvoir identifier rapidement les vulnérabilités potentielles pour protéger nos systèmes et nos données. Le contexte géopolitique actuel accentue encore plus l\u0026rsquo;importance de la sécurité des logiciels, avec des attaques de plus en plus sophistiquées comme plus récemment celles sur la supply chain.\nQuelques sources sur le sujet :\nTrivy supply chain attack: https://www.paloaltonetworks.com/blog/cloud-security/trivy-supply-chain-attack/ Checkmarx security update: https://checkmarx.com/blog/checkmarx-security-update/ Axios NPM package compromised: https://www.trendmicro.com/fr_fr/research/26/c/axios-npm-package-compromised.html Conclusion Maintenant que vous savez ce qu\u0026rsquo;est une SBOM, Dependency track et l\u0026rsquo;importance de ce type d\u0026rsquo;outil nous verrons dans les prochains articles comment déployer Dependency Track en mode GitOps sur Kubernetes avec ArgoCD ou FluxCD, et comment l\u0026rsquo;intégrer dans une pipeline CI/CD pour automatiser la gestion des vulnérabilités de vos applications.\n","date":"2026-05-05T08:00:00+02:00","image":"/posts/dependency-track/dependency-track.png","permalink":"/posts/dependency-track/","title":"Dependency Track - Monitorer les vulnérabilités de vos applications"},{"content":"Préambule Si vous ne savez pas ce qu\u0026rsquo;est une SBOM (Software Bill of Materials), je vous invite à lire mon article sur SBOM.\nGrype - Qu\u0026rsquo;est-ce que c\u0026rsquo;est ? Dans l\u0026rsquo;article précédant je vous ai présenté Syft pour générer ces fameuses SBOM, Grype est un outil complémentaire et développé par la même équipe, qui permet de scanner ces SBOM à la recherche de vulnérabilités connues. Il est notamment utile dans une chaine de CI/CD pour automatiser la détection de vulnérabilités dans les dépendances utilisées par votre projet.\nEn Local Installation Là encore les étapes ressemblent beaucoup à l\u0026rsquo;installation de Syft.\nSur Linux ou macOS, vous pouvez installer Syft en utilisant Homebrew :\nbrew tap anchore/grype brew install grype ou via curl :\ncurl -sSfL https://get.anchore.io/grype | sudo sh -s -- -b /usr/local/bin Sur Windows j\u0026rsquo;aime utiliser Scoop :\nscoop bucket add main scoop install grype Un petit coup de grype version pour vérifier que tout est bien installé et c\u0026rsquo;est parti !\nApplication: grype Version: 0.111.1 BuildDate: 2026-04-22T17:16:07Z GitCommit: 89819276e8ebf63b4f607ca3a1b0faa3fc75f28d GitDescription: v0.111.1 Platform: windows/amd64 GoVersion: go1.26.2 Compiler: gc Syft Version: v1.43.0 Supported DB Schema: 6 Utilisation Le cas simple à partir d\u0026rsquo;une image docker :\nLe résultat :\ngrype alpine:latest ✔ Vulnerability DB [updated] ✔ Parsed image sha256:3cb067eab609612d81b4d82ff8ad71d73482bb3059a87b642d7e14f0ed659cde ✔ Cataloged contents 4d889c14e7d5a73929ab00be2ef8ff22437e7cbc545931e52554a7b00e123d8b ├── ✔ Packages [16 packages] ├── ✔ File metadata [79 locations] ├── ✔ Executables [17 executables] └── ✔ File digests [79 files] ✔ Scanned for vulnerabilities [3 vulnerability matches] ├── by severity: 0 critical, 0 high, 3 medium, 0 low, 0 negligible └── by status: 0 fixed, 3 not-fixed, 0 ignored NAME INSTALLED TYPE VULNERABILITY SEVERITY EPSS RISK busybox 1.37.0-r30 apk CVE-2025-60876 Medium \u0026lt; 0.1% (15th) \u0026lt; 0.1 busybox-binsh 1.37.0-r30 apk CVE-2025-60876 Medium \u0026lt; 0.1% (15th) \u0026lt; 0.1 ssl_client 1.37.0-r30 apk CVE-2025-60876 Medium \u0026lt; 0.1% (15th) \u0026lt; 0.1 Et pour une analyse encore plus rapide, vous pouvez directement scanner une SBOM générée par Syft :\nsyft alpine:latest -o cyclonedx-json \u0026gt; sbom.json # génère une SBOM pour l\u0026#39;image alpine:latest grype sbom:./sbom.json # scanne la SBOM à la recherche de vulnérabilités² En CI (GitHub Actions) Savoir détecté les vulnérabilités en local c\u0026rsquo;est bien mais automatiser leurs détection dans une pipeline CI/CD c\u0026rsquo;est encore mieux ! On applique ainsi les bonnes pratiques DevOps : détecter tôt les problèmes lors du cycle de développement, corriger vite et avant déploiement en production.\nVoici un exemple de workflow GitHub Actions qui utilise Grype pour scanner une image Docker à la recherche de vulnérabilités et échoue si une vulnérabilité critique est détectée.\nname: Build, SBOM and Grype scan on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: jobs: build-scan: name: Build image, generate SBOM and scan with Grype runs-on: ubuntu-latest permissions: contents: read env: IMAGE_NAME: demo-node-app IMAGE_TAG: ${{ github.sha }} steps: - name: Checkout repository uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Build Node.js Docker image (load locally) uses: docker/build-push-action@v7 with: context: . file: ./Dockerfile push: false load: true tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} - name: Generate SBOM with Syft (SPDX JSON) uses: anchore/sbom-action@v0 with: image: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} format: spdx-json output-file: sbom.spdx.json upload-artifact: false - name: Upload SBOM artifact uses: actions/upload-artifact@v4 with: name: sbom path: sbom.spdx.json if-no-files-found: error - name: Scan SBOM with Grype (fail on critical) id: grype uses: anchore/scan-action@v7 with: sbom: sbom.spdx.json fail-build: true severity-cutoff: critical output-format: sarif output-file: grype-report.sarif - name: Upload Grype report artifact if: always() uses: actions/upload-artifact@v7 with: name: grype-report path: grype-report.sarif if-no-files-found: error Vous pouvez retrouver un exemple fonctionnel sur mon GitHub.\nIl est également possible d\u0026rsquo;arriver au même résultat en jouant les commandes présentées ci dessous mais Anchore propose des actions GitHub pré-construites pour faciliter l\u0026rsquo;intégration de Syft et Grype dans vos workflows CI/CD. Encore une fois autant privilégier la facilité et éviter de réinventer la roue.\nConclusion Grype est un outil à la fois simple et puissant pour analyser vos SBOM à la recherche de vulnérabilités. Il s\u0026rsquo;intègre facilement en CI et peut même être configuré en tant que commit hook pour scanner vos images ou votre code avant même de pousser vos changements, réduisant ainsi la boucle de feedback.\nSources\nGrype GitHub Repository Grype Documentation Anchore GitHub Actions ","date":"2026-05-01T08:00:00+02:00","image":"/posts/grype/grype.png","permalink":"/posts/grype/","title":"Grype : scanner les vulnérabilités de vos images depuis un SBOM"},{"content":"Qu\u0026rsquo;est-ce que c\u0026rsquo;est ? Une SBOM (Software Bill of Materials) est un fichier qui liste tous les composants logiciels utilisés dans un projet, y compris leurs versions et leurs dépendances. C\u0026rsquo;est un peu comme une liste d\u0026rsquo;ingrédients pour une recette de cuisine, mais pour les logiciels.\nElles peuvent exister sous plusieurs formats notamment pour les plus connus : CycloneDX, SPDX.\nLes différents formats CycloneDX CycloneDX est un format de SBOM conçu pour être facile à lire et à analyser. Il utilise une structure hiérarchique pour représenter les composants logiciels et leurs dépendances, ce qui facilite la compréhension des relations entre les différents éléments d\u0026rsquo;un projet. Il est notamment développé par la fondation OWASP et est largement utilisé dans l\u0026rsquo;industrie pour la gestion des vulnérabilités et la conformité.\nSPDX SPDX (Software Package Data Exchange) quant à lui est un format développé par la Linux Foundation. Il est conçu pour être un standard ouvert et largement adopté pour la représentation des informations sur les composants logiciels, y compris les licences, les vulnérabilités et les dépendances.\nExemple d\u0026rsquo;une SBOM au format SPDX :\nLequel choisir ? Le choix entre SPDX et CycloneDX dépend de vos besoins spécifiques en matière de gestion des composants logiciels. Si vous recherchez un format facile à lire et à analyser, CycloneDX peut être un bon choix. Si vous avez besoin d\u0026rsquo;un format plus complet qui inclut des informations sur les licences et les vulnérabilités, SPDX pourrait être plus adapté.\nVoici un tableau récapitulatif pour vous aider à choisir :\nCaractéristique SPDX CycloneDX Facilité de lecture Moins lisible pour les humains ❌ Plus lisible ✅ Analyse automatisée Plus adapté pour l\u0026rsquo;analyse Adapté pour l\u0026rsquo;analyse Adoption Large adoption dans l\u0026rsquo;industrie Adoption croissante Support des licences Oui ✅ Non ❌ Support des vulnérabilités Oui ✅ Non ❌ Comment la générer ? Un de mes outils préférés pour générer des SBOM est Syft, développé par Anchore. Syft est un outil en ligne de commande qui peut analyser les images de conteneurs, les systèmes de fichiers et les projets de code source pour générer des SBOM au format CycloneDX ou SPDX.\nIl supporte une multitude de langages et de gestionnaires de paquets.\nLes autres standards du marché sont cdxgen et trivy, ce dernier ayant été impacté par une faille de sécurité récemment, je préfère ne pas l\u0026rsquo;utiliser pour le moment (cf. l\u0026rsquo;excellent article de Stéphane Robert à ce sujet).\nInstallation Sur Linux ou macOS, vous pouvez installer Syft en utilisant Homebrew :\nbrew install anchore/syft/syft ou via curl :\ncurl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin Sur Windows j\u0026rsquo;aime utiliser Scoop :\nscoop bucket add main scoop install syft Un petit coup de syft version pour vérifier que tout est bien installé et c\u0026rsquo;est parti !\nsyft version Application: syft Version: 1.38.0 BuildDate: 2025-11-17T17:42:49Z GitCommit: a033ae525f6c7ef937c6f49513e3403f07a1d6c0 GitDescription: v1.38.0 Platform: windows/amd64 GoVersion: go1.25.4 Compiler: gc SchemaVersion: 16.1.0 Utilisation Voici comment générer une SBOM pour une image Docker\nLe plus simple pour commencer est de s\u0026#39;appuyer sur une image docker standard comme alpine. ```bash syft alpine:latest -o cyclonedx-json \u0026gt; sbom.json Ici la commande pull alpine:latest et génère une SBOM au format CycloneDX en JSON, qui est ensuite redirigée vers un fichier sbom.json.\nVoici comment générer une SBOM pour un projet de code source\nsyft dir:/path/to/your/project -o cyclonedx-json \u0026gt; sbom.json ou pour cibler sur un fichier en particulier :\nsyft file:/path/to/your/project/package.json -o cyclonedx-json \u0026gt; sbom.json # pour un projet Node.js syft file:/path/to/your/project/pyproject.toml -o cyclonedx-json \u0026gt; sbom.json # pour un projet Python Conclusion La SBOM est un outil essentiel pour la gestion des composants logiciels et la sécurité des applications. En fournissant une vue d\u0026rsquo;ensemble claire de tous les composants utilisés dans un projet, elle permet aux équipes de développement et de sécurité de mieux comprendre les risques associés à leurs logiciels et de prendre des mesures proactives pour les atténuer.\nCette SBOM peut ensuite être intégrée dans votre pipeline CI/CD et analysée avec des outils comme Dependency Track pour détecter les vulnérabilités connues et assurer la conformité de vos logiciels.\nSources\nSBOM - OWASP SBOM - NIST SBOM - SPDX Syft - Anchore ","date":"2026-04-30T10:29:03+02:00","image":"/posts/sbom/sbom.png","permalink":"/posts/sbom/","title":"SBOM : savoir ce qui tourne vraiment dans vos images"},{"content":"Qu\u0026rsquo;est-ce que c\u0026rsquo;est ? Hugo est un générateur de site statique open-source écrit en Go. Il permet de créer des sites web rapides et performants sans avoir besoin d\u0026rsquo;une base de données ou d\u0026rsquo;un serveur dynamique. Hugo est particulièrement apprécié pour sa simplicité d\u0026rsquo;utilisation, sa flexibilité et sa rapidité de génération.\nPourquoi choisir Hugo ? J\u0026rsquo;applique un principe fondamental dès qu\u0026rsquo;il s\u0026rsquo;agit de choisir un outil : KISS (Keep It Simple, Stupid). Hugo s\u0026rsquo;appuie sur du markdown pour la rédaction de contenu, c\u0026rsquo;est un format que j\u0026rsquo;apprécie déjà pour la documentation technique. Les commandes pour build, déployer ou ajouter du contenu pré-formaté sont d\u0026rsquo;une simplicité enfantine, ce qui me permet de me concentrer sur la création de contenu plutôt que sur la configuration de l\u0026rsquo;outil.\nEnfin, Hugo est largement supporté par la communauté et peut être déployé partout facilement : Github Pages, Netlify, AWS/GCP/Azure, etc.\nPour ma part j\u0026rsquo;ai choisi de m\u0026rsquo;appuyer sur Cloudflare Pages pour héberger mon site.\nConvaincus ? Alors passons à la pratique !\nCréer son premier site Pré-requis :\nGo \u0026amp; Git installés sur votre machine Je ne m\u0026rsquo;attarderai pas sur l\u0026rsquo;installation de Go et Git, il existe de nombreux tutoriels en ligne pour cela. Assurez-vous simplement d\u0026rsquo;avoir les dernières versions installées.\nInstaller Hugo go install github.com/gohugoio/hugo@latest hugo version Vous devriez obtenir quelque chose de la sorte :\nhugo v0.160.1-d6bc8165e62b29d7d70ede01ed01d0f88de327e6+extended windows/amd64 BuildDate=2026-04-08T14:02:42Z VendorInfo=gohugoio\nCréer un nouveau projet hugo new site mon-site cd mon-site Architecture Voici l\u0026rsquo;architecture de base d\u0026rsquo;un projet Hugo :\nmon-site/ ├── archetypes/ # modèles de contenu ├── content/ # fichiers de contenu (articles, pages, etc.) ├── data/ # données structurées (YAML, JSON, etc.) ├── layouts/ # templates pour le rendu ├── static/ # fichiers statiques (images, CSS, JS) ├── themes/ # thèmes pour le design ├── config.toml/.yaml # configuration du site └── ... # autres fichiers et dossiers Lancer le serveur de développement hugo server Félicitations, votre site est désormais accessible via http://localhost:1313\nConfiguration Git Initions un nouveau dépôt Git et poussons notre projet sur GitHub (ou toute autre plateforme de votre choix) :\ngit init git remote add origin https://github.com/\u0026lt;your-gh-username\u0026gt;/\u0026lt;repository-name\u0026gt; git branch -M main git push -u origin main Personnalisation Ajouter un thème Il existe une multitude de thèmes disponibles sur Hugo Themes. Pour en ajouter un à votre projet, suivez les instructions spécifiques à chaque thème.\nEn ce qui me concerne j\u0026rsquo;ai choisi le thème PaperMod, que j\u0026rsquo;ai personnalisé pour répondre à mes besoins.\nVoyons ensemble comment l\u0026rsquo;installer et le configurer\ncd mon-site git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod git submodule update --init --recursive # needed when you reclone your repo (submodules may not get cloned automatically) NB: Bien que je ne sois pas un grand fan de Git Submodules, c\u0026rsquo;est la méthode recommandée par les auteurs du thème pour l\u0026rsquo;installation et la mise à jour.\nDernière étape, il faut indiquer à Hugo d\u0026rsquo;utiliser ce thème en modifiant le fichier config.toml :\ntheme: PaperMod C\u0026rsquo;est terminé, un petit coup de hugo server et vous devriez voir votre nouveau thème en action !\nArchetypes Il est également possible de créer des templates de contenu pour faciliter la création de nouvelles pages ou articles. Par exemple, pour créer un template d\u0026rsquo;article de blog, créez un fichier blog.md dans le dossier archetypes avec le contenu suivant :\n--- date: \u0026#39;{{ .Date }}\u0026#39; draft: true title: \u0026#39;{{ replace .File.ContentBaseName \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#39; --- Maintenant, lorsque vous souhaitez créer un nouvel article de blog, il vous suffit d\u0026rsquo;exécuter la commande suivante :\nhugo new posts/mon-nouvel-article.md -k blog On déploie ? Ce n\u0026rsquo;est pas tout de faire tourner son projet en local, encore faut-il le rendre accessible au monde entier !\nJ\u0026rsquo;ai déjà parlé brièvement de l\u0026rsquo;hébergement sur différentes plateformes plus tôt dans l\u0026rsquo;article. Pour ma part j\u0026rsquo;ai choisi de m\u0026rsquo;appuyer sur Cloudflare Pages, qui offre une intégration transparente avec GitHub et une configuration très simple.\nJ\u0026rsquo;ai quelques projets d\u0026rsquo;articles autour de Cloudflare en tête donc autant commencer à m\u0026rsquo;y familiariser dès maintenant !\nHébergement Là encore c\u0026rsquo;est très simple et je trouve la documentation officielle d\u0026rsquo;Hugo très bien faite à ce sujet : Hugo Deployment.\nSi vous suivez scrupuleusement les étapes votre site devrait être en ligne en quelques minutes seulement !\nSources Hugo Documentation Hugo Themes PaperMod Documentation Hugo Deployment ","date":"2026-04-29T11:30:03Z","image":"/posts/hugo/hugo.svg","permalink":"/posts/hugo/","title":"Créer un site statique avec Hugo"}]