
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.