Votre mission
FlashShop prépare une opération commerciale. L’application de démonstration tourne sur une seule machine, et personne ne sait ce qui se passera si elle tombe pendant la vente. On vous demande une plateforme de laboratoire avec plusieurs instances, des machines privées et une capacité qui s’ajuste. Le paiement et le catalogue métier ne font pas partie de votre mission.
« La page doit rester accessible quand une machine tombe. Et je veux comprendre quand la plateforme ajoute de la capacité, combien de temps ça prend et jusqu’où elle peut aller. »
Ce dont le produit a besoin
- Servir une page légère depuis plusieurs instances, derrière un point d’entrée unique.
- Garder les VM sans IP publique, tout en leur permettant les sorties dont elles ont besoin.
- Retirer du trafic une instance défaillante et retrouver une capacité saine.
- Mesurer comment la capacité réagit à une charge bornée.
Le périmètre du laboratoire
Vous réalisez le VPC, les VM privées, l’Instance Template, un MIG régional, le Load Balancer, les health checks, l’autoscaling et Cloud NAT. Pour Cloud Armor, une règle simple si vous en avez le temps, sinon une conception argumentée.
Le formateur vous donnera les volumes métier quand vous les lui demanderez. Ils servent à raisonner, pas à être reproduits : les essais de charge du laboratoire ont leurs propres limites, fixées à l’étape 5.
L’application sert seulement à faire passer des requêtes ou des messages. Votre travail porte sur Terraform, le réseau, IAM, le pipeline, le monitoring et la reprise. Personne ne vous demande une application métier complète, et elle ne sera pas évaluée. Le kit de démarrage est là pour vous éviter de l’écrire.
Ce qui compte en premier
Le parcours suit six étapes, et le temps passe vite. Le socle, c’est ce que vous devez pouvoir montrer quoi qu’il arrive. La version complète vient ensuite, si le temps le permet. Un socle solide et bien expliqué vaut mieux qu’une version complète vérifiée à moitié. À chaque étape, notez en quelques lignes ce que vous avez décidé et ce que vous avez mesuré.
| Étape | Socle | Version complète |
|---|---|---|
| 01 Cadrer | Une fiche d’hypothèses et trois critères de réussite validés par le formateur. | Les volumes obtenus, une limite de coût et ce que vous écartez, écrits noir sur blanc. |
| 02 Architecture | Le schéma cible expliqué flux par flux, avec votre découpage Terraform. | Trois choix d’infrastructure argumentés, avec les alternatives écartées. |
| 03 Construire | Le chemin nominal déployé par Terraform, avec un état distant et des identités dédiées : des réponses de deux instances privées derrière le Load Balancer. | Des modules documentés, un plan sans dérive et un test négatif, c’est-à-dire un refus ou une erreur attendus. |
| 04 Automatiser | Une CI qui s’authentifie par fédération d’identité et lance fmt, validate et plan à chaque changement. | L’apply du plan relu après approbation, un test du parcours nominal et une destruction approuvée. |
| 05 Éprouver | Un dashboard, une alerte, un essai de charge borné et une analyse argumentée de la nouvelle contrainte. | Deux alertes dont la notification a bien été reçue, une expérience menée jusqu’au retour au service, et un essai borné sur la nouvelle contrainte. |
| 06 Présenter | 20 minutes sur ce qui fonctionne vraiment, un ordre de grandeur des coûts et le nettoyage des ressources. | Un calcul de coûts détaillé et les écarts avec une cible de production. |
Avant de commencer
Avant de créer la moindre ressource, faites le point avec le formateur :
- Le projet GCP du laboratoire. La facturation doit être active, les API et les quotas disponibles, et vous devez pouvoir créer les ressources et déléguer les permissions nécessaires.
- La région, le préfixe de votre groupe, les labels à poser et le plafond de dépense. Une alerte de budget prévient, elle ne coupe rien.
- Vos outils : Terraform, Git et gcloud. Si votre sujet utilise des conteneurs, il vous faut aussi de quoi construire une image et un registre pour la stocker.
- Un dépôt Git pour le groupe et une plateforme de CI. En local, vous travaillez avec les identifiants prévus par le cours. En CI, vous préparerez une identité fédérée aux droits limités.
- Un endroit où ranger vos preuves : schémas, captures, résultats horodatés et décisions, sans aucun secret. L’un exécute, l’autre relit, le troisième mesure, et vous changez de rôle régulièrement.
Votre jeu de démonstration
Une page ou une route HTTP qui renvoie l’identifiant de l’instance et la version de l’application. Le démarrage doit être automatique et reproductible. Aucune boutique ni transaction à coder. Le script de démarrage du kit ci-dessous fait exactement cela.
Vous savez dans quel projet vous travaillez, ce que vous avez le droit de créer et comment tout arrêter puis supprimer. S’il vous manque un accès, demandez-le plutôt que de le contourner avec des permissions trop larges.
Kit de démarrage
Ce kit vous évite d’écrire l’application. Il contient le code à déployer, avec une interface qui montre ce que fait votre infrastructure, et un docker compose qui imite les services GCP pour tout faire tourner sur votre machine avant le premier déploiement. Il ne contient pas de Terraform : l’écrire reste le cœur du TP.
Vous pouvez le modifier, mais il ne sera pas évalué. Lisez-le avant de l’utiliser, car vous devrez pouvoir l’expliquer comme n’importe quel code que vous n’avez pas écrit vous-même.
Un script de démarrage pour les VM du MIG. Il installe, sans rien télécharger, une page qui montre quelle instance répond derrière le Load Balancer, et un petit générateur de charge borné à 20 requêtes par seconde. La version affichée vient de la métadonnée app-version du template. En local, trois conteneurs construits depuis ce même script jouent les VM de trois zones, derrière un nginx qui joue le Load Balancer.
Télécharger le kit, archive zipVoir la démo en ligne
| Rôle | En local | Sur GCP |
|---|---|---|
| Point d’entrée | nginx, local/lb.conf | Application Load Balancer et backend service |
| Instances | trois conteneurs, un par zone | VM privées du MIG régional |
| Retrait d’une instance arrêtée | le DNS de Docker ne la renvoie plus | health check du backend service |
| Remplacement et ajout de capacité | non imités | autohealing et autoscaler du MIG |
Pour l’essayer : décompressez l’archive, puis lancez docker compose up --build dans le dossier flashshop et ouvrez http://localhost:8080. Le README de l’archive explique comment passer sur GCP.
vm/startup.sh, le script de démarrage
#!/bin/bash
# Script de démarrage FlashShop pour une image Debian 12.
# À passer au template avec l'argument metadata_startup_script et la fonction file() de Terraform.
# Il installe une page HTTP sur le port 8080 avec le Python déjà présent sur l'image :
# aucun téléchargement n'est nécessaire. Il peut être rejoué sans risque à chaque démarrage.
#
# Routes : / page qui montre quelle instance répond, avec un petit générateur de charge
# /api/instance identité de l'instance qui a répondu (JSON)
# /api/travail même chose après un calcul de ms millisecondes, pour charger le CPU
# /api/deploiement ce que l'instance constate de son propre déploiement
# /healthz pour les health checks du Load Balancer et de l'autohealing
set -euo pipefail
cat > /opt/flashshop.py <<'PY'
import ipaddress
import itertools
import json
import os
import socket
import threading
import time
import urllib.parse
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
def metadata(path, default):
request = urllib.request.Request(
"http://metadata.google.internal/computeMetadata/v1/" + path,
headers={"Metadata-Flavor": "Google"},
)
try:
with urllib.request.urlopen(request, timeout=2) as response:
return response.read().decode()
except Exception:
return default
# Sur Compute Engine, tout vient du serveur de métadonnées. En local, des variables d'environnement
# prennent le relais. La version vient de la métadonnée app-version du template.
SUR_GCE = not os.environ.get("INSTANCE_NAME") and metadata("instance/id", None) is not None
INSTANCE = os.environ.get("INSTANCE_NAME") or metadata("instance/name", socket.gethostname())
ZONE = (os.environ.get("ZONE") or metadata("instance/zone", "inconnue")).rsplit("/", 1)[-1]
VERSION = os.environ.get("APP_VERSION") or metadata("instance/attributes/app-version", "dev")
EXTERNE = metadata("instance/network-interfaces/0/access-configs/0/external-ip", None) if SUR_GCE else None
CREE_PAR = metadata("instance/attributes/created-by", "") if SUR_GCE else ""
DEMARRAGE = time.time()
REQUETES = itertools.count(1)
# Démo publique : charge plafonnée, calcul court et nombre de requêtes limité par seconde.
DEMO = os.environ.get("DEMO_PUBLIQUE") == "true"
CHARGE_MAX = int(os.environ.get("CHARGE_MAX", "5" if DEMO else "20"))
TRAVAIL_MAX_MS = 20 if DEMO else 500
# Plages documentées des sondes de health check des Load Balancers Google.
PLAGES_SONDES = [ipaddress.ip_network("35.191.0.0/16"), ipaddress.ip_network("130.211.0.0/22")]
etat = {"sondes": 0, "derniere_sonde": 0.0, "sortie": None}
verrou = threading.Lock()
def tester_sortie():
# Sans IP externe, une sortie qui réussit passe forcément par Cloud NAT.
while True:
try:
socket.create_connection(("deb.debian.org", 443), timeout=3).close()
etat["sortie"] = True
except OSError:
etat["sortie"] = False
time.sleep(300)
class Limiteur:
def __init__(self, par_seconde):
self.capacite = self.jetons = par_seconde
self.maj = time.monotonic()
self.verrou = threading.Lock()
def autoriser(self):
with self.verrou:
maintenant = time.monotonic()
self.jetons = min(self.capacite, self.jetons + (maintenant - self.maj) * self.capacite)
self.maj = maintenant
if self.jetons < 1:
return False
self.jetons -= 1
return True
LIMITEUR = Limiteur(30) if DEMO else None
def bloc(id, nom, etat_bloc, detail="", preuve=""):
return {"id": id, "nom": nom, "etat": etat_bloc, "detail": detail, "preuve": preuve}
def carte():
if not SUR_GCE:
blocs = [
bloc("vm", "VM du groupe", "simule", f"conteneur {INSTANCE}, zone {ZONE}, version {VERSION}"),
bloc("ip", "Adresse des VM", "simule", "les VM du compose ne publient aucun port"),
bloc("mig", "Groupe d’instances", "simule", "trois conteneurs du compose, un par zone"),
bloc("sondes", "Health checks", "simule", "nginx ne sonde pas les VM en local"),
bloc("nat", "Sortie Internet", "simule", "sortie directe du conteneur"),
]
else:
blocs = [bloc("vm", "VM du groupe", "ok", f"instance {INSTANCE}, zone {ZONE}, version {VERSION}", "serveur de métadonnées")]
blocs.append(bloc("ip", "Adresse des VM", "alerte", f"cette VM a une IP externe : {EXTERNE}", "access-config présent")
if EXTERNE else bloc("ip", "Adresse des VM", "ok", "aucune IP externe sur cette VM", "aucun access-config dans les métadonnées"))
if "/instanceGroupManagers/" in CREE_PAR:
genre = "MIG régional" if "/regions/" in CREE_PAR else "MIG zonal"
blocs.append(bloc("mig", "Groupe d’instances", "ok", f"{genre} {CREE_PAR.rsplit('/', 1)[-1]}", CREE_PAR))
else:
blocs.append(bloc("mig", "Groupe d’instances", "inconnu", "cette VM n’a pas été créée par un MIG"))
with verrou:
sondes, derniere = etat["sondes"], etat["derniere_sonde"]
blocs.append(bloc("sondes", "Health checks", "ok", f"{sondes} sonde(s) reçue(s) depuis les plages Google, la dernière il y a {int(time.time() - derniere)} s", "35.191.0.0/16 et 130.211.0.0/22")
if sondes else bloc("sondes", "Health checks", "inconnu", "aucune sonde reçue : health check créé, pare-feu ouvert aux plages des sondes ?"))
if etat["sortie"] is None:
blocs.append(bloc("nat", "Sortie Internet", "inconnu", "test de sortie en cours"))
elif etat["sortie"] and not EXTERNE:
blocs.append(bloc("nat", "Sortie par Cloud NAT", "ok", "sortie Internet possible sans IP externe", "connexion à deb.debian.org:443 réussie"))
elif etat["sortie"]:
blocs.append(bloc("nat", "Sortie Internet", "ok", "sortie par l’IP externe de la VM, pas par NAT", "connexion à deb.debian.org:443 réussie"))
else:
blocs.append(bloc("nat", "Sortie Internet", "inconnu", "aucune sortie Internet : Cloud Router et NAT en place ?", "connexion à deb.debian.org:443 impossible"))
blocs += [
bloc("armor", "Cloud Armor", "manuel", "règle et logs de décision : à montrer dans la console"),
bloc("autoscaler", "Autoscaler", "manuel", "décisions de capacité : à montrer dans le MIG et Monitoring"),
bloc("obs", "Monitoring et alertes", "manuel", "dashboard et alertes : à montrer dans la console"),
]
return {"plateforme": "Compute Engine" if SUR_GCE else "local", "blocs": blocs}
PAGE = r"""<!DOCTYPE html>
<html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>FlashShop, démonstrateur</title>
<style>
:root{--ink:#0f172a;--muted:#5b6575;--line:#e2e8f0;--soft:#f6f8fa;--night:#0b1220;--accent:#e0473a;--ok:#0f8a4f;--warn:#b45309;--err:#c5221f;--mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace}
*{box-sizing:border-box}body{margin:0;background:var(--soft);color:#1e293b;font:15px/1.6 ui-sans-serif,-apple-system,"Segoe UI",Roboto,Arial,sans-serif}
header{background:var(--night);color:#fff}header div{max-width:1100px;margin:auto;padding:12px 24px;display:flex;gap:12px;align-items:center}
.mark{width:10px;height:10px;border-radius:2px;background:var(--accent)}.brand{font:600 15px/1 var(--mono)}#env{margin-left:auto;font:12px/1.4 var(--mono);color:#94a3b8}
main{max-width:1100px;margin:auto;padding:24px;display:grid;gap:16px}h1{margin:0;font-size:26px;color:var(--ink)}.lead{color:var(--muted);margin:4px 0 0}
.panel{background:#fff;border:1px solid var(--line);border-radius:8px;padding:18px}.panel h2{margin:0 0 12px;font:600 12px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px}.stat{border:1px solid var(--line);border-radius:6px;padding:10px 12px;background:#fff}
.stat>span{display:block;font:11px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}.stat strong{font:600 20px/1.35 var(--mono);color:var(--ink)}
button{font:500 13px/1.4 var(--mono);padding:7px 12px;border-radius:5px;border:1px solid #c4ceda;background:#fff;cursor:pointer}button.on{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
.row{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.muted{color:var(--muted);font-size:13px}
.barre{display:grid;grid-template-columns:minmax(150px,240px) 1fr 70px;gap:10px;align-items:center;margin:8px 0;font:13px/1.4 var(--mono)}
.barre .fond{height:14px;background:#eef1f4;border-radius:3px;overflow:hidden}.barre .fond span{display:block;height:100%}.barre.muette{opacity:.4}
#cases{display:flex;flex-wrap:wrap;gap:3px}#cases i{width:12px;height:12px;border-radius:2px;display:block}
.badge{display:inline-block;font:600 11px/1.6 var(--mono);padding:1px 7px;border-radius:4px;background:var(--soft);border:1px solid var(--line);color:var(--muted)}
.badge.ok{color:var(--ok);border-color:#b7e1c7;background:#effaf3}.badge.warn{color:var(--warn);border-color:#f3d7a7;background:#fff8ec}.badge.err{color:var(--err);border-color:#f5c2bf;background:#fef3f2}.badge.sim{color:#4f46e5;border-color:#c7d2fe;background:#eef2ff}
.carte{display:grid;gap:12px}.resume{font:13px/1.4 var(--mono);color:var(--muted)}
.rangee{display:grid;grid-template-columns:110px repeat(auto-fit,minmax(170px,1fr));gap:10px}.rangee>span{font:600 11px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--muted);padding-top:12px}
.bloc{border:1px solid var(--line);border-left:4px solid #cbd5e1;border-radius:6px;padding:10px 12px;background:#fff;min-width:0}
.bloc strong{display:block;color:var(--ink);margin:6px 0 2px;font-size:14px}.bloc p{margin:0;font-size:13px;line-height:1.45;color:var(--muted)}.bloc code{display:block;margin-top:6px;font:11px/1.4 var(--mono);color:var(--muted);overflow-wrap:anywhere}
.etat-ok{border-left-color:var(--ok);background:#f5fbf7}.etat-alerte{border-left-color:var(--warn);background:#fffaf0}.etat-echec{border-left-color:var(--err);background:#fff6f5}
.etat-inconnu{border-style:dashed;border-left-style:solid}.etat-manuel{border-style:dotted;border-left-style:solid;background:var(--soft)}.etat-simule{border-left-color:#6366f1;background:#f7f7ff}
@media(max-width:760px){.rangee{grid-template-columns:1fr}.rangee>span{padding-top:4px}}
</style></head><body>
<header><div><span class="mark"></span><span class="brand">FlashShop</span><span id="env">...</span></div></header>
<main>
<div><h1>Qui répond derrière le Load Balancer ?</h1><p class="lead">Chaque requête passe par le Load Balancer, qui choisit une instance du MIG. Arrêtez le service sur une VM et regardez sa barre s’arrêter.</p></div>
<section class="panel"><h2>Carte du déploiement</h2><div class="carte" id="carte"><p class="muted">Analyse en cours...</p></div></section>
<div class="stats"><div class="stat"><span>Requêtes</span><strong id="n">0</strong></div><div class="stat"><span>Erreurs</span><strong id="e">0</strong></div><div class="stat"><span>Latence p50</span><strong id="p50">...</strong></div><div class="stat"><span>Latence p95</span><strong id="p95">...</strong></div></div>
<section class="panel"><h2>Charge</h2>
<div class="row"><button data-rate="1" class="on">Observer, 1 req/s</button><button data-rate="5">5 req/s</button><button data-rate="10">10 req/s</button><button data-rate="20">20 req/s</button><button data-rate="0">Arrêter</button>
<label class="muted"><input type="checkbox" id="cpu"> avec du calcul côté serveur</label></div>
<p class="muted" id="minuteur">Les paliers de charge s’arrêtent seuls après 60 secondes. Ne dépassez pas la capacité convenue avec le formateur.</p></section>
<section class="panel"><h2>Répartition entre les instances</h2><div id="barres"></div></section>
<section class="panel"><h2>Dernières réponses</h2><div id="cases"></div><p class="muted">Une case par réponse, de la couleur de l’instance. En rouge : une erreur ou un délai dépassé.</p></section>
</main>
<script>
const $=s=>document.querySelector(s);const vues=new Map();const latences=[];const cases=[];let n=0,erreurs=0,minuterie=null,fin=0,via=null,plateforme=null;
const couleur=id=>'hsl('+([...id].reduce((h,c)=>(h*31+c.charCodeAt(0))%997,7)*137.5)%360+' 70% 50%)';
async function requete(){n++;const t0=performance.now();const url=$('#cpu').checked?'/api/travail?ms=50':'/api/instance';
try{const r=await fetch(url,{cache:'no-store',signal:AbortSignal.timeout(3000)});if(!r.ok)throw new Error(r.status);via=r.headers.get('via')||via;const d=await r.json();
const ms=performance.now()-t0;latences.push(ms);if(latences.length>200)latences.shift();
const v=vues.get(d.instance)||{zone:d.zone,version:d.version,n:0};v.n++;v.vu=Date.now();v.version=d.version;vues.set(d.instance,v);cases.push(couleur(d.instance));}
catch(e){erreurs++;cases.push('var(--err)');}if(cases.length>150)cases.shift();}
function centile(p){if(!latences.length)return'...';const t=[...latences].sort((a,b)=>a-b);return Math.round(t[Math.min(t.length-1,Math.floor(p*t.length))])+' ms';}
function dessiner(){$('#n').textContent=n;$('#e').textContent=erreurs;$('#p50').textContent=centile(.5);$('#p95').textContent=centile(.95);
const total=[...vues.values()].reduce((s,v)=>s+v.n,0)||1;$('#barres').replaceChildren(...[...vues.entries()].sort().map(([id,v])=>{const d=document.createElement('div');
d.className='barre'+(Date.now()-v.vu>5000?' muette':'');d.innerHTML='<span></span><span class="fond"><span></span></span><span></span>';d.children[0].textContent=id+' / '+v.zone+' / '+v.version;
d.children[1].firstChild.style.width=(v.n/total*100)+'%';d.children[1].firstChild.style.background=couleur(id);d.children[2].textContent=v.n;return d;}));
$('#cases').replaceChildren(...cases.map(c=>{const i=document.createElement('i');i.style.background=c;return i;}));
if(fin){const reste=Math.max(0,Math.round((fin-Date.now())/1000));$('#minuteur').textContent='Palier en cours, encore '+reste+' s.';if(!reste)regler(1);}}
function regler(rate){clearInterval(minuterie);minuterie=null;fin=0;document.querySelectorAll('button[data-rate]').forEach(b=>b.classList.toggle('on',Number(b.dataset.rate)===rate));
if(rate>0)minuterie=setInterval(requete,1000/rate);if(rate>1)fin=Date.now()+60000;else $('#minuteur').textContent='Les paliers de charge s’arrêtent seuls après 60 secondes.';}
document.querySelectorAll('button[data-rate]').forEach(b=>b.addEventListener('click',()=>regler(Number(b.dataset.rate))));
const etats={ok:['prouvé','ok'],alerte:['à revoir','warn'],echec:['en échec','err'],inconnu:['pas encore détecté',''],manuel:['à prouver vous-même',''],simule:['simulé en local','sim']};
const bloc=(id,nom,etat,detail='',preuve='')=>({id,nom,etat,detail,preuve});
async function carte(){let s;try{s=await(await fetch('/api/deploiement',{cache:'no-store'})).json();}catch{return;}
const local=s.plateforme==='local';const blocs=[...s.blocs];const zones=new Set([...vues.values()].map(v=>v.zone));
blocs.push(local?bloc('lb','Load Balancer','simule','nginx, local/lb.conf'):via&&/google/i.test(via)?bloc('lb','Load Balancer','ok','les réponses passent par un Load Balancer Google','via: '+via):bloc('lb','Load Balancer','inconnu','aucun en-tête Via de Google : la page passe-t-elle par le Load Balancer ?'));
const z=[...zones].sort().join(', ');blocs.push(zones.size>1?bloc('zones','Répartition entre zones',local?'simule':'ok','réponses venues de '+zones.size+' zones et '+vues.size+' instances',z):bloc('zones','Répartition entre zones',local?'simule':'inconnu',zones.size?'une seule zone vue pour l’instant : '+z:'en attente de réponses'));
const rangees=[['Entrée',['lb','armor']],['Flotte',['mig','zones','vm','ip']],['Santé',['sondes']],['Sorties',['nat']],['Exploitation',['autoscaler','obs']]];
const parId=new Map(blocs.map(b=>[b.id,b]));const lignes=rangees.map(([titre,ids])=>{const r=document.createElement('div');r.className='rangee';const t=document.createElement('span');t.textContent=titre;r.append(t);
for(const id of ids){const b=parId.get(id);if(!b)continue;const[texte,classe]=etats[b.etat]||etats.inconnu;const c=document.createElement('div');c.className='bloc etat-'+b.etat;
c.innerHTML='<span class="badge"></span><strong></strong><p></p><code></code>';c.children[0].className='badge '+classe;c.children[0].textContent=texte;c.children[1].textContent=b.nom;c.children[2].textContent=b.detail;
if(b.preuve)c.children[3].textContent=b.preuve;else c.children[3].remove();r.append(c);}return r;});
const det=blocs.filter(b=>b.etat!=='manuel');const ok=det.filter(b=>b.etat==='ok').length;const resume=document.createElement('p');resume.className='resume';
resume.textContent=det.every(b=>b.etat==='simule')?'En local, tous les blocs sont simulés. Une fois déployée sur GCP, la carte s’allume bloc par bloc.':ok+' bloc(s) prouvé(s) sur '+det.length+' détectables.';
$('#carte').replaceChildren(resume,...lignes);}
fetch('/api/instance',{cache:'no-store'}).then(r=>r.json()).then(d=>{$('#env').textContent='page servie par '+d.instance+' / '+d.zone+' / '+d.version;
document.querySelectorAll('button[data-rate]').forEach(b=>{if(Number(b.dataset.rate)>d.charge_max)b.remove();});});
regler(1);setInterval(dessiner,500);carte();setInterval(carte,10000);
</script></body></html>
""".encode()
class Handler(BaseHTTPRequestHandler):
def repondre(self, status, body, kind):
self.send_response(status)
self.send_header("Content-Type", kind)
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
def json(self, contenu, status=200):
self.repondre(status, json.dumps(contenu).encode() + b"\n", "application/json; charset=utf-8")
def identite(self, **extra):
return {"instance": INSTANCE, "zone": ZONE, "version": VERSION, "depuis_s": int(time.time() - DEMARRAGE),
"requetes": next(REQUETES), "charge_max": CHARGE_MAX, **extra}
def do_GET(self):
url = urllib.parse.urlsplit(self.path)
if url.path == "/healthz":
try:
source = ipaddress.ip_address(self.client_address[0])
if any(source in plage for plage in PLAGES_SONDES):
with verrou:
etat["sondes"] += 1
etat["derniere_sonde"] = time.time()
except ValueError:
pass
return self.repondre(200, b"ok\n", "text/plain; charset=utf-8")
if url.path == "/":
return self.repondre(200, PAGE, "text/html; charset=utf-8")
if url.path.startswith("/api/") and LIMITEUR and not LIMITEUR.autoriser():
return self.json({"erreur": "trop de requêtes pour la démo publique"}, 429)
if url.path == "/api/instance":
return self.json(self.identite())
if url.path == "/api/travail":
try:
ms = min(TRAVAIL_MAX_MS, max(0, int(urllib.parse.parse_qs(url.query).get("ms", ["50"])[0])))
except ValueError:
ms = 20
fin = time.perf_counter() + ms / 1000
while time.perf_counter() < fin:
pass
return self.json(self.identite(travail_ms=ms))
if url.path == "/api/deploiement":
return self.json(carte())
self.send_error(404)
def log_message(self, *args):
pass # les journaux du Load Balancer suffisent
threading.Thread(target=tester_sortie, daemon=True).start()
serveur = ThreadingHTTPServer(("0.0.0.0", int(os.environ.get("PORT", "8080"))), Handler)
print(f"FlashShop prêt sur le port {serveur.server_address[1]}, instance {INSTANCE}", flush=True)
serveur.serve_forever()
PY
cat > /etc/systemd/system/flashshop.service <<'UNIT'
[Unit]
Description=FlashShop, page de démonstration
After=network-online.target
[Service]
ExecStart=/usr/bin/python3 /opt/flashshop.py
Restart=on-failure
DynamicUser=yes
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable flashshop.service
systemctl restart flashshop.service
docker-compose.yml, le lancement local
# FlashShop en local : docker compose up --build, puis http://localhost:8080
name: flashshop-local
# Trois conteneurs jouent les VM du MIG régional, une par zone, tous construits depuis la même image,
# comme les instances créées à partir d'un même template.
x-vm: &vm
image: flashshop-vm:local
build:
context: .
dockerfile: local/Dockerfile.vm
services:
vm-europe-west9-a:
<<: *vm
environment: { INSTANCE_NAME: flashshop-mig-a1, ZONE: europe-west9-a, APP_VERSION: v1 }
vm-europe-west9-b:
<<: *vm
environment: { INSTANCE_NAME: flashshop-mig-b1, ZONE: europe-west9-b, APP_VERSION: v1 }
vm-europe-west9-c:
<<: *vm
environment: { INSTANCE_NAME: flashshop-mig-c1, ZONE: europe-west9-c, APP_VERSION: v1 }
# Joue le rôle de l'Application Load Balancer. Les VM, elles, ne publient aucun port.
lb:
image: nginx:stable-alpine
ports:
- "8080:80"
volumes:
- ./local/lb.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- vm-europe-west9-a
- vm-europe-west9-b
- vm-europe-west9-c
local/lb.conf, le Load Balancer imité
# Émulation locale de l'Application Load Balancer devant le MIG.
# Ce fichier ne se déploie jamais : sur GCP, ce rôle revient au backend service,
# à son health check et à l'autohealing du MIG.
# Le DNS de Docker est relu en continu (valid=5s) : une VM arrêtée sort de la liste,
# une VM relancée y revient, comme un backend qui redevient sain.
resolver 127.0.0.11 valid=5s ipv6=off;
upstream mig {
zone mig 64k;
server vm-europe-west9-a:8080 max_fails=1 fail_timeout=10s resolve;
server vm-europe-west9-b:8080 max_fails=1 fail_timeout=10s resolve;
server vm-europe-west9-c:8080 max_fails=1 fail_timeout=10s resolve;
}
server {
listen 80;
location / {
proxy_pass http://mig;
# Pas de nouvel essai sur une autre instance : les erreurs restent visibles côté client.
proxy_next_upstream off;
proxy_connect_timeout 2s;
proxy_read_timeout 5s;
}
}
README.md, le passage sur GCP
# FlashShop, kit de démarrage
Un script de démarrage pour les VM du MIG. Il installe une page qui montre quelle instance répond derrière le Load Balancer, avec un petit générateur de charge borné. Le même code tourne en local, sur trois conteneurs qui jouent les VM de trois zones. Le kit est un démonstrateur : il sert à vérifier votre infrastructure, il n’est pas évalué.
## Ce qu’il contient
```text
vm/startup.sh Le script à passer au template d’instance (metadata_startup_script)
local/ Ce qui remplace GCP en local, jamais déployé
Dockerfile.vm Une « VM » locale, construite à partir de startup.sh
lb.conf nginx dans le rôle de l’Application Load Balancer
docker-compose.yml Trois VM, une par zone, derrière le Load Balancer local
```
## Lancer en local
```sh
docker compose up --build
```
Ouvrez http://localhost:8080. La page interroge le Load Balancer une fois par seconde et montre la répartition des réponses entre les instances. Les boutons de charge envoient 5, 10 ou 20 requêtes par seconde pendant 60 secondes, avec ou sans calcul côté serveur.
Pour simuler la perte d’une VM, puis son retour :
```sh
docker compose stop vm-europe-west9-b
docker compose start vm-europe-west9-b
```
Sa barre s’arrête de grandir, quelques erreurs peuvent apparaître, puis le trafic se répartit sur les deux autres.
| Rôle | En local | Sur GCP |
| --- | --- | --- |
| Point d’entrée | nginx, `local/lb.conf` | Application Load Balancer et backend service |
| Instances | trois conteneurs construits depuis `startup.sh` | VM privées du MIG régional, créées depuis le template |
| Retrait d’une instance | le DNS de Docker ne la renvoie plus | health check du backend service |
| Remplacement d’une instance | à faire à la main avec `docker compose start` | autohealing du MIG |
| Ajout de capacité | non imité | autoscaler du MIG |
| Sorties vers Internet | directes | Cloud NAT |
Le Load Balancer local montre le principe de la répartition et du retrait d’une instance. Il ne reproduit ni les délais des health checks de GCP, ni l’autohealing, ni l’autoscaling.
## Passer sur GCP
Le script `vm/startup.sh` se donne tel quel au template d’instance, par exemple avec `metadata_startup_script = file("startup.sh")` dans votre module. Il n’a besoin d’aucun accès à Internet : le Python présent sur l’image Debian 12 suffit.
| Élément | Valeur |
| --- | --- |
| Port de l’application | `8080`, à déclarer comme named port du groupe d’instances |
| Chemin des health checks | `/healthz` |
| Version affichée | métadonnée `app-version` du template, pour prouver quelle version est servie |
| Service systemd | `flashshop` : `sudo systemctl stop flashshop` sur une VM pour l’expérience de panne |
| Charge CPU | `/api/travail?ms=50`, plafonné à 500 ms par requête |
Les noms d’instance et de zone viennent du serveur de métadonnées de Compute Engine. En local, les variables `INSTANCE_NAME`, `ZONE` et `APP_VERSION` les remplacent.
Le générateur de charge de la page est volontairement limité à 20 requêtes par seconde et à 60 secondes par palier, pour rester dans les bornes du laboratoire. Si aucun scale-out ne se produit, expliquez pourquoi plutôt que d’augmenter la charge.
## La carte du déploiement
En haut de l’interface, l’architecture cible est dessinée bloc par bloc. Chaque bloc s’allume selon ce que l’application constate elle-même, avec la preuve affichée dessous :
| Couleur | Sens |
| --- | --- |
| vert, « prouvé » | l’application l’a vérifié elle-même |
| orange, « à revoir » | ça fonctionne, mais c’est un anti-pattern connu |
| rouge, « en échec » | l’application a essayé et ça ne marche pas |
| pointillés, « pas encore détecté » | rien de visible pour l’instant |
| gris, « à prouver vous-même » | invisible depuis l’application : montrez-le dans la console |
| violet, « simulé en local » | l’équivalent local, en attendant le déploiement |
La carte constate, elle ne note pas : un bloc vert ne dit pas que votre choix est le bon, seulement qu’il est en place.
Pour FlashShop, chaque VM constate elle-même :
| Bloc | Comment |
| --- | --- |
| VM et adresse | serveur de métadonnées : nom, zone, version, présence d’une IP externe |
| Groupe d’instances | métadonnée `created-by` : MIG régional ou zonal |
| Health checks | sondes reçues sur `/healthz` depuis les plages `35.191.0.0/16` et `130.211.0.0/22`, ce qui prouve aussi la règle de pare-feu |
| Sortie par Cloud NAT | une connexion sortante qui réussit sans IP externe |
Le navigateur ajoute le passage par un Load Balancer Google (en-tête `Via`) et le nombre de zones qui ont répondu. Aucun droit IAM n’est nécessaire : tout passe par le serveur de métadonnées.
## Démo publique
| Variable | Rôle |
| --- | --- |
| `DEMO_PUBLIQUE` | `true` : calcul plafonné à 20 ms, 30 requêtes par seconde au plus par instance. |
| `CHARGE_MAX` | Palier de charge le plus haut proposé par la page : 20 par défaut, 5 en démo publique. |
01 Cadrer
Comprendre la demande
Laissez Terraform de côté pour l’instant. Partez du message du responsable et traduisez-le en un parcours utilisateur et en critères de réussite que l’on peut observer.
À faire
- Choisissez dans la liste ci-dessous les trois questions qui vous semblent prioritaires et posez-les au formateur. Les autres pourront venir ensuite.
- Séparez ce qui est confirmé, ce que vous supposez pour l’instant et ce qui reste à décider.
- Fixez un minimum démontrable, une limite de coût et ce que vous laissez de côté dans la première version.
Les questions à poser
- Quel trafic normal et quel pic ?
- Combien de temps dure le pic ?
- Le service doit-il survivre à la perte d’une zone entière ?
- Vers quelles destinations les VM doivent-elles sortir ?
- Quel signal déclenche l’autoscaling ?
- Comment protéger l’entrée HTTP contre des abus simples ?
Modèle de fiche de cadrage
| Besoin ou question | Réponse ou hypothèse | Preuve prévue |
|---|---|---|
| À compléter | Qui l’a confirmée ? | Comment la vérifier ? |
Montrez au formateur une fiche courte avec vos hypothèses et trois critères de réussite. Il valide le périmètre et le plafond de charge avant la suite.
02 Architecture
Comprendre l’architecture cible
Cette architecture est votre point de départ. Votre travail consiste à la déployer avec Terraform, à la sécuriser, à l’automatiser et à vérifier qu’elle se comporte comme prévu.
L’entrée HTTP passe par le Load Balancer, les sorties des VM passent par NAT, et le MIG régional répartit les instances. Ne confondez pas le health check qui oriente le trafic avec la politique d’autohealing qui remplace une VM malade.
flowchart TB U["Visiteur"] --> LB["Application Load Balancer externe"] ARM["Cloud Armor policy sur le backend"] -.-> LB LB --> BE["Backend service et health check"] BE --> A["VM privée zone A"] BE --> B["VM privée zone B"] MIG["MIG régional template et autoscaler"] -.-> A MIG -.-> B A --> NAT["Cloud NAT et Cloud Router"] B --> NAT NAT --> EXT["Dépendances externes"] LB -.-> OBS["Monitoring et logs"] MIG -.-> OBS
Sur un petit écran, faites défiler le schéma horizontalement.
Voir et copier le code Mermaid
flowchart TB
U["Visiteur"] --> LB["Application Load Balancer externe"]
ARM["Cloud Armor
policy sur le backend"] -.-> LB
LB --> BE["Backend service
et health check"]
BE --> A["VM privée
zone A"]
BE --> B["VM privée
zone B"]
MIG["MIG régional
template et autoscaler"] -.-> A
MIG -.-> B
A --> NAT["Cloud NAT
et Cloud Router"]
B --> NAT
NAT --> EXT["Dépendances externes"]
LB -.-> OBS["Monitoring et logs"]
MIG -.-> OBS
Comment ça circule
Le visiteur appelle le point d’entrée
Le Load Balancer reçoit la requête et la répartit entre les backends disponibles, selon sa configuration.
Les VM servent la page
Le MIG crée les instances à partir du même template, dans plusieurs zones. Aucune VM applicative n’a d’IP publique.
La capacité évolue
L’autoscaler ajuste la taille du groupe selon le signal et les bornes choisis. Tenez compte du temps de démarrage, des health checks et du plafond.
Les sorties passent par NAT
Cloud NAT donne aux VM privées les accès sortants dont elles ont besoin. Ce chemin n’a rien à voir avec le trafic entrant, qui arrive par le Load Balancer.
Répartir les VM entre zones, remplacer une VM supprimée et ajouter de la capacité sont trois mécanismes différents. Prévoyez une preuve pour chacun.
Approfondir dans la documentation Google Cloud
À faire à partir du schéma
- Suivez une requête ou un événement de bout en bout et expliquez le rôle de chaque service GCP.
- Listez les ressources Terraform nécessaires, leurs dépendances et ce que chaque module expose aux autres.
- Repérez ce qui reste à décider : région et zones, exposition réseau, identités et permissions, dimensionnement, seuils et politique de reprise.
- Annotez trois choix d’infrastructure avec leurs compromis. Gardez un schéma fidèle à ce que vous déployez réellement.
Présentez au formateur votre découpage Terraform, les flux autorisés et les paramètres retenus. On parle ici d’infrastructure : concevoir une application métier n’est pas l’objet du TP.
03 Construire
Construire le premier parcours
Avancez par petits pas : vous déployez, vous vérifiez, puis vous ajoutez la dépendance suivante. Les ressources finales sont décrites dans Terraform. La console reste utile pour observer et diagnostiquer.
À faire
- Créez le réseau, les sorties nécessaires et les règles de pare-feu que votre schéma justifie.
- Construisez un template reproductible et un MIG régional d’au moins deux instances réparties sur deux zones, si le budget validé le permet.
- Configurez le Load Balancer, ses backends et ses sondes, puis montrez que plusieurs instances répondent.
- Configurez le remplacement des instances défaillantes et l’autoscaling, en documentant le minimum, le maximum, le signal et les délais.
À montrer
Des réponses venant de plusieurs instances, des VM sans IP externe et un plan Terraform sans modification inattendue.
À expliquer
Une sonde du Load Balancer détecte une application en panne. Est-ce que cela suffit à remplacer la VM ? Où avez-vous configuré ce comportement ?
Besoin d’un indice ?
Indice 1, une piste
Séparez trois décisions : où envoyer une requête, quand remplacer une VM et quand ajouter de la capacité.
Indice 2, où chercher
Health checks et autohealing du MIG
Les ressources du provider sont ensuite listées dans la rubrique Documentation Terraform.
Indice 3, un coup de pouce
Le health check du Load Balancer et la politique d’autohealing du MIG ont des rôles distincts. Vérifiez les délais de démarrage avant de provoquer une panne : une sonde trop agressive peut entretenir une boucle de remplacements.
Organiser Terraform
Découpez vos modules par responsabilité. Écrivez d’abord leurs entrées, leurs sorties et leurs dépendances : le module racine se contente de les assembler. Chaque module a une courte documentation. Inutile en revanche de créer un module par ressource.
Un découpage à discuter, une fois votre propre proposition faite
Chaque module contient main.tf, variables.tf, outputs.tf et un court README. Le module racine relie les sorties des uns aux entrées des autres. Ce découpage est une proposition : à vous de le défendre ou de l’améliorer.
| Module | Responsabilité | Entrées principales | Sorties utiles |
|---|---|---|---|
| network | Le VPC, le subnet, Cloud Router et NAT, et les règles de pare-feu. | region, cidr, règles pour le LB et les health checks | network_id, subnet_id, router_id |
| web_fleet | L’Instance Template, le MIG régional, l’autoscaler et l’autohealing. | subnet_id, image, zones, min et max, signal | instance_group, named_port |
| edge | Le Load Balancer, le backend service, son health check et Cloud Armor. | instance_group, named_port, règles de protection | ip_address, backend_service_id |
| observability | Le dashboard du LB, du MIG et des VM, et les alertes de latence et de capacité. | backend_id, mig_id, seuils, canaux | dashboard_id, alert_policy_ids |
Une organisation de dépôt possible :
infra/
bootstrap/ # backend GCS et accès CI, cycle séparé
envs/
lab/ # module racine déployé, backend et variables
prod-design/ # variante documentée, sans déploiement imposé
modules/
network/
web_fleet/
edge/
observability/
app/ # le kit de démarrage, si vous l’utilisez
tests/
smoke/ # vérification du chemin nominal
load/ # scripts et jeux synthétiques
docs/
architecture.mmd
decisions/ # décisions et alternatives (ADR)
runbooks/ # diagnostic et reprise
evidence/ # résultats, sans secret
README.mdÉtat, environnements et bootstrap
L’état Terraform vit dans un bucket GCS créé par un bootstrap séparé, lancé avant le laboratoire et qui n’en dépend pas. Activez le versioning de ce bucket et limitez qui peut y accéder. Le backend GCS gère le verrouillage, et un préfixe par environnement évite de mélanger les états.
Ne commitez ni l’état, ni les plans, ni les clés, ni les valeurs de secrets. Attention, sensitive = true masque une valeur à l’affichage mais ne la retire pas du state. Gardez plutôt un fichier d’exemple avec les variables non sensibles. Voir la documentation des modules Terraform.
Le chemin nominal fonctionne. terraform fmt -check et terraform validate passent, et un nouveau plan ne propose rien d’inattendu. Un autre membre du groupe sait expliquer les dépendances.
04 Automatiser
Rendre le déploiement reproductible
Un collègue doit pouvoir relire puis déployer un changement sans refaire vos manipulations dans la console.
À faire
- À chaque changement, la CI vérifie le format, initialise sans backend et lance
validate. Les versions sont figées et le fichier de verrouillage est commité. - La CI s’authentifie par fédération d’identité, limitée à votre dépôt et aux branches ou environnements autorisés. Aucune clé JSON durable dans Git.
- Le plan est produit sur une branche de confiance, relu, puis appliqué tel quel après une approbation explicite. Un seul déploiement à la fois par environnement.
- Un test du parcours nominal suit l’apply. Si quelque chose échoue, le pipeline s’arrête là.
- La destruction est un job manuel et approuvé, suivi d’un inventaire des ressources restantes. Le bootstrap garde son propre cycle.
À montrer
Un petit changement suivi de son commit jusqu’au test final, et une erreur de validation volontaire arrêtée avant tout apply. Les plans sont des artefacts privés, conservés peu de temps.
Besoin d’un indice ?
Indice 1, une piste
Dessinez les jobs et demandez-vous quelle identité exécute chacun d’eux. Qu’est-ce qui prouve que le plan appliqué est bien celui qui a été relu ?
Indice 2, où chercher
La page du backend GCS et celle sur la fédération d’identité pour les pipelines.
Indice 3, un coup de pouce
Séparez quatre temps : une validation sans accès au cloud, un plan authentifié, une approbation, puis l’apply. Rattachez le plan au commit et à l’environnement. Si le code, les variables ou l’état changent, le plan doit être recalculé et relu à nouveau.
Montrez une exécution de la CI avec son test final. Une procédure manuelle documentée aide au diagnostic, mais elle ne remplace pas cette preuve d’automatisation.
05 Éprouver
Observer, tester, expliquer
Annoncez votre hypothèse avant chaque test. Un graphique doit répondre à une question précise : avoir un dashboard ne prouve pas, à lui seul, que le système est fiable.
Préparer les mesures
La latence p95, les erreurs, le nombre de backends sains, la taille réelle et souhaitée du MIG, et le signal choisi pour l’autoscaling.
Déployez avec Terraform un dashboard et deux alertes utiles. Pour chacune, précisez la métrique, le filtre, la fenêtre, le seuil, le destinataire et la première action de diagnostic. Vérifiez que la notification arrive bien, puis qu’elle se referme quand tout revient à la normale.
Des signaux à adapter
| Signal | Où le trouver | Décision ou exemple de seuil |
|---|---|---|
| Latence p95 et taux de 5xx | Load Balancer | Exemple pour le labo : un p95 au-dessus de 1 s pendant 3 minutes, avec un trafic suffisant. Regardez alors les backends. |
| Instances saines, taille souhaitée et réelle | MIG et health checks | Repérez un écart qui dure au-delà du temps de démarrage, et examinez le script de démarrage. |
| Signal de capacité et ressources des VM | CPU, métrique du LB ou métrique applicative | Vérifiez que le signal suit bien la saturation, et gardez une trace de chaque changement de capacité. |
L’essai de charge du laboratoire
Mesurez 5 requêtes par seconde pendant 2 minutes, puis 5, 10 et 20 requêtes par seconde pendant 1 minute chacune. Ne dépassez pas la capacité maximale convenue. Si aucun scale-out ne se produit, expliquez pourquoi plutôt que d’augmenter la charge sans limite.
Avant de lancer quoi que ce soit, fixez la cible autorisée, le plafond de ressources, la durée, le volume et la règle d’arrêt. Arrêtez si le coût dérape ou si la dégradation persiste. Un exemple de seuil à faire valider : plus de 5 % d’erreurs pendant 30 secondes.
Une expérience pour vous entraîner
Sur une seule VM du MIG de laboratoire, arrêtez le service HTTP. Suivez son retrait du trafic, les erreurs vues par le client et son éventuel remplacement, puis comparez la durée observée aux délais configurés.
Écrivez la procédure de retour avant de commencer et ne changez qu’un paramètre à la fois. Si vous modifiez quelque chose à la main pour diagnostiquer, remettez ensuite Terraform en accord avec l’état voulu.
À montrer
Une chronologie de la panne et du retour au service, les réponses vues par le client et les courbes de capacité. Une instance remplacée n’est pas une décision de scale-out : ne confondez pas les deux.
Trame du compte rendu
- L’hypothèse et le résultat attendu.
- Le périmètre, la charge, l’heure de début et l’heure de fin.
- Ce que vous avez observé avant, pendant et après, et l’impact sur le parcours utilisateur.
- Les pistes de diagnostic, les vérifications faites et la cause retenue.
- La correction, la preuve du retour au service et les limites de votre conclusion.
En cours de route, le formateur vous annoncera un événement supplémentaire. Vous devrez en expliquer l’impact et proposer une réponse. Selon le temps et le budget, cette réponse pourra mêler une expérience bornée et une évolution d’architecture argumentée.
Votre compte rendu sépare ce qui a été mesuré, ce qui reste une hypothèse et ce qui demanderait un test plus large. Chaque membre sait lire les courbes.
06 Présenter
Présenter ce que vous avez réalisé
Chaque groupe dispose de 20 minutes de présentation technique, démonstration comprise. Les trois membres prennent la parole. Montrez la solution telle qu’elle est, avec ses résultats et ses limites, et gardez des traces de secours au cas où la démonstration en direct échouerait.
| Séquence | Durée | À montrer |
|---|---|---|
| Besoin et périmètre | 2 min | Les hypothèses, les objectifs et le minimum réellement livré. |
| Architecture réalisée | 4 min | Le schéma, les flux, la sécurité, vos choix et les alternatives écartées. |
| Terraform et pipeline | 4 min | Les modules et leurs interfaces, l’état distant et la trace d’un déploiement par la CI. |
| Démonstration et résultats | 6 min | Le chemin nominal, l’essai de charge, l’incident, le monitoring et la reprise. Prévoyez des captures au cas où la démonstration échouerait. |
| Coûts et limites | 3 min | Les coûts estimés, les écarts avec la production, ce qui n’est pas fait et la suite. |
| Conclusion | 1 min | Le bilan technique et ce que le groupe retient. |
Ce que votre support doit référencer
- Le dépôt, son README de déploiement et de destruction, le contrat de chaque module et une trace du pipeline.
- Le schéma réellement déployé et trois décisions argumentées.
- Les résultats de charge, le compte rendu d’incident, les alertes et le runbook.
- Une estimation des principaux coûts, ce qui n’a pas été réalisé et l’écart avec une cible de production.
Après la présentation
À la date convenue avec le formateur, détruisez les ressources du laboratoire, vérifiez ce qui reste et signalez ce que vous gardez volontairement. Tenir le budget et nettoyer font partie du travail.
Répétez avec un chronomètre. Chaque membre doit pouvoir expliquer un flux, une permission, une panne et une limite sans se contenter de lire les diapositives.
Où en êtes-vous ?
Socle
Version complète
Ces cases sont enregistrées dans votre navigateur uniquement. Le formateur ne les voit pas.
Si vous êtes en avance
Choisissez une seule extension, une fois le parcours principal terminé et son coût validé. Ajouter des services ne remplace pas des preuves de qualité.
- Tester une mise à jour progressive et le retour à une version saine.
- Ajouter une règle Cloud Armor sur un chemin de démonstration et vérifier son effet.
- Comparer le CPU à un signal plus proche de la saturation réelle de votre application.
Documentation Terraform
Ouvrez ces aides quand vous en avez besoin. Le rôle de chaque service est indiqué, mais leur assemblage, leurs paramètres et leurs permissions restent à concevoir.
Ressources Terraform utiles
Cette liste est un point de départ et votre architecture demandera d’autres ressources. Chaque lien ouvre la page du provider Google, avec ses arguments et ses exemples.
| Ressource | Rôle | Conseil |
|---|---|---|
google_compute_instance_template | Décrire les VM et leur démarrage. | Pas de bloc access_config si les VM ne doivent pas avoir d’IP externe. Le script de démarrage doit pouvoir être rejoué sans casse. |
google_compute_region_instance_group_manager | Créer le MIG réparti sur plusieurs zones. | Régional, pas zonal. Exposez le groupe d’instances et le named port au module du Load Balancer. |
google_compute_region_autoscaler | Définir les bornes et le signal de capacité. | Commencez avec un plafond bas, et tenez compte du temps de démarrage et du signal réellement mesuré. |
google_compute_health_check | Vérifier que le service répond. | Le port et le chemin doivent exister dans l’application. Distinguez le contrôle du LB de celui de l’autohealing. |
google_compute_backend_service | Associer le MIG au Load Balancer. | Vérifiez le named port, les health checks et la capacité déclarée du backend. |
google_compute_router_nat | Permettre les sorties des VM privées. | Il faut un Cloud Router. NAT sert aux sorties, pas à exposer le site. |
google_compute_firewall | N’autoriser que les flux nécessaires. | Les sources à autoriser dépendent du type de Load Balancer retenu : reportez-vous à sa documentation. |
google_compute_security_policy | Ajouter une policy Cloud Armor. | Commencez par une règle simple, que vous pouvez tester sans bloquer toute la démonstration. |
Ressources communes et configuration du provider
google_project_service active les API nécessaires, et google_service_account crée une identité dédiée. Donnez à chaque identité les permissions dont elle a besoin, au bon niveau, plutôt qu’un rôle Owner ou Editor sur tout le projet.
Pour l’observabilité, il vous faudra un dashboard, une politique d’alerte et un canal de notification. Vérifiez que la notification arrive réellement.
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "8.6.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
Cette version est un exemple, vérifié lors de la préparation du support. Contrôlez la dernière version publiée avant le TP. Déclarez vos variables et commitez .terraform.lock.hcl. Si votre projet utilise déjà une autre version, n’en changez pas sans relire le plan et les notes de migration. En local, utilisez ADC ; en CI, une identité fédérée. Jamais de clé JSON dans le code.
Critères d’évaluation
L’évaluation porte sur ce que le groupe a réalisé, sur ses résultats et sur la façon dont il les explique. Il n’y a pas de barème chiffré.
| Critère | Ce qui doit être observable |
|---|---|
| Cadrage et architecture | Des hypothèses explicites, des flux lisibles, des alternatives et des compromis documentés. |
| Modules et reproductibilité | Des responsabilités cohérentes, des interfaces claires, un état distant, des versions figées et un redéploiement documenté. |
| Pipeline Terraform | Une validation automatique, un plan relu puis appliqué tel quel, et des identités CI limitées. |
| Réseau, IAM et secrets | Des expositions justifiées, le moindre privilège, aucun secret dans Git et un state protégé. |
| Charge et résilience | Un protocole reproductible, des mesures avant et après, un incident analysé et un retour au service démontré. |
| Observabilité | Un dashboard utile, des seuils justifiés, une notification vérifiée et une procédure de diagnostic. |
| Coût et périmètre | Des ordres de grandeur, les limites du laboratoire, la destruction et le contrôle des ressources restantes. |
| Présentation et défense collective | Le groupe tient ses 20 minutes et montre ce qui fonctionne vraiment, avec ses limites. Chaque membre explique les choix, y compris le code proposé par un LLM. |