5) Web Performance & CDN
CDN (Akamai/Cloudflare/Fastly), cache HTTP & reverse proxies (Nginx/HAProxy/Varnish), edge computing, HTTP/2/3, image/media optimisation, observabilité et patterns e-commerce.
CDN — Anycast, Cache hierarchy & Shielding
- Anycast : la même IP BGP est annoncée depuis plusieurs PoPs → routage vers le PoP au RTT minimal (pas un round-robin DNS).
- Hiérarchie de cache : edge → mid-tier → shield. Le shield absorbe les MISS et protège l’origine (réduction des connexions et des pics).
- Clé de cache “propre” : host + path + query triée (liste blanche) + (quelques) headers stables.
- Ignorer les paramètres de tracking (
utm_*,fbclid,gclid). - Limiter
VaryàAccept,Accept-Encoding, éventuellementAccept-Language(ou cookie → headerX-Lang). - Ne jamais varier sur
Authorizationsi évitable (préférer des claims copiées en header dédié).
- Ignorer les paramètres de tracking (
- TTL matrix :
- HTML : court (30–120s) +
stale-while-revalidate/stale-if-error. - Assets versionnés : très long (1 an) +
immutable. - API GET : 30–120s si idempotent, sinon revalidation (
ETag/Last-Modified).
- HTML : court (30–120s) +
- Compression & négociation : Brotli (HTML/CSS/JS), gzip fallback ; pour images, format=auto ou
Accept: image/avif/webp. - Anti-stampede : request collapsing (coalescing), grace (servir du stale pendant refresh) et verrou de cache côté PoP.
# Pseudo-clé normalisée (ex. Worker)
const url = new URL(request.url);
const whitelist = ["page","sort","size","color"];
[...url.searchParams.keys()].forEach(k => { if(!whitelist.includes(k)) url.searchParams.delete(k) });
const cacheKey = `${url.host}|${url.pathname}|q=${[...url.searchParams].sort().join("&")}|a=${request.headers.get("Accept")||"*"}|ae=${request.headers.get("Accept-Encoding")||"*"}`
👉 KPI côté CDN : edge HIT%, shield HIT%, origin egress, TTFB p95, MISS causas (query/cookies/Vary).
- Origin Shielding : un unique PoP “shield” parle à l’origine → réduction du fan-out et meilleure stabilité en purge.
- Implémentations : Fastly (Origin Shield), Cloudflare (Tiered Cache), Akamai (Mid-Tier), CloudFront (Regional Edge Caches).
- Sécurité origine :
- Allowlist des IP/ASN CDN, ou mTLS (cert client) entre CDN ↔ origine.
- Origine privée : tunnel/privatelink/VPN (Cloudflare Tunnel, AWS PrivateLink, etc.).
- Perf origine :
- Keep-alive + HTTP/2 multiplexé ; tailles de buffers adaptées ; reuse de session TLS.
- Désactiver la compression si le CDN compresse (évite double gzip).
- Réponses cache-friendly :
Cache-Control,ETag,Vary,Age. - Support
206 Partial Contentpour les médias (range requests).
- Haute dispo : backends multiples (primary/secondary), health checks dédiés (
/health) et stratégie de failback avec hystérésis.
# Nginx origine — réponses cache-friendly
add_header Cache-Control "public, s-maxage=60, stale-while-revalidate=120" always;
add_header ETag $upstream_http_etag;
add_header Vary "Accept, Accept-Encoding";
# Eviter double compression si CDN compresse :
gzip off; brotli off;
⚠️ Les cookies sur HTML invalident souvent le cache. Isoler les fragments privés via ESI/edge worker.
- Geo-steering & compliance : rediriger par région (EU/US/APAC) pour RGPD, taxes, contenu localisé.
- RTT steering : choisir le PoP réellement le plus rapide (et pas uniquement le plus proche).
- Failover : actif/passif entre régions/origines, avec connection draining et warm-up de cache.
- Backbone privé : Cloudflare Argo / Akamai SureRoute → chemins inter-PoPs optimisés et plus stables.
- DNS Anycast couplé au CDN : résolutions rapides, faible variance ; TTL courts côté LB DNS pour accélérer les bascules.
- Multi-CDN (si utilisé) : harmoniser TTL/headers/purges, éviter les doubles MISS et les boucles d’origine.
# Exemple weighted steering (pseudo-API CDN)
{ "pools":[{"name":"eu","weight":95},{"name":"eu-new","weight":5}], "steering_policy":"random","session_affinity":true }
# Voir le PoP et l'état de cache (tous CDN confondus)
curl -I https://www.example.com/ | grep -i 'cf-ray\|x-served-by\|via\|x-cache\|age\|server-timing'
# Inspecter les headers/TTL & chaîne de revalidation
curl -si https://static.example.com/img.png | sed -n '1,40p'
# Test Anycast/chemin réseau (depuis un autre site/région)
mtr -rwc 5 www.example.com
# Forcer un PoP/resolve spécifique pour diagnostiquer
curl --resolve www.example.com:443:203.0.113.10 -I https://www.example.com/
# Cloudflare : vérifier le datacenter (colo)
curl -svo /dev/null https://www.example.com 2>&1 | grep -i 'cf-ray\|colo'
# Fastly/Akamai : X-Served-By et X-Cache détaillent POP et HIT/MISS
👉 Enrichir la réponse avec Server-Timing (cdn; cache; origin) pour attribuer le TTFB, et tracer le cf-ray/x-served-by dans vos logs d’accès.
HTTP Caching — Cache-Control, ETag, Vary, SWR/SIE, Purge
Cache-Controlmax-age= navigateur ;s-maxage= proxy/CDN.public= cacheable partout ;private= navigateur uniquement.no-store= jamais en cache (auth, panier) ;no-cache= cacheable mais revalidation à chaque fois.
- SWR (
stale-while-revalidate) : servir du stale pendant le refresh asynchrone. - SIE (
stale-if-error) : en erreur (5xx, timeout) → servir version expirée. - Vary minimal : seulement
Accept,Accept-Encoding, éventuellementAccept-Language. ÉviterVary: Authorization(explosif). - Matrice TTL : HTML 30–120s + SWR/SIE ; Assets versionnés 1 an +
immutable; API GET 30–120s ou revalidation ; médias avec206. - Anti-patterns : cookies sur HTML public, query tracking (
utm_*), ETag sensibles à l’ordre des champs JSON, purge globale.
# Nginx — micro-caching + background update (anti-stampede)
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=HTML:200m max_size=10g inactive=10m use_temp_path=off;
map $http_cookie $bypass { default 0; ~*"(session|cart|auth)=" 1; } # isole le privé
server {
listen 443 ssl http2;
location / {
proxy_pass http://origin;
proxy_set_header Host $host;
proxy_cache HTML;
proxy_cache_bypass $bypass;
proxy_cache_lock on; # collapse
proxy_cache_valid 200 1m; # micro-TTL
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on; # SWR-like
add_header X-Cache $upstream_cache_status always; # HIT/MISS/BYPASS/EXPIRED
}
}
# ETag / Last-Modified (304)
curl -I https://www.example.com/app.js
curl -I -H "If-None-Match: <etag>" https://www.example.com/app.js
curl -I -H "If-Modified-Since: Tue, 10 Sep 2024 10:00:00 GMT" https://www.example.com/app.css
# Serveur — bonnes pratiques (pseudo)
ETag: W/"sha256:ab12..." # weak ETag = stable si recompile identique
Last-Modified: Tue, 10 Sep 2024 10:00:00 GMT
Cache-Control: public, max-age=0, s-maxage=0, must-revalidate
# Si inchangé → 304 Not Modified (sans body)
# Varnish — grace/reuse pendant revalidation
sub vcl_backend_response {
if (beresp.status == 200) {
set beresp.ttl = 60s;
set beresp.grace = 120s; # SIE/SWR côté Varnish
}
}
- Utiliser ETag weak pour éviter des MISS inutiles (même payload, hash différent).
- Coupler ETag + Last-Modified (fallback robuste).
- Réponses 304 doivent être **sans** body, et conserver les headers de validation.
# Fastly — hard purge & soft purge
curl -X PURGE -H "Fastly-Key: <token>" -H "Surrogate-Key: PRODUCT:42" https://www.example.com/
curl -X PURGE -H "Fastly-Key: <token>" -H "Fastly-Soft-Purge: 1" -H "Surrogate-Key: PRODUCT:42" https://www.example.com/
# Cloudflare — purge par tags (API v4)
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE/purge_cache" \
-H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
--data '{"tags":["PRODUCT:42","CATEGORY:12"]}'
# Akamai — Fast Purge
curl -X POST -H "Content-Type:application/json" -H "Authorization: EG1-HMAC-SHA256 ..." \
--data '{"objects":["/images/logo.png"]}' "https://api.ccu.akamai.com/ccu/v3/invalidate/url/production"
# CloudFront — invalidation ciblée
aws cloudfront create-invalidation --distribution-id D123 --paths "/products/42*"
⚠️ Prioriser purges ciblées (par tag/clé) + origin shielding. Journaliser les purges, et utiliser retry/backoff.
# 1) HTML SSR (cache court) + SWR/SIE
Cache-Control: public, s-maxage=60, stale-while-revalidate=120, stale-if-error=600
Surrogate-Control: max-age=60, stale-while-revalidate=120
Vary: Accept, Accept-Encoding
# 2) Assets versionnés (JS/CSS/img) — long TTL + immutable
Cache-Control: public, max-age=31536000, immutable
# 3) API GET cacheable + revalidation
Cache-Control: public, s-maxage=120, stale-while-revalidate=300, stale-if-error=600
ETag: W/"prod-42-v15"
Vary: Accept, Accept-Encoding, X-Region, X-Currency
# 4) Contenu privé (panier/compte)
Cache-Control: no-store, private
# 5) 404/410 négatifs cacheables (évite martelage)
Cache-Control: public, s-maxage=60
✅ Pattern recommandé : HTML court TTL + API GET cacheables + fragments edge ; assets versionnés très long TTL.
Reverse Proxy — Nginx, HAProxy, Varnish
# Nginx reverse proxy + cache
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:100m max_size=5g inactive=60m use_temp_path=off;
map $request_uri $nocache { default 0; ~*"/auth|/admin" 1; }
server {
listen 443 ssl http2;
location / {
proxy_pass http://origin;
proxy_set_header Host $host;
proxy_cache STATIC;
proxy_cache_bypass $nocache;
proxy_cache_lock on;
proxy_cache_valid 200 301 302 10m;
add_header X-Cache-Status $upstream_cache_status;
}
}
- proxy_cache_lock : évite le thundering herd (plusieurs clients déclenchent 1 seule requête origine).
- split clients : possible de diriger un % de trafic vers une nouvelle version.
- microcaching : TTL court (1s–5s) utile pour API à fort trafic.
- Headers : toujours ajouter
X-Cache-Statuspour debug (HIT / MISS / BYPASS).
# HAProxy — LB + health + stickiness
frontend fe_https
bind :443 ssl crt /etc/ssl/cert.pem alpn h2,http/1.1
default_backend be_app
backend be_app
balance roundrobin
option httpchk GET /health
http-check expect status 200
server app1 10.0.1.10:443 ssl verify none check
server app2 10.0.1.11:443 ssl verify none check
stick-table type ip size 1m expire 30m store http_req_rate(10s)
http-request track-sc0 src
acl abuse sc_http_req_rate(0) gt 50
http-request deny if abuse
- Load-balancing : round-robin, leastconn, hash sur cookie/IP pour stickiness.
- Health checks : HTTP, TCP, SSL → détection fine des pannes.
- Rate limiting : stick-tables pour protéger contre abus/DDoS.
- Offloading TLS : support ALPN (HTTP/2), gestion de certificats, ciphers modernes.
# Varnish — VCL (simplifié)
vcl 4.1;
backend default { .host = "origin"; .port = "80"; }
sub vcl_recv {
if (req.url ~ "(?i)/auth|/admin") { return (pass); }
set req.http.X-Forwarded-Proto = "https";
}
sub vcl_backend_response {
if (beresp.status == 200) { set beresp.ttl = 10m; }
}
sub vcl_deliver {
set resp.http.X-Cache = obj.hits > 0 ? "HIT" : "MISS";
}
- VCL language : flexible (regex, headers, TTL dynamiques).
- Grace mode : sert un objet expiré pendant que le backend est en erreur.
- Ban / Purge : fine-grained invalidation (regex sur URL, tags via Surrogate-Key).
- Extensions : VMODs (GeoIP, Lua, Elasticache integration).
# Gzip/Brotli + HSTS (Nginx)
gzip on;
gzip_types text/plain text/css application/json application/javascript;
brotli on;
brotli_types text/plain text/css application/json application/javascript image/svg+xml;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- Brotli : meilleur taux de compression que gzip (surtout HTML/CSS/JS).
- HSTS : impose HTTPS + sous-domaines → sécurité accrue.
- OCSP Stapling : améliore performance TLS.
- ALPN : support HTTP/2 et HTTP/3 (QUIC).
Edge Computing — Workers / Functions
export default {
async fetch(req, env, ctx) {
const url = new URL(req.url);
// A/B testing simple
const bucket = Math.random() < 0.5 ? "A" : "B";
url.pathname = bucket === "A" ? "/v1" : "/v2";
const res = await fetch(url.toString(), req);
// Cache hint
const headers = new Headers(res.headers);
headers.set("Cache-Control", "public, s-maxage=60, stale-while-revalidate=120");
return new Response(res.body, {status: res.status, headers});
}
}
- Déploiement global en < 30s, sans serveur → JavaScript, WASM, Durable Objects.
- Intégrations : KV, D1 (SQLite distribué), R2 (object storage), Queues.
- Limites : 10–50ms CPU time, sandbox V8, pas d’accès natif TCP/UDP.
- Idéal pour : réécritures d’URL, auth JWT, HTMLRewriter, feature flags.
// Fastly Compute@Edge (JS) — réécriture d'URL & clé de cache
import { env } from "fastly:env";
addEventListener("fetch", event => event.respondWith(handleRequest(event)));
async function handleRequest(event) {
let req = event.request;
let url = new URL(req.url);
if (url.pathname.startsWith("/img/")) {
url.searchParams.set("w", "800");
}
req = new Request(url.toString(), req);
return fetch(req);
}
- Langages : Rust (perf + sécurité), JavaScript (plus simple).
- Très faible latence (WASM sandbox, quelques ms).
- Accès natif à la cache clé (surrogate-key, VCL héritée).
- Use cases : image resizing, geo-routing, API shaping.
// Lambda@Edge (viewer-request) — redirection HTTP→HTTPS
exports.handler = (event, context, callback) => {
const req = event.Records[0].cf.request;
if (req.headers["cloudfront-forwarded-proto"][0].value !== "https") {
return callback(null, { status: "301", headers: { location: [{ value: "https://" + req.headers.host[0].value + req.uri }] } });
}
return callback(null, req);
};
- Déclenchement aux 4 étapes CloudFront : viewer-request, viewer-response, origin-request, origin-response.
- Langage : Node.js (runtime limité, cold starts parfois problématiques).
- Accès direct à l’écosystème AWS (S3, DynamoDB, Secrets Manager).
- Limite : latence > Workers/Compute@Edge car dépend CloudFront propagation.
- Réécritures & routing : A/B testing, multi-versioning, blue/green.
- Sécurité : auth fine (JWT, OAuth2), bot mitigation, rate limiting.
- Personnalisation : headers dynamiques (GeoIP, device), pricing local.
- Optimisation : image resizing, HTML injection, edge includes.
- Failover : rediriger trafic si origine down, fallback CDN.
- Compliance : traitement RGPD en Europe, data residency.
👉 L’Edge devient un mini data-center distribué : réduire latence, rapprocher la logique métier de l’utilisateur, limiter le trafic vers l’origine.
Optimisation Images & Media
- Formats :
AVIF(meilleur ratio) >WebP>JPEG(fallback). PNG uniquement si transparence/lossless requis. - Responsive : utiliser
<picture>,srcset+sizes, etwidth/heightpour réserver la place (évite CLS). - Client Hints : activer
Accept,DPR,Width,Viewport-Width(côté serveur/CDN) +Varycorrespondant. - Compression : AVIF/WebP côté CDN (Cloudflare Image Resizing, Fastly IO, imgproxy/Thumbor). Éliminer EXIF; gérer profils colorimétriques (sRGB).
- Performance :
loading="lazy",decoding="async",fetchpriority="high|low"pour héro vs. below-the-fold. - SEO/Accès : attribut
altdescriptif;<link rel="preload" as="image">pour les héro; placeholders LQIP/BlurHash.
# Réponses HTTP côté CDN/origin (exemple)
Accept-CH: DPR, Width, Viewport-Width
Vary: Accept, DPR, Width, Viewport-Width
- À la volée : privilégier services d’image resizing (Cloudflare/ Fastly IO / imgproxy / Thumbor) + cache agressif.
- Clé de cache : inclure format (avif/webp), dims, qualité, DPR dans la clé (évite collisions).
- Sharpen post-resize & limites de dimensions pour prévenir abus.
- Sprite/Icons : SVG inline quand possible; sinon
symbolsprite.
# Nginx — image filter (simple, CPU ++, préférer libvips côté service)
location /img/ {
image_filter resize 800 -; # largeur 800 px
image_filter_sharpen 50;
proxy_pass http://origin;
add_header Cache-Control "public, s-maxage=2592000, stale-while-revalidate=86400";
}
# Cloudflare Image Resizing (ex.)
https://cdn.example.com/cdn-cgi/image/width=800,quality=70,format=auto/img/photo.jpg
# Fastly IO (ex.)
https://cdn.example.com/ios/image/upload/w=800,q=70,f=auto/img/photo.jpg
# imgproxy (ex.)
https://img.example.com/insecure/rs:fit:800:0/q:70/f:auto/plain/https://origin/img/photo.jpg
- Codecs : AV1 (qualité/top, CPU +), VP9 (bon), H.264 (fallback maximal). Conteneurs : MP4 (fMP4/CMAF), WebM.
- Streaming : HLS/DASH (segments 2–4s), GOP alignés, key-int=seg*fps, bitrate ladder selon VMAF.
- Delivery :
Cache-Controlagressif sur segments,stale-if-errorpour robustesse. - UX :
preload="metadata",playsinline,muted autoplaysi nécessaire;poster+track(VTT) pour accessibilité. - Thumbs : VTT thumbnails, storyboard JPEG; audio-only rendition pour mobilité/faible débit.
# ffmpeg — ladder HLS (simplifié)
ffmpeg -i input.mp4 \
-filter:v:0 scale=w=1920:h=-2 -c:v:0 libx264 -b:v:0 6000k -g 96 -keyint_min 96 -sc_threshold 0 -c:a aac -b:a 128k \
-filter:v:1 scale=w=1280:h=-2 -c:v:1 libx264 -b:v:1 3000k -g 96 -keyint_min 96 -sc_threshold 0 -c:a aac -b:a 128k \
-filter:v:2 scale=w=854:h=-2 -c:v:2 libx264 -b:v:2 1500k -g 96 -keyint_min 96 -sc_threshold 0 -c:a aac -b:a 96k \
-filter:v:3 scale=w=640:h=-2 -c:v:3 libx264 -b:v:3 800k -g 96 -keyint_min 96 -sc_threshold 0 -c:a aac -b:a 64k \
-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 v:3,a:3" \
-master_pl_name master.m3u8 \
-hls_time 4 -hls_playlist_type vod -hls_segment_type fmp4 \
-hls_segment_filename "v%v/seg_%03d.m4s" -use_template 1 -use_timeline 1 \
-f hls v%v/stream.m3u8
# HTML5 vidéo (access & perf)
# Headers recommandés pour segments
Cache-Control: public, s-maxage=86400, stale-if-error=600
Timing-Allow-Origin: *
HTTP/2, HTTP/3 (QUIC) & Tuning TCP/TLS
# Nginx — activer HTTP/2 + réglages clés
server {
listen 443 ssl http2;
server_name example.com;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EECDH+AESGCM:EECDH+CHACHA20:!aNULL:!MD5'; # TLS 1.2
ssl_ecdh_curve X25519:secp384r1;
ssl_session_cache shared:SSL:50m; # ~400k sessions
ssl_session_timeout 1d;
ssl_stapling on; ssl_stapling_verify on;
# H2 tuning
http2_max_concurrent_streams 256;
http2_recv_buffer_size 512k;
http2_idle_timeout 30s;
add_header Alt-Svc 'h2=":443"; ma=86400' always; # annonce H2
}
- Multiplexing & HPACK : réduit head-of-line au niveau HTTP, compresse headers.
- Priorités : privilégier les ressources critiques (CSS, above-the-fold). Utiliser l’en-tête
Priority(RFC 9218) si support CDN. - Push HTTP/2 : déprécié dans la pratique → préférer
preload+ priorité. - Tests :
curl -I --http2 https://example.com,nghttp -vn https://example.com/,h2load -n 100 -c 10. - Pièges : objets trop nombreux → privilégier bundling/priority hints; surveiller TTFB et files d’attente h2.
# Nginx — HTTP/3 (QUIC) + Alt-Svc
server {
listen 443 quic reuseport; # UDP/QUIC
listen 443 ssl http2; # fallback TLS/TCP
http3 on;
ssl_protocols TLSv1.3; # H3 = TLS 1.3 only
add_header Alt-Svc 'h3=":443"; ma=86400; persist=1' always;
# 0-RTT (attention aux replays, GET idempotents uniquement)
ssl_early_data on;
# H3 buffer/timeout
http3_hq on;
quic_gso on;
keepalive_timeout 30s;
}
- UDP + QUIC : latence de connexion plus faible, meilleure perf en mobilité/perte.
- 0-RTT : n’autoriser que des requêtes idempotentes (GET/HEAD).
- MTU/PMTUD : QUIC min MTU ~1200 octets → éviter fragmentation; surveiller DF/ICMP.
- Priorité H3 : via l’en-tête
Priority(urgency/importance), si supporté par le proxy/CDN. - Tests :
curl -I --http3 https://example.com,h3load -n 100 -c 10,ss -u '( sport = :443 )'.
# Linux — congestion control & buffers
sysctl -w net.core.somaxconn=4096
sysctl -w net.core.rmem_default=1048576 net.core.wmem_default=1048576
sysctl -w net.core.rmem_max=268435456 net.core.wmem_max=268435456
sysctl -w net.ipv4.tcp_rmem='4096 1048576 268435456'
sysctl -w net.ipv4.tcp_wmem='4096 1048576 268435456'
sysctl -w net.ipv4.tcp_congestion_control=bbr # ou bbr2 si dispo
sysctl -w net.ipv4.tcp_mtu_probing=1 # PMTUD agressif
sysctl -w net.ipv4.ip_local_port_range='10240 65535'
# UDP (QUIC) — tampons élevés
sysctl -w net.ipv4.udp_mem='4096 1048576 268435456'
sysctl -w net.ipv4.udp_rmem_min=131072
sysctl -w net.ipv4.udp_wmem_min=131072
# TLS — modern config Nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256';
ssl_ecdh_curve X25519:secp384r1;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets on; # ⚠️ gérer rotation
Global Load Balancing & Failover
- Modèles :
- DNS-based GSLB (Route 53, Cloudflare Load Balancer, NS1, Akamai Edge DNS) : steering par latence, geo, poids.
- Anycast + proxy LB (GCP/GCLB, Azure Front Door, Cloudflare) : décisions L7 + failover plus rapide que DNS pur.
- Active/Active multi-régions (réplication DB, stateless côté app) vs Active/Passive (RTO plus long mais plus simple).
- Steering : Latency-based, Geo fence (RGPD), Weighted canary (ex : 5% EU-West → EU-North), Proximity + disponibilité PoP.
- DNS TTL : utiliser TTL courts (20–60 s) pour accélérer les bascules, mais tenir compte du caching résolveur (min TTL, negative TTL).
- Écueils : EDNS Client Subnet partiellement supporté; IP géolocalisée ≠ réalité réseau; CGNAT peut biaiser le routage.
- Failover : planifier failback avec hystérésis (seuils “N up checks” avant retour), connection draining, warm-up des caches.
# Route 53 — Latency/Health-basic (via CLI, pseudo-exemple)
aws route53 change-resource-record-sets --hosted-zone-id ZONE --change-batch '{
"Changes":[{"Action":"UPSERT","ResourceRecordSet":{
"Name":"app.example.com","Type":"A","SetIdentifier":"eu-west",
"Region":"eu-west-1","AliasTarget":{"HostedZoneId":"ALBZONE","DNSName":"alb-eu.example.com","EvaluateTargetHealth":true},
"HealthCheckId":"HC_EU"}}]}'
# Cloudflare LB — Pool + Monitor (curl, simplifié)
curl -X POST https://api.cloudflare.com/client/v4/user/load_balancers/pools \
-H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
--data '{"name":"eu-pool","origins":[{"name":"eu1","address":"203.0.113.10"}],"check_regions":["WEU"]}'
curl -X POST https://api.cloudflare.com/client/v4/user/load_balancers/monitors \
-H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
--data '{"type":"https","method":"GET","path":"/health","interval":30,"retries":2,"timeout":5,"expected_body":"","expected_codes":"200"}'
👉 Bonnes pratiques : séparer pools par région, définir “fallback pool” global, activer session affinity si nécessaire et draining lors des maintenances.
# HAProxy — health + out-of-band checks + hysteresis
backend be_app
balance leastconn
option httpchk GET /health
http-check send-state
http-check expect status 200
default-server rise 3 fall 2 inter 3000 fastinter 1000 downinter 5000
server app1 10.0.1.10:443 ssl verify none check
server app2 10.0.1.11:443 ssl verify none check
# Nginx OSS — passive HC (via proxy_next_upstream) / active HC = Nginx Plus
location / {
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_next_upstream_tries 2;
}
# Synthetic check (curl) — SNI/Host + timing
curl -sS -o /dev/null -w "code:%{http_code} ttlb:%{time_starttransfer}\n" \
-H "Host: app.example.com" https://203.0.113.10/health
- Health endpoints : indépendants de la DB si possible; retourner 200 uniquement si dépendances critiques OK (cache, MQ, DB ping léger).
- Paramètres : rise/fall pour éviter flapping; timeouts courts (2–5 s), intervalle 15–30 s.
- Réseau : vérifier SNI/TLS et Host, certains origins répondent différemment sans SNI.
- Régional : checks depuis plusieurs régions pour refléter la réalité (Cloudflare check_regions, NS1 network groups).
- Obs. : stocker up/down reason, corréler avec métriques (CPU, 5xx, p99 RTT) et tracer les bascules.
# HAProxy — cookie-based affinity (L7)
backend be_app
cookie SRV insert indirect nocache secure httponly
server app1 10.0.1.10:443 ssl verify none check cookie s1
server app2 10.0.1.11:443 ssl verify none check cookie s2
# HAProxy — consistent hashing (IP/HTTP header/JWT)
backend be_api
balance hdr(X-User) # ou balance uri / balance hdr(Authorization)
hash-type consistent
# Nginx — ip_hash (basique, attention CGNAT)
upstream api {
ip_hash;
server 10.0.1.10:443;
server 10.0.1.11:443;
}
- Types d’affinité :
- Cookie L7 (recommandé pour web : compatible proxy/CDN).
- Consistent hashing sur header/ID utilisateur/JWT pour répartir sans cookie.
- IP hash : simple mais fragile (CGNAT, mobiles, IPv6 privacy).
- État applicatif : externaliser sessions (Redis) ou JWT stateless pour permettre bascule inter-régions.
- Draining : avant maintenance, marquer serveur DRAIN (ou poids=0) et attendre fin des connexions (WebSocket/HTTP2).
- WebSocket/HTTP2 : affinité par connexion (H2/H3), garder idle timeout adapté (30–120 s) et pings applicatifs.
# Diagnostics bascule/propagation
dig +short app.example.com
dig @1.1.1.1 app.example.com +trace
kdig -d app.example.com
# Vérifier le pool/PoP servi (selon fournisseur)
curl -I https://app.example.com | grep -i "cf-ray\|x-served-by\|via\|x-lb-pool"
👉 Vérifier temps de propagation DNS réel (résolveurs publics & ISP), monitorer le taux d’erreurs 5xx par région, et tracer les événements de failover/failback.
Sécurité en bordure — DDoS, WAF & Gestion des bots
- Couches : L3/4 (volumétrique : UDP/SYN/ACK, amplification NTP/DNS/SSDP) + L7 (HTTP flood, cache-busting, GraphQL heavy queries).
- Anycast + scrubbing : absorption globale en bordure ; activer auto mitigation, connection tracking, SYN cookies.
- 0-RTT + challenge : activer mode “Under Attack”/IUAM (JS/Turnstile) en cas de pic L7 ; refuser corps > N Ko sur routes sensibles.
- Edge rules : bloquer/ralentir par ASN, pays, réputation IP, JA3/TLS-FP, méthode/UA anormaux, absence d’Accept-Encoding/Referer.
- Failover : TTL DNS court, origin shielding, circuit breaker sur backend, caps de débit par région.
- Observabilité : métriques p95/p99, req/s par route, taux 429/403/5xx, “attack windows” (minute buckets), logs échantillonnés.
# Nginx — protections L7 (extraits)
limit_req_zone $binary_remote_addr zone=reqs:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conns:10m;
server {
location /login {
limit_req zone=reqs burst=20 nodelay;
limit_conn conns 20;
client_body_timeout 5s; client_max_body_size 256k;
}
# Drop payloads trop gros hors uploads
location ~* ^/(search|api)/ { if ($request_length > 8192) { return 413; } }
}
# Cloudflare WAF — règles (pseudo)
- action: block expr: (http.request.uri.path contains "/wp-admin" and not ip.geoip.asnum in {AS1234})
- action: challenge expr: (http.request.method in {"POST"} and http.request.uri.path starts_with "/auth")
- action: bypass expr: (cf.bot_management.verified_bot) # éviter faux positifs
# AWS WAF — exemples managés + custom
- managed: AWSManagedRulesCommonRuleSet
- custom: size_constraint (uri, GT, 2048) -> block
- custom: rate_based (scope: ip, limit: 1000 req/5m, path: "/api/")
# Fastly WAF (VCL pseudo)
if (req.url ~ "(?i)(union select|sleep\()") { return (synth(403)); }
if (req.method != "GET" && req.method != "HEAD" && req.method != "POST") { return (synth(405)); }
- Politiques : OWASP CRS activé → ajouter règles spécifiques (path sensibles :
/admin,/wp-*,/graphql). - Exclusions : whitelists par verified bots, IP d’outils (monitoring), endpoints multipart.
- False positives : démarrer en log, passer en challenge, puis block progressivement ; tracer rule_id & request_id.
- Headers : bloquer “exotic methods”, forcer
Content-Typeattendu, limiterrequest_body& JSON depth. - API : mTLS (client cert), HMAC signature, schema validation (OpenAPI/GraphQL depth & cost).
# HAProxy — rate limit IP + sunrise (hystérésis)
stick-table type ip size 1m expire 10m store http_req_rate(10s),conn_rate(10s)
acl abusive sc_http_req_rate(0) gt 100
acl bursty sc_conn_rate(0) gt 30
http-request deny if abusive || bursty
# Nginx — token bucket par route
limit_req_zone $binary_remote_addr zone=perip:20m rate=30r/m;
location /api/ {
limit_req zone=perip burst=60 nodelay;
add_header X-RateLimit-Limit 30; add_header X-RateLimit-Policy "30/m";
}
# Cloudflare — bot score & firewall (pseudo)
if (cf.bot_management.score < 15) { action=challenge; }
if (http.user_agent eq "") { action=block; }
- Détection bot : fingerprint TLS/JA3, intégrité headers, comportement (req cadence, chemin, entropie UA), cookies de preuve de travail/Turnstile.
- Affinité & sessions : éviter de casser les sessions légitimes → appliquer rate-limit par clé (user_id/JWT) plutôt que par IP seule.
- Scraping : robots.txt + “honey paths”, tarpit (latence progressive), watermarking, limites par ASN/TOR.
- GraphQL & recherche : limiter la depth, le cost (complexité), et la fenêtre QPS par token.
- URLs signées pour médias/exports ; expiration courte + horodatage/nonce.
/login, /search, /graphql). (4) Dégrader gracieusement : réduire fonctionnalités coûteuses, servir cache statique. (5) Surveiller p95/p99, 429/403/5xx, et lever le mode atténuation par paliers.# Diagnostics rapides
# 1) Voir patterns & codes
grep -E '"(GET|POST) /(login|graphql|search)"' /var/log/nginx/access.log | awk '{print $1,$9}' | sort | uniq -c | sort -nr | head
# 2) ASNs & pays dominants
zcat access.log.*.gz | awk '{print $1}' | sort | uniq -c | sort -nr | head
# 3) Cloudflare/Fastly headers utiles
curl -I https://www.example.com | grep -i "cf-ray\|cf-bot-score\|x-cache\|via"
Observabilité — RUM, Core Web Vitals, Logs CDN & Synthetic
- Field vs Lab : RUM = réel (utilisateurs), Lab = contrôle (Lighthouse). Ciblez p75 pour CWV.
- Vitals : LCP < 2.5s, CLS < 0.1, INP < 200ms (seuils “Good”).
- Attribution : identifier l’élément LCP (URL image, font), source du CLS (frames, ads), interaction INP (click vs input).
- Échantillonnage : 1–5% trafic suffisent; anonymiser (pas d’URL PII) ; batcher via
sendBeacon.
<script type="module">
// RUM minimal étendu (LCP, INP, CLS) + attribution + batching
import {onLCP, onINP, onCLS} from "https://unpkg.com/web-vitals@3/dist/web-vitals.attribution.iife.js";
const buf=[];
function ship(evt){
buf.push(evt);
if (buf.length >= 10 || document.visibilityState === "hidden") {
navigator.sendBeacon("/rum", JSON.stringify({t:Date.now(), samples:buf.splice(0)}));
}
}
function payload(metric){
const d = metric.attribution || {};
return {
n: metric.name, v: metric.value, id: metric.id,
el: d.lcpEntry?.url || d.eventTarget || d.element || null,
url: location.pathname, ua: navigator.userAgentData?.brands?.[0]?.brand || "na",
net: navigator.connection?.effectiveType, dpr: window.devicePixelRatio
};
}
onLCP((m)=>ship(payload(m)), {reportAllChanges:true});
onINP((m)=>ship(payload(m)));
onCLS((m)=>ship(payload(m)), {reportAllChanges:true});
</script>
👉 stocker côté serveur : name,value,id,url,el,net,dpr,ts, puis agréger par pays/UA/page, percentile p75 par jour.
- Champs clés : PoP/colo, RTT, TTFB,
X-Cache(HIT/MISS),cf-ray|x-served-by|via, bytes,cache_age,origin_status. - Pipeline : CDN → (Logpush/Realtime) → S3/R2 → ETL → OLAP (BigQuery/ClickHouse) → dashboards.
- Dimensions : path, tag/Surrogate-Key, pays/ASN, UA, status, colo, host (multi-tenant).
# Cloudflare Logpull → R2/S3 (ex. curl simplifié)
curl -H "Authorization: Bearer <TOKEN>" \
"https://api.cloudflare.com/client/v4/zones/<ZONE>/logs/received?start=...&end=..." | gzip > cf-logs.ndjson.gz
# Fastly Realtime API (hits/miss par service)
curl -H "Fastly-Key: <KEY>" https://api.fastly.com/stats/service/<SID>?from=1h | jq '.data.requests, .data.hits'
# Analyse rapide X-Cache / TTFB (ndjson)
zcat *.gz | jq -r '[.ClientRequestHost,.ClientRequestURI,.CacheStatus,.EdgeStartTimestamp,.EdgeEndTimestamp] | @tsv' \
| awk '{ttfb=$5-$4; print $1$2,$3,ttfb}' | sort | head
# BigQuery (ex) — taux de HIT par path
SELECT path, ROUND(100*SUM(IF(cache_status="HIT",1,0))/COUNT(*),1) AS hit_rate
FROM `proj.cdn.logs`
WHERE _PARTITIONDATE = CURRENT_DATE()
GROUP BY path ORDER BY hit_rate ASC LIMIT 50;
⚠️ corréler MISS avec clés de cache (query, cookies, Vary) et vérifier “origin shield” pour lisser la charge.
- Types : disponibilité (ping/HTTP), parcours UX (login/checkout), perf (TTFB/LCP/INP lab).
- Couverture : 3–5 régions / 2 FAI / 2 devices (desktop/mobile) ; fréquence 1–5 min (uptime), 15–30 min (parcours).
// k6 — perfs avec seuils (TTFB/LCP via nav timings simplifiés)
import http from "k6/http"; import { check, sleep } from "k6";
export let options = {
thresholds: { http_req_failed: ["rate<0.5%"], http_req_waiting: ["p(95)<200"] },
vus: 20, duration: "2m"
};
export default function(){
const res = http.get("https://www.example.com/");
check(res, { "status 200": (r)=>r.status===200, "p95 TTFB<200ms": (r)=>r.timings.waiting<200 });
sleep(1);
}
// Playwright — parcours login + trace
// npx playwright test --project=chromium --trace=on
import { test, expect } from "@playwright/test";
test("login flow", async ({ page }) => {
await page.goto("https://www.example.com");
await page.click('text=Sign in');
await page.fill('#email','user@test'); await page.fill('#pwd','***'); await page.click('text=Submit');
await expect(page.locator('text=Welcome')).toBeVisible();
});
// WebPageTest CLI (filmstrip, mobile)
# wpt -k KEY test https://www.example.com --mobile --location=Dulles_MotoG --runs 3 --video --metrics
👉 stocker résultats synthetic séparément de RUM ; alerter sur régression > X% vs baseline 7 jours.
- SLI (ce qu’on mesure) : disponibilité HTTP (2xx/3xx), TTFB p95, CWV p75 (LCP/CLS/INP), taux HIT CDN, erreurs 5xx.
- SLO (objectif) : ex. Uptime 99.95%, TTFB p95 < 200ms, LCP p75 < 2.5s, erreurs < 0.1%.
- Error budget = 1 − SLO. Pour 99.95% mensuel → budget ≈ 21.6 min d’indispo/mois.
- Burn rate : alerter si consommation du budget trop rapide (multi-fenêtres).
- Budgets perf : poids page, nb requêtes, JS main-thread, long tasks (>50ms), images non optimisées.
# Prometheus — alertes burn rate (ex. SLO 99.9% sur 30j)
# Fast burn (m=14.4 sur 5m/1h)
- alert: SLOErrorBudgetFastBurn
expr: (sum(rate(http_requests_total{code=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))) > (1-0.999)*14.4
for: 5m
labels: {severity: page}
annotations: {summary: "Fast burn > 1h budget", slo: "99.9%"}
# Slow burn (m=6 sur 30m/6h)
- alert: SLOErrorBudgetSlowBurn
expr: (sum(rate(http_requests_total{code=~"5.."}[30m])) / sum(rate(http_requests_total[30m]))) > (1-0.999)*6
for: 30m
labels: {severity: ticket}
✅ Astuce : afficher erreur budget restant et jours restants à burn rate courant dans le dashboard.
Patterns e-commerce — HTML cacheable & personnalisation
- HTML cacheable court : 30–120s avec
stale-while-revalidate/stale-if-error+ hole-punching pour les zones privées (panier, compte). - Fragments dynamiques : ESI/Edge Worker/HTMLRewriter pour injecter mini-blocs (panier qty, prix localisés, bannière).
- Clé de cache propre : normaliser l’ordre des filtres & query (ex:
sort=price|color=blue|size=m) ; ignorer paramètres de tracking (utm_*). - Listing/catalogue : cache par template + filtres majeurs ; paginations avec TTL court (ou désindexer SEO si très dynamiques).
- Personnalisation non bloquante : HTML cacheable “générique” + decorate côté edge/browser (prix pays, devise, promo) — éviter
Varyexplosifs. - Panier / compte :
Cache-Control: no-store; appels API séparés, courts et cacheables côté CDN avec token (voir onglet “API”). - Images : formats
avif/webp, resizing à la volée,immutablepour assets versionnés. - A/B & feature flags : router par cookie/segment côté edge, mais garder HTML commun ; injecter variante par fragment.
<!--#include virtual="/fragments/cart-count" -->
new HTMLRewriter().on('#cart-count', {
element(e){ e.setInnerContent(COOKIE_CART_QTY || '0'); }
}).transform(await fetch(origin));
👉 Objectif : maximiser le HIT HTML tout en gardant la personnalisation en bordure ou client sur de petits fragments.
- Tagging systématique : chaque réponse porte des tags (Surrogate-Key) :
PRODUCT:42,CATEGORY:12,COLLECTION:summer24. - Évènements : mise à jour prix/stock → enqueue d’un job de purge ciblée (tags) + invalidation API/images associées.
- Anti-stampede : utiliser origin shielding + grace (SWR/SIE) ; micro-jitter côté edge.
- Failback : en cas de purge massive, servir une version “safe” (cache warm) depuis le shield.
# Fastly — purge par Surrogate-Key
curl -X PURGE -H "Fastly-Key: <token>" \
-H "Surrogate-Key: PRODUCT:42 CATEGORY:12" https://www.example.com/
# Cloudflare — purge par tags (API)
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE/purge_cache" \
-H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
--data '{"tags":["PRODUCT:42","CATEGORY:12"]}'
# Varnish — BAN (regex)
varnishadm "ban req.http.Surrogate-Key ~ \"(PRODUCT:42|CATEGORY:12)\""
⚠️ Éviter la purge globale. Privilégier invalidations idempotentes, en batch, avec retry/backoff et journal d’audit.
- GET cacheables : fiches produit, stocks “near-real-time” (TTL 5–30s), prix par région ;
s-maxage+stale-while-revalidate. - GraphQL : APQ/persisted queries + cache key = (operationName + variables triées). Limiter depth/cost.
- Tokens : éviter
Vary: Authorization(explosif). Utiliser claims stables (segment, pays, devise) copiés en headers dédiés (ex:X-Segment) pour la clé. - Panier : endpoints séparés (
/cart) non cacheables mais edge KV possible pour d’anciens paniers (expiration courte). - Protection : quotas (rate-limit) par userId/service account, mTLS/HMAC pour webhooks & partenaires.
# Headers type pour API GET
Cache-Control: public, s-maxage=60, stale-while-revalidate=300, stale-if-error=600
Surrogate-Control: max-age=60
ETag: "prod-42-v15"
Vary: Accept-Encoding, X-Region, X-Currency
# Exemple Cloudflare Worker — clé de cache “safe”
const cacheKey = new Request(url.toString(), {
headers: { "X-Region": region, "X-Currency": currency, "Accept": req.headers.get("Accept") || "*" },
method: "GET"
});
let res = await caches.default.match(cacheKey);
if (!res) {
res = await fetch(url, req);
res = new Response(res.body, res);
res.headers.set("Cache-Control", "public, s-maxage=60, stale-while-revalidate=300");
event.waitUntil(caches.default.put(cacheKey, res.clone()));
}
return res;
# GraphQL — Persisted Query (clé = opName+vars)
POST /gql?hash=abc123
# côté edge, map hash→document; clé = "op:getProduct|id:42|region:eu"
