
Ce chapitre interconnecte les 3 Nets créés au Chapitre 1 en mesh complet : A↔B, A↔C, B↔C. Trois peerings, acceptés explicitement, et 18 routes inter-Net (3 tiers × 3 Nets × 2 Nets distants). Le piège central est OUTSCALE-spécifique : sur ce cloud, les peerings intra-compte ne sont pas auto-acceptés, il faut une ressource outscale_net_peering_acceptation séparée, sans quoi les routes pointant vers le peering tombent en blackhole. Public visé : intermédiaire à avancé, avec le Chapitre 2 déployé. À la sortie de ce chapitre, toute VM de Net-A peut joindre toute VM de Net-B et Net-C, et réciproquement.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Coder les 3 peerings full-mesh en Terraform avec
for_eachsur des couples ordonnés. - Gérer l'acceptation explicite des peerings via
outscale_net_peering_acceptation(piège OUTSCALE n°1 du capstone). - Produire les 18 routes inter-Net sans copier-coller, en aplatissant
Nets × Tiers × Distantsen une seule map. - Consommer le state du stack
00-netsviadata "terraform_remote_state". - Diagnostiquer une route en
blackhole(le symptôme qu'on rencontre 1 fois sur 2 quand on porte un module AWS).
Prérequis
Section intitulée « Prérequis »- Le Chapitre 1, 3 Nets /22 appliqué sur le compte cible (48 ressources actives).
- Backend OOS opérationnel, variables
AWS_*_CHECKSUM_*exportées (cf. Chapitre 1). - Familiarité avec
for_eachsur unemap(object)et avec les fonctionsmerge,flatten,sort.
Le piège n°1, l'acceptation explicite des peerings
Section intitulée « Le piège n°1, l'acceptation explicite des peerings »C'est la spécificité OUTSCALE à connaître avant de toucher au code. Sur AWS, un peering VPC créé dans le même compte est auto-accepté ; on déclare une seule ressource aws_vpc_peering_connection et c'est terminé. Sur OUTSCALE, le peering naît en état pending-acceptance, même si la source et l'accepter appartiennent au même compte. Tant qu'il reste dans cet état :
- Aucun trafic ne traverse.
- Toute
outscale_routequi le référence apparaît en étatblackholecôté OUTSCALE, la route existe mais le paquet est jeté. terraform applyréussit sans erreur, le piège est silencieux.
Le fix est une ressource Terraform de plus :
resource "outscale_net_peering" "mesh" { for_each = local.peering_pairs source_net_id = local.nets[each.value.source].net_id accepter_net_id = local.nets[each.value.accepter].net_id # tags...}
resource "outscale_net_peering_acceptation" "mesh" { for_each = local.peering_pairs net_peering_id = outscale_net_peering.mesh[each.key].net_peering_id}La ressource outscale_net_peering_acceptation fait passer le peering en état active. Sans elle, c'est blackhole garanti, j'ai vu cette erreur arriver sur tous les portages depuis AWS, sans exception.
Pourquoi un mesh complet et pas un hub-and-spoke
Section intitulée « Pourquoi un mesh complet et pas un hub-and-spoke »Trois topologies sont envisageables pour interconnecter trois Nets :
| Topologie | Peerings | Pour | Contre |
|---|---|---|---|
| Linéaire A↔B, B↔C | 2 | Coût peering minimal | A→C transite par B (interdit, le peering OUTSCALE n'est pas transitif) |
| Hub-and-spoke A↔B, A↔C | 2 | Centralisation des règles via Net-A | Net-A devient SPOF logique ; même limite de transit |
| Full-mesh A↔B, A↔C, B↔C | 3 | Pas de SPOF, latence directe entre n'importe quels Nets | 1 peering de plus à payer + à gérer |
Le piège des deux premières topologies est la non-transitivité du peering OUTSCALE (et AWS, c'est partagé). Un paquet de Net-A à destination de Net-C ne peut pas rebondir via Net-B même si A↔B et B↔C existent. Sans peering direct A↔C, il faut un NAT applicatif (proxy, load balancer interne) sur le Net pivot, ce qui ajoute une latence et un point de défaillance.
Le full-mesh ferme cette discussion. Sur N Nets, ça fait N×(N-1)/2 peerings, 3 pour N=3, 6 pour N=4, 10 pour N=5. Au-delà de 5-6 Nets, on bascule vers une Transit Gateway (que OUTSCALE n'expose pas en service managé à ce jour, cf. roadmap). Pour le capstone à 3 Nets, le full-mesh reste l'option idéale.
La paramétrisation, couples ordonnés pour l'idempotence
Section intitulée « La paramétrisation, couples ordonnés pour l'idempotence »Trois Nets a, b, c produisent trois couples non orientés : {a,b}, {a,c}, {b,c}. L'écueil est de les coder par accident en orienté (a-b, b-a), Terraform créerait alors deux peerings au lieu d'un. La discipline est de trier lexicographiquement chaque couple :
locals { peering_pairs = { "a-b" = { source = "a", accepter = "b" } "a-c" = { source = "a", accepter = "c" } "b-c" = { source = "b", accepter = "c" } }}La clé <source>-<accepter> reste stable pour la durée de vie du stack. Choisir qui est source et qui est accepter n'a pas d'incidence fonctionnelle (le trafic est bidirectionnel une fois le peering actif), c'est juste un choix de convention figée pour Terraform.
Les 18 routes inter-Net, un seul for_each pour tout générer
Section intitulée « Les 18 routes inter-Net, un seul for_each pour tout générer »Chaque tier (public, private, data) de chaque Net doit pouvoir joindre les CIDR des deux autres Nets. Soit 3 tiers × 3 Nets × 2 Nets distants = 18 routes. À la main, c'est 18 blocs outscale_route quasi identiques, donc une bombe à retardement de cohérence. Le bon pattern est un double for_each aplati :
locals { # Pour chaque Net : la liste des CIDR distants à router via leur peering. remote_routes = { for net_key, net in local.nets : net_key => [ for other_key, other in local.nets : { cidr = other.cidr peering_key = "${sort([net_key, other_key])[0]}-${sort([net_key, other_key])[1]}" } if other_key != net_key ] }
# Aplatir Nets × Tiers × Distants en une seule map indexée. inter_net_routes = merge([ for net_key, routes in local.remote_routes : { for entry in flatten([ for r in routes : [ for tier in ["public", "private", "data"] : { key = "${net_key}_${tier}_to_${r.peering_key}" net_key = net_key tier = tier cidr = r.cidr peering_key = r.peering_key } ] ]) : entry.key => entry } ]...)}
resource "outscale_route" "inter_net" { for_each = local.inter_net_routes
route_table_id = local.route_tables[each.value.net_key][each.value.tier] destination_ip_range = each.value.cidr net_peering_id = outscale_net_peering.mesh[each.value.peering_key].net_peering_id}Lisons l'expression. remote_routes["a"] produit la liste des deux Nets distants vus de A : [{cidr="10.11.0.0/22", peering_key="a-b"}, {cidr="10.12.0.0/22", peering_key="a-c"}]. Le inter_net_routes croise ces couples avec les 3 tiers de chaque Net pour produire 18 entrées dont la clé est lisible : a_private_to_a-b, b_data_to_b-c…
La consommation du state du stack 00-nets
Section intitulée « La consommation du state du stack 00-nets »Le stack 05-peering/ ne possède aucune ressource outscale_net ni outscale_route_table, il les emprunte au stack 00-nets/ via le state distant :
data "terraform_remote_state" "nets" { backend = "s3" config = { bucket = "capstone-outscale-tfstate" key = "00-nets/terraform.tfstate" region = "eu-west-2" endpoints = { s3 = "https://oos.eu-west-2.outscale.com" } skip_credentials_validation = true skip_region_validation = true skip_requesting_account_id = true skip_metadata_api_check = true skip_s3_checksum = true use_path_style = true }}
locals { nets = data.terraform_remote_state.nets.outputs.nets route_tables = data.terraform_remote_state.nets.outputs.route_tables}Trois bénéfices à découper en stacks :
- Cycle de vie indépendant. Vous pouvez
terraform destroyle stack05-peering(~10 secondes) sans toucher aux Nets, pratique pour tester un re-câblage. - Permissions plus fines. Le compte EIM qui pilote
05-peeringn'a pas besoin deoutscale_net:Create, justeoutscale_net_peering:*etoutscale_route:*. - Surface de blast réduite. Une erreur dans
05-peeringne peut pas casser les Nets eux-mêmes.
Le prix à payer : vous devez terraform apply les stacks dans l'ordre (00-nets puis 05-peering) et tenir un contrat d'output stable côté 00-nets. C'est un pattern standard de pipeline IaC, il vaut largement le découpage.
Étapes, apply pas à pas
Section intitulée « Étapes, apply pas à pas »-
Vérifier que le stack
00-netsest bienapplied.Fenêtre de terminal cd terraform/00-nets/terraform output -json | jq '.nets.value | keys'# → ["a", "b", "c"]Si le state est vide, retournez au Chapitre 1.
-
Initialiser le stack peering.
Fenêtre de terminal cd ../05-peering/terraform initLe
inittélécharge le provider 1.5.0 (déjà en cache local depuis le stack précédent) et configure le backend. -
Lire le plan, vous devez voir 24 ressources à créer :
- 3
outscale_net_peering(un par couple) - 3
outscale_net_peering_acceptation(un par peering) - 18
outscale_route(3 tiers × 3 Nets × 2 Nets distants)
Fenêtre de terminal terraform planPlan: 24 to add, 0 to change, 0 to destroy.
- 3
-
Appliquer.
Fenêtre de terminal terraform applyCompter ~30 secondes. La création est très rapide, pas d'IGW ni de NAT à provisionner ici.
-
Vérifier que les 3 peerings sont en
active(pas enpending-acceptance).Fenêtre de terminal oapi-cli ReadNetPeerings --Filters.Tags '["project=capstone"]' \| jq '.NetPeerings[] | {tag: (.Tags[] | select(.Key=="Name") | .Value), state: .State.Name}'Doit afficher
state: "active"× 3. Si vous voyezpending-acceptance, l'acceptation Terraform a échoué, relire la section piège n°1. -
Vérifier que les routes ne sont PAS en blackhole.
Fenêtre de terminal oapi-cli ReadRouteTables --Filters.Tags '["project=capstone"]' \| jq '.RouteTables[].Routes[] | select(.NetPeeringId != null) | {dest: .DestinationIpRange, peering: .NetPeeringId, state: .State}'Toutes les entrées doivent avoir
state: "active". Si une seule estblackhole, c'est qu'un peering est resté enpending-acceptance.
Validation fonctionnelle, un ping inter-Net
Section intitulée « Validation fonctionnelle, un ping inter-Net »Le seul test qui ne ment pas est de pinger une VM dans Net-B depuis une VM dans Net-A. Vous ferez ce test au Chapitre 4 quand le bastion sera déployé. À ce stade, on se contente de la validation par API, peering active + routes active côté OUTSCALE = trafic théoriquement OK. La validation fonctionnelle viendra avec la première VM joignable.
Pourquoi for_each et pas 3 modules
Section intitulée « Pourquoi for_each et pas 3 modules »Une variante consisterait à instancier 3 fois un module Terraform peering paramétré par (source, accepter). Le code serait plus court mais le tradeoff penche en faveur du for_each direct :
- Lisibilité du plan, un
terraform planmontre 3 ressourcesoutscale_net_peering.mesh["a-b"], lisibles d'un coup. Avec un module, c'estmodule.peering_ab.outscale_net_peering.this× 3, plus verbeux. - Refactor moins coûteux, passer de 3 à 4 Nets ajoute une ligne dans
peering_pairs, contre 3 invocations de module supplémentaires. - Pas de plus-value modulaire, la logique tient en 30 lignes ; un module ajouterait une indirection sans encapsuler de complexité réelle.
La règle générale : for_each pour 2-10 instances, module quand on dépasse une centaine de lignes ou qu'on veut publier la logique en réutilisable. Ici, on est très loin du seuil.
Pièges courants
Section intitulée « Pièges courants »| Symptôme | Cause | Solution |
|---|---|---|
Routes en blackhole côté API | Peering en pending-acceptance | S'assurer que outscale_net_peering_acceptation est créée pour chaque peering |
Error: argument min/max requires number au plan | Comparaison de strings avec min()/max() | Remplacer par sort([a, b])[0] et sort([a, b])[1] |
| Peerings doublés (6 au lieu de 3) | Couples non triés (a-b et b-a) | Trier lexicographiquement la clé du couple |
Error: data.terraform_remote_state.nets — Output not found | 00-nets non appliqué ou clé d'output renommée | Re-apply le stack 00-nets, vérifier les noms d'outputs |
Error: trailing checksum is not supported au lecteur de state | Variables AWS_*_CHECKSUM_* non exportées | direnv allow à la racine du dépôt |
| Trafic OK A→B mais pas B→A | Route manquante côté Net-B | Vérifier que inter_net_routes produit bien 18 entrées (6 par Net) |
À retenir
Section intitulée « À retenir »- Sur OUTSCALE, l'acceptation est explicite, même intra-compte, la ressource
outscale_net_peering_acceptationest mandatory. - 3 Nets en mesh = 3 peerings + 18 routes (3 tiers × 3 Nets × 2 distants), tout généré par un
for_eachaplati. - Le peering n'est pas transitif, un mesh complet est requis dès qu'il faut plus que de l'étoile.
- HCL ne compare pas les strings avec
</>/min()/max(), le pattern idiomatique estsort([a, b])[0]. - Le state distant (
data.terraform_remote_state) est le contrat propre pour empiler des stacks IaC sans tout fusionner dans un mégastack. - Une route en
blackholeest silencieuse côté Terraform, la vérification se fait côté API OUTSCALE après l'apply.