Réseaux modernes#
Les réseaux informatiques n’ont jamais évolué aussi vite. En quelques années, le software-defined networking a bouleversé la façon dont les réseaux de data centers sont construits ; eBPF a rendu le noyau Linux programmable sans recompilation ; VXLAN permet de superposer des réseaux virtuels sur n’importe quelle infrastructure IP ; et Kubernetes a créé ses propres abstractions réseau. Ce chapitre couvre les technologies qui définissent l’état de l’art des réseaux de 2025.
SD-WAN — Software-Defined Wide Area Network#
Le SD-WAN sépare le plan de contrôle (décisions d’acheminement centralisées) du plan de données (transport effectif des paquets), à l’image de ce que SDN fait pour les réseaux locaux.
Architecture#
fig, ax = plt.subplots(figsize=(12, 6))
ax.set_xlim(0, 12)
ax.set_ylim(0, 7)
ax.axis('off')
ax.set_title("Architecture SD-WAN — séparation plan de contrôle / données", fontsize=13, fontweight='bold', pad=12)
# Contrôleur central
ax.add_patch(mpatches.FancyBboxPatch((4, 5.5), 4, 1.2,
boxstyle="round,pad=0.15", facecolor='#4575b4', alpha=0.2,
edgecolor='#4575b4', linewidth=2.5))
ax.text(6, 6.1, "SD-WAN Controller (cloud)", ha='center', va='center',
fontsize=11, color='#4575b4', fontweight='bold')
ax.text(6, 5.7, "Visibilité centralisée · Politiques QoS · Routage applicatif",
ha='center', va='center', fontsize=8.5, color='#4575b4')
# Sites distants
sites = [
(1.5, 3, "Siège\n(HQ)"),
(5.5, 3, "Agence\n(Branch)"),
(9.5, 3, "Data Center\n(Cloud)"),
]
for x, y, label in sites:
ax.add_patch(mpatches.FancyBboxPatch((x-1.1, y-0.5), 2.2, 1.0,
boxstyle="round,pad=0.1", facecolor='#1a9850', alpha=0.2,
edgecolor='#1a9850', linewidth=2))
ax.text(x, y, label, ha='center', va='center', fontsize=9, color='#1a9850', fontweight='bold')
# CPE SD-WAN (équipements edge)
for x, y in [(1.5, 4.2), (5.5, 4.2), (9.5, 4.2)]:
ax.add_patch(mpatches.FancyBboxPatch((x-0.6, y-0.25), 1.2, 0.55,
boxstyle="round,pad=0.05", facecolor='#d73027', alpha=0.2,
edgecolor='#d73027', linewidth=1.5))
ax.text(x, y+0.05, "CPE SD-WAN", ha='center', va='center', fontsize=7.5, color='#d73027', fontweight='bold')
# Flèches contrôleur ↔ CPE (plan de contrôle)
for x in [1.5, 5.5, 9.5]:
ax.annotate("", xy=(x, 4.5), xytext=(6, 5.5),
arrowprops=dict(arrowstyle='<->', color='#4575b4', lw=1.5, linestyle='dashed'))
# Tunnels overlay (plan de données)
liens_wan = [
(2.6, 4.2, 4.4, 4.2, "MPLS"),
(2.6, 3.8, 4.4, 3.8, "Internet"),
(6.6, 4.2, 8.4, 4.2, "4G/5G"),
(6.6, 3.8, 8.4, 3.8, "Internet"),
]
for x1, y1, x2, y2, label in liens_wan:
ax.plot([x1, x2], [y1, y2], '-', color='#888888', linewidth=1.5, alpha=0.7)
ax.text((x1+x2)/2, y1+0.12, label, ha='center', fontsize=7.5, color='#555555')
ax.text(6, 1.8,
"Le contrôleur sélectionne dynamiquement le meilleur lien WAN\n"
"(MPLS, Internet, 4G/5G) selon la QoS applicative et les coûts.",
ha='center', va='center', fontsize=9, color='#333333',
bbox=dict(boxstyle='round,pad=0.4', facecolor='#f0f8ff', edgecolor='#4575b4'))
plt.tight_layout()
plt.savefig('_static/sdwan_archi.png', dpi=100, bbox_inches='tight')
plt.show()
eBPF — Berkeley Packet Filter étendu#
eBPF (extended Berkeley Packet Filter) est une technologie révolutionnaire du noyau Linux qui permet d’exécuter des programmes sandboxés dans le noyau sans modifier son code source ni charger de modules.
Principes fondamentaux#
fig, ax = plt.subplots(figsize=(12, 6))
ax.set_xlim(0, 12)
ax.set_ylim(0, 7)
ax.axis('off')
ax.set_title("Architecture eBPF — du code utilisateur au noyau Linux", fontsize=13, fontweight='bold', pad=12)
# Espace utilisateur
ax.add_patch(mpatches.FancyBboxPatch((0.3, 4.5), 5.4, 2.2,
boxstyle="round,pad=0.2", facecolor='#4575b4', alpha=0.08,
edgecolor='#4575b4', linewidth=2))
ax.text(3, 6.4, "Espace utilisateur", ha='center', fontsize=11, color='#4575b4', fontweight='bold')
# Espace noyau
ax.add_patch(mpatches.FancyBboxPatch((0.3, 0.3), 11.4, 3.9,
boxstyle="round,pad=0.2", facecolor='#d73027', alpha=0.05,
edgecolor='#d73027', linewidth=2))
ax.text(6, 3.8, "Noyau Linux (kernel space)", ha='center', fontsize=11, color='#d73027', fontweight='bold')
# Composants espace utilisateur
for x, y, label, col in [
(1.8, 5.6, "Programme C\n(source eBPF)", '#4575b4'),
(3.5, 5.6, "Compilateur\n(Clang/LLVM)", '#4575b4'),
(5.3, 5.6, "Bytecode\neBPF (.o)", '#4575b4'),
]:
ax.add_patch(mpatches.FancyBboxPatch((x-0.7, y-0.4), 1.4, 0.8,
boxstyle="round,pad=0.05", facecolor=col, alpha=0.2,
edgecolor=col, linewidth=1.5))
ax.text(x, y+0.05, label, ha='center', va='center', fontsize=8, color=col, fontweight='bold')
if x < 5.3:
ax.annotate("", xy=(x+0.85, y), xytext=(x+0.7, y),
arrowprops=dict(arrowstyle='->', color=col, lw=1.5))
# Vérificateur
ax.add_patch(mpatches.FancyBboxPatch((6.5, 4.7), 2.5, 1.0,
boxstyle="round,pad=0.1", facecolor='#1a9850', alpha=0.2,
edgecolor='#1a9850', linewidth=2))
ax.text(7.75, 5.2, "Vérificateur\n(safety checker)", ha='center', va='center',
fontsize=9, color='#1a9850', fontweight='bold')
# Chargement syscall bpf()
ax.annotate("", xy=(7.75, 4.7), xytext=(5.3, 5.2),
arrowprops=dict(arrowstyle='->', color='#555555', lw=1.8))
ax.text(6.5, 5.1, "bpf()\nsyscall", ha='center', fontsize=8, color='#555555', style='italic')
# Points d'accroche dans le noyau
hooks = [
(1.5, 2.5, "XDP\n(avant stack réseau)"),
(3.5, 2.5, "tc ingress/egress\n(Traffic Control)"),
(5.5, 2.5, "kprobes /\ntracepointss"),
(7.5, 2.5, "Socket\nfilter"),
(9.5, 2.5, "Sécurité LSM\n(SELinux-like)"),
]
for x, y, label in hooks:
ax.add_patch(mpatches.FancyBboxPatch((x-0.8, y-0.45), 1.6, 0.9,
boxstyle="round,pad=0.05", facecolor='#d73027', alpha=0.18,
edgecolor='#d73027', linewidth=1.5))
ax.text(x, y+0.05, label, ha='center', va='center', fontsize=7.5, color='#d73027', fontweight='bold')
ax.annotate("", xy=(x, y+0.45), xytext=(7.75, 4.7),
arrowprops=dict(arrowstyle='->', color='#1a9850', lw=1.2, linestyle='dotted'))
# Maps
ax.add_patch(mpatches.FancyBboxPatch((9.0, 1.2), 2.2, 0.9,
boxstyle="round,pad=0.1", facecolor='#984ea3', alpha=0.2,
edgecolor='#984ea3', linewidth=1.5))
ax.text(10.1, 1.65, "BPF Maps\n(données partagées)", ha='center', va='center',
fontsize=8, color='#984ea3', fontweight='bold')
plt.tight_layout()
plt.savefig('_static/ebpf_archi.png', dpi=100, bbox_inches='tight')
plt.show()
XDP — eXpress Data Path#
XDP est le point d’accroche eBPF le plus bas dans la pile réseau Linux. Il s’exécute avant même l’allocation d’un sk_buff, offrant des performances proches de DPDK tout en restant intégré au noyau.
// Exemple de programme XDP en C (illustratif)
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <arpa/inet.h>
// Map pour compter les paquets par IP source
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 65536);
__type(key, __u32); // IP source
__type(value, __u64); // compteur
} paquets_par_ip SEC(".maps");
SEC("xdp")
int xdp_firewall(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return XDP_PASS;
if (eth->h_proto != htons(ETH_P_IP)) return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) return XDP_PASS;
__u32 src = ip->saddr;
__u64 *compteur = bpf_map_lookup_elem(&paquets_par_ip, &src);
if (compteur) {
(*compteur)++;
// Bloquer si > 1 000 000 paquets
if (*compteur > 1000000) return XDP_DROP;
} else {
__u64 un = 1;
bpf_map_update_elem(&paquets_par_ip, &src, &un, BPF_ANY);
}
return XDP_PASS;
}
Actions XDP
Un programme XDP retourne une action qui détermine le destin du paquet :
XDP_PASS: transmettre à la pile réseau Linux normale.XDP_DROP: éliminer le paquet immédiatement (DDoS mitigation ultra-rapide).XDP_TX: renvoyer le paquet sur la même interface (réflecteur).XDP_REDIRECT: rediriger vers une autre interface ou un socket AF_XDP.
DPDK — Data Plane Development Kit#
DPDK est un ensemble de bibliothèques permettant le bypass complet du noyau Linux pour le traitement de paquets haute performance.
Comparaison des performances#
fig, ax = plt.subplots(figsize=(10, 5))
méthodes = ['Kernel\nstandard', 'AF_PACKET\n(v3)', 'eBPF/XDP\n(native)', 'DPDK\n(poll mode)']
débit_mpps = [1.5, 3.5, 14.0, 29.0] # Millions de paquets par seconde (64 octets)
latence_µs = [50, 30, 5, 1.5] # Latence en µs
x = np.arange(len(méthodes))
width = 0.35
ax2 = ax.twinx()
bars1 = ax.bar(x - width/2, débit_mpps, width, color='#4575b4', alpha=0.8,
label='Débit (Mpps, 64B)', edgecolor='white')
bars2 = ax2.bar(x + width/2, latence_µs, width, color='#d73027', alpha=0.8,
label='Latence (µs)', edgecolor='white')
ax.set_xticks(x)
ax.set_xticklabels(méthodes, fontsize=11)
ax.set_ylabel("Débit (Millions de paquets/s)", fontsize=11, color='#4575b4')
ax2.set_ylabel("Latence (µs)", fontsize=11, color='#d73027')
ax.set_title("Performances de traitement de paquets\n(CPU 3 GHz, paquets 64 octets)", fontsize=12, fontweight='bold')
for bar, val in zip(bars1, débit_mpps):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height()+0.2,
f"{val} Mpps", ha='center', fontsize=9, color='#4575b4', fontweight='bold')
for bar, val in zip(bars2, latence_µs):
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height()+0.3,
f"{val} µs", ha='center', fontsize=9, color='#d73027', fontweight='bold')
lines1, labels1 = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax.legend(lines1 + lines2, labels1 + labels2, loc='upper left', fontsize=10)
plt.tight_layout()
plt.savefig('_static/dpdk_perf.png', dpi=100, bbox_inches='tight')
plt.show()
Data center networking — topologie spine-leaf#
La topologie spine-leaf est devenue le standard des data centers modernes. Elle remplace la hiérarchie traditionnelle (cœur / distribution / accès) par une architecture 2 couches offrant des chemins à faible latence et un débit uniforme.
Principes#
Leaf switches : connectés aux serveurs. Chaque leaf est connecté à tous les spines.
Spine switches : interconnectent tous les leaves. Aucun spine ne se connecte à un autre spine.
ECMP (Equal-Cost Multi-Path) : le trafic est réparti équitablement sur tous les chemins disponibles.
Tout chemin feuille-à-feuille traverse exactement 2 sauts : leaf → spine → leaf.
try:
import networkx as nx
HAS_NX = True
except ImportError:
HAS_NX = False
fig, ax = plt.subplots(figsize=(13, 7))
ax.set_xlim(-1, 13)
ax.set_ylim(-1, 7)
ax.axis('off')
ax.set_title("Topologie Spine-Leaf — data center moderne", fontsize=14, fontweight='bold', pad=12)
# Spines (4 switches)
n_spines = 4
n_leaves = 8
spine_y = 5.5
leaf_y = 2.0
spine_xs = np.linspace(1.5, 10.5, n_spines)
leaf_xs = np.linspace(0.5, 11.5, n_leaves)
# Dessiner les connexions spine-leaf (toutes)
for sx in spine_xs:
for lx in leaf_xs:
ax.plot([sx, lx], [spine_y - 0.35, leaf_y + 0.35],
'-', color='#aaaaaa', linewidth=0.8, alpha=0.5, zorder=1)
# Spines
for i, sx in enumerate(spine_xs):
ax.add_patch(plt.Rectangle((sx-0.65, spine_y-0.35), 1.3, 0.7,
facecolor='#4575b4', alpha=0.25, edgecolor='#4575b4', linewidth=2.5,
zorder=3))
ax.text(sx, spine_y, f"Spine {i+1}", ha='center', va='center',
fontsize=8.5, color='#4575b4', fontweight='bold', zorder=4)
# Leaves + serveurs
for i, lx in enumerate(leaf_xs):
ax.add_patch(plt.Rectangle((lx-0.55, leaf_y-0.3), 1.1, 0.6,
facecolor='#1a9850', alpha=0.25, edgecolor='#1a9850', linewidth=2,
zorder=3))
ax.text(lx, leaf_y, f"Leaf {i+1}", ha='center', va='center',
fontsize=7.5, color='#1a9850', fontweight='bold', zorder=4)
# Serveurs sous chaque leaf
for j in range(2):
sx_srv = lx + (j - 0.5) * 0.7
ax.add_patch(mpatches.FancyBboxPatch((sx_srv - 0.25, 0.4), 0.5, 0.6,
boxstyle="round,pad=0.05", facecolor='#d73027', alpha=0.18,
edgecolor='#d73027', linewidth=1.5, zorder=3))
ax.text(sx_srv, 0.7, f"S{i*2+j+1}", ha='center', va='center',
fontsize=6.5, color='#d73027', fontweight='bold', zorder=4)
ax.plot([lx, sx_srv], [leaf_y - 0.3, 1.0], '-', color='#d73027',
linewidth=0.8, alpha=0.6, zorder=2)
# Légende ECMP
ax.text(6, -0.5,
"ECMP : tout chemin Leaf A → Leaf B utilise exactement 2 sauts\n"
"Le trafic est équilibré sur les 4 chemins disponibles (4 spines)",
ha='center', va='center', fontsize=9.5, color='#333333',
bbox=dict(boxstyle='round,pad=0.4', facecolor='#f0f8ff', edgecolor='#4575b4'))
# Légende icônes
for x, label, col in [(0.5, "Spine", '#4575b4'), (2.5, "Leaf", '#1a9850'), (4.5, "Serveur", '#d73027')]:
ax.add_patch(mpatches.FancyBboxPatch((x-0.3, 6.1), 0.6, 0.45,
boxstyle="round,pad=0.05", facecolor=col, alpha=0.25, edgecolor=col, linewidth=1.5))
ax.text(x+0.5, 6.33, label, va='center', fontsize=9, color=col, fontweight='bold')
plt.tight_layout()
plt.savefig('_static/spine_leaf.png', dpi=100, bbox_inches='tight')
plt.show()
MLAG et zero-oversubscription#
MLAG (Multi-Chassis Link Aggregation) : regroupe des ports sur deux switches physiques distincts, éliminant le SPoF.
Zero oversubscription : ratio de contention de 1:1 — aucun goulot d’étranglement entre les couches. Impossible à grande échelle sans budgets considérables, mais visé pour les workloads HPC.
VXLAN — overlay réseau L2 sur L3#
VXLAN (Virtual eXtensible LAN) encapsule des trames Ethernet dans des paquets UDP, permettant de créer des réseaux virtuels L2 qui s’étendent au-delà des domaines L3.
Encapsulation#
fig, ax = plt.subplots(figsize=(12, 5))
ax.set_xlim(0, 12)
ax.set_ylim(0, 6)
ax.axis('off')
ax.set_title("Encapsulation VXLAN — trame L2 dans UDP/IP", fontsize=13, fontweight='bold', pad=12)
couches = [
(0.2, 4.5, 1.6, "Ethernet\nextérieur\n(14 o)", '#555555'),
(2.0, 4.5, 1.6, "IP extérieur\n(VTEP src→dst)\n(20 o)", '#d73027'),
(3.8, 4.5, 1.2, "UDP\ndport=4789\n(8 o)", '#f46d43'),
(5.2, 4.5, 1.2, "VXLAN\nHeader\nVNI (8 o)", '#fdae61'),
(6.6, 4.5, 1.6, "Ethernet\nintérieur\n(14 o)", '#4575b4'),
(8.4, 4.5, 1.6, "IP intérieur\n(VM src→dst)\n(20 o)", '#74add1'),
(10.2, 4.5, 1.5, "Payload\n(TCP/UDP…)", '#abd9e9'),
]
for x, y, w, label, col in couches:
ax.add_patch(mpatches.FancyBboxPatch((x, y), w, 1.0,
boxstyle="round,pad=0.05", facecolor=col, alpha=0.3,
edgecolor=col, linewidth=2))
ax.text(x + w/2, y + 0.5, label, ha='center', va='center',
fontsize=8, color=col, fontweight='bold')
# Accolades groupées
for x1, x2, y_br, label, col in [
(0.2, 4.8, 3.8, "Encapsulation réseau physique\n(ajoutée par le VTEP source)", '#d73027'),
(5.2, 6.0, 3.2, "VXLAN header\n(VNI = identifiant réseau virtuel)", '#fdae61'),
(6.6, 11.7, 3.8, "Trame originale du tenant\n(VM source → VM destination)", '#4575b4'),
]:
ax.annotate("", xy=(x1, y_br + 0.3), xytext=(x2, y_br + 0.3),
arrowprops=dict(arrowstyle='|-|', color=col, lw=1.5))
ax.text((x1+x2)/2, y_br - 0.0, label, ha='center', va='top', fontsize=8, color=col)
# VNI
ax.text(5.8, 2.0,
"VNI (VXLAN Network Identifier) — 24 bits → 16 millions de segments virtuels possibles\n"
"VTEP (VXLAN Tunnel End Point) : l'équipement (physique ou logiciel) qui encapsule/décapsule",
ha='center', va='center', fontsize=9, color='#333333',
bbox=dict(boxstyle='round,pad=0.4', facecolor='#f5f5f5', edgecolor='#888888'))
plt.tight_layout()
plt.savefig('_static/vxlan_encap.png', dpi=100, bbox_inches='tight')
plt.show()
BGP EVPN — plan de contrôle pour VXLAN#
BGP EVPN (Ethernet VPN, RFC 7432) est le standard de facto pour distribuer les informations d’accessibilité MAC/IP entre les VTEPs d’un réseau VXLAN. Il remplace l’apprentissage flood-and-learn par un plan de contrôle centralisé.
Types de routes EVPN :
Type 2 : MAC/IP Advertisement — distribue les adresses MAC et IP des VMs.
Type 3 : Inclusive Multicast — indique les VTEPs participants à chaque VNI.
Type 5 : IP Prefix Route — routage inter-VNI (IP routing entre segments).
Kubernetes networking#
Kubernetes ajoute plusieurs couches d’abstraction réseau au-dessus de Linux.
Modèle réseau Kubernetes#
fig, ax = plt.subplots(figsize=(13, 7))
ax.set_xlim(0, 13)
ax.set_ylim(0, 8)
ax.axis('off')
ax.set_title("Architecture réseau Kubernetes", fontsize=14, fontweight='bold', pad=12)
# Nœuds
for node_x, node_label in [(1, "Node 1"), (7, "Node 2")]:
ax.add_patch(mpatches.FancyBboxPatch((node_x - 0.2, 0.5), 5.5, 6.5,
boxstyle="round,pad=0.2", facecolor='#f0f0f0',
edgecolor='#888888', linewidth=2, zorder=1))
ax.text(node_x + 2.5, 6.7, node_label, ha='center', fontsize=11,
fontweight='bold', color='#555555')
# Pods
for i, (pod_x, pod_label, pod_col) in enumerate([
(node_x + 0.5, f"Pod A{i+1}\n10.0.{node_x}.{i+2}/16", '#4575b4'),
(node_x + 2.5, f"Pod B{i+1}\n10.0.{node_x}.{i+4}/16", '#1a9850'),
]):
ax.add_patch(mpatches.FancyBboxPatch((pod_x - 0.4, 1.8), 1.6, 1.2,
boxstyle="round,pad=0.1", facecolor=pod_col, alpha=0.2,
edgecolor=pod_col, linewidth=2, zorder=3))
ax.text(pod_x + 0.4, 2.4, pod_label, ha='center', va='center',
fontsize=7.5, color=pod_col, fontweight='bold', zorder=4)
# Veth pair vers cbr0
ax.plot([pod_x + 0.4, pod_x + 0.4], [1.8, 1.2], '-',
color=pod_col, linewidth=1.5, alpha=0.7, zorder=2)
# bridge CNI (cbr0 / cni0)
ax.add_patch(mpatches.FancyBboxPatch((node_x + 0.1, 0.7), 4.5, 0.55,
boxstyle="round,pad=0.05", facecolor='#984ea3', alpha=0.2,
edgecolor='#984ea3', linewidth=2, zorder=3))
ax.text(node_x + 2.35, 0.975, "CNI bridge (cbr0) — réseau pod", ha='center',
va='center', fontsize=8, color='#984ea3', fontweight='bold', zorder=4)
# kube-proxy / iptables rules
ax.add_patch(mpatches.FancyBboxPatch((node_x + 0.1, 3.2), 4.5, 0.7,
boxstyle="round,pad=0.05", facecolor='#d73027', alpha=0.15,
edgecolor='#d73027', linewidth=1.5, zorder=3))
ax.text(node_x + 2.35, 3.55, "kube-proxy (iptables/IPVS)\nService → Pod translation",
ha='center', va='center', fontsize=7.5, color='#d73027', fontweight='bold')
# Services
services = [
(2.5, 5.0, "ClusterIP Service\n(10.96.0.1:80)", '#4575b4'),
(7.5, 5.0, "NodePort Service\n(:30080)", '#1a9850'),
(10.5, 5.0, "LoadBalancer\n(IP externe)", '#d73027'),
]
for x, y, label, col in [(2.5, 5.0, "ClusterIP\n10.96.0.1:80", '#4575b4'),
(7.5, 5.0, "NodePort\n:30080", '#1a9850')]:
ax.add_patch(mpatches.FancyBboxPatch((x-0.8, y-0.35), 1.6, 0.7,
boxstyle="round,pad=0.1", facecolor=col, alpha=0.2,
edgecolor=col, linewidth=2, zorder=3))
ax.text(x, y, label, ha='center', va='center', fontsize=8.5, color=col, fontweight='bold')
# Ingress
ax.add_patch(mpatches.FancyBboxPatch((5, 6.8), 3, 0.8,
boxstyle="round,pad=0.1", facecolor='#ff7f00', alpha=0.2,
edgecolor='#ff7f00', linewidth=2))
ax.text(6.5, 7.2, "Ingress Controller (Nginx / Traefik)\nHTTPS → Services",
ha='center', va='center', fontsize=8.5, color='#ff7f00', fontweight='bold')
# Internet
ax.add_patch(plt.Circle((11.5, 7.2), 0.5, facecolor='#4575b4', alpha=0.2,
edgecolor='#4575b4', linewidth=2))
ax.text(11.5, 7.2, "🌐", ha='center', va='center', fontsize=16)
ax.annotate("", xy=(8, 7.2), xytext=(11, 7.2),
arrowprops=dict(arrowstyle='->', color='#4575b4', lw=2))
plt.tight_layout()
plt.savefig('_static/k8s_networking.png', dpi=100, bbox_inches='tight')
plt.show()
/tmp/ipykernel_11610/3136204634.py:71: UserWarning: Glyph 127760 (\N{GLOBE WITH MERIDIANS}) missing from font(s) DejaVu Sans.
plt.tight_layout()
/tmp/ipykernel_11610/3136204634.py:72: UserWarning: Glyph 127760 (\N{GLOBE WITH MERIDIANS}) missing from font(s) DejaVu Sans.
plt.savefig('_static/k8s_networking.png', dpi=100, bbox_inches='tight')
/home/loc/legacy_workspace/notebooks/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 127760 (\N{GLOBE WITH MERIDIANS}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)
Types de Services Kubernetes#
Type |
Accessibilité |
Description |
|---|---|---|
|
Interne cluster uniquement |
IP virtuelle, routée par kube-proxy/IPVS |
|
Externe via node IP:port |
Ouvre un port (30000-32767) sur chaque nœud |
|
Externe via LB cloud |
Provisionne un LB cloud (AWS ELB, GCP LB…) |
|
DNS alias |
Redirige vers un FQDN externe via CNAME |
CNI — Container Network Interface#
Les plugins CNI implémentent la connectivité réseau des pods :
cni_data = {
'Plugin': ['Flannel', 'Calico', 'Cilium', 'Weave Net', 'Canal'],
'Modèle réseau': ['VXLAN/host-gw', 'BGP/VXLAN', 'eBPF/VXLAN', 'VXLAN/Fast Datapath', 'VXLAN+Calico'],
'NetworkPolicy': ['Non', 'Oui', 'Oui (avancé)', 'Oui', 'Oui'],
'Performance': ['★★★', '★★★★', '★★★★★', '★★★', '★★★★'],
'Cas d\'usage': ['Simple, débutant', 'Prod, sécurité', 'Haute perf, eBPF', 'On-premise', 'Flannel+Calico'],
}
fig, ax = plt.subplots(figsize=(12, 3.5))
ax.axis('off')
df_cni = pd.DataFrame(cni_data)
table = ax.table(cellText=df_cni.values, colLabels=df_cni.columns,
cellLoc='center', loc='center',
colWidths=[0.12, 0.22, 0.16, 0.14, 0.30])
table.auto_set_font_size(False)
table.set_fontsize(9)
table.scale(1, 2.2)
for (row, col), cell in table.get_celld().items():
if row == 0:
cell.set_facecolor('#2c7bb6')
cell.set_text_props(color='white', fontweight='bold')
elif row % 2 == 0:
cell.set_facecolor('#f0f8ff')
cell.set_edgecolor('#dddddd')
ax.set_title("Plugins CNI pour Kubernetes", fontsize=12, fontweight='bold', pad=18)
plt.tight_layout()
plt.savefig('_static/cni_comparison.png', dpi=100, bbox_inches='tight')
plt.show()
SR-IOV et virtualisation réseau#
SR-IOV (Single Root I/O Virtualization) permet à une carte réseau physique d’exposer plusieurs fonctions virtuelles (VF) directement à des machines virtuelles ou des containers, éliminant le goulot d’étranglement de l’hyperviseur.
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
# SR-IOV vs virtio
ax1 = axes[0]
ax1.set_xlim(0, 10)
ax1.set_ylim(0, 7)
ax1.axis('off')
ax1.set_title("SR-IOV : bypass de l'hyperviseur", fontsize=11, fontweight='bold')
couches_virtio = [
(0.5, 5.8, "VM 1\n(guest OS)", '#4575b4'),
(0.5, 4.3, "Virtual\nNIC (virtio)", '#74add1'),
(0.5, 2.8, "Hyperviseur\n(vSwitch)", '#d73027'),
(0.5, 1.3, "Carte réseau\nphysique (PF)", '#1a9850'),
]
for x, y, label, col in couches_virtio:
ax1.add_patch(mpatches.FancyBboxPatch((x, y), 3.5, 0.9,
boxstyle="round,pad=0.1", facecolor=col, alpha=0.2,
edgecolor=col, linewidth=1.5))
ax1.text(x+1.75, y+0.45, label, ha='center', va='center', fontsize=8.5, color=col, fontweight='bold')
if y > 1.3:
ax1.annotate("", xy=(x+1.75, y), xytext=(x+1.75, y+0.9+0.05),
arrowprops=dict(arrowstyle='<->', color='#888888', lw=1.5))
ax1.text(2.25, 0.4, "Modèle paravirtualisé (virtio)\nLatence : ~50 µs", ha='center',
fontsize=8, color='#333333', style='italic')
couches_sriov = [
(5.5, 5.8, "VM 1\n(guest OS)", '#4575b4'),
(5.5, 4.3, "VF (Virtual\nFunction)", '#74add1'),
(5.5, 1.3, "PF + VFs\n(SR-IOV NIC)", '#1a9850'),
]
for x, y, label, col in couches_sriov:
ax1.add_patch(mpatches.FancyBboxPatch((x, y), 3.5, 0.9,
boxstyle="round,pad=0.1", facecolor=col, alpha=0.2,
edgecolor=col, linewidth=1.5))
ax1.text(x+1.75, y+0.45, label, ha='center', va='center', fontsize=8.5, color=col, fontweight='bold')
ax1.plot([7.25, 7.25], [4.3, 2.2], '-', color='#1a9850', linewidth=2)
ax1.annotate("", xy=(7.25, 2.2), xytext=(7.25, 4.3),
arrowprops=dict(arrowstyle='->', color='#1a9850', lw=2))
ax1.text(7.6, 3.2, "Direct\nDMA", ha='left', fontsize=8, color='#1a9850', fontweight='bold')
ax1.add_patch(mpatches.FancyBboxPatch((5.5, 2.7), 3.5, 0.9,
boxstyle="round,pad=0.1", facecolor='#dddddd', alpha=0.3,
edgecolor='#aaaaaa', linewidth=1, linestyle='dashed'))
ax1.text(7.25, 3.15, "Hyperviseur\n(bypassed)", ha='center', va='center',
fontsize=8, color='#aaaaaa', style='italic')
ax1.text(7.25, 0.4, "SR-IOV\nLatence : ~2-5 µs", ha='center',
fontsize=8, color='#333333', style='italic')
# SmartNIC / DPU
ax2 = axes[1]
ax2.set_xlim(0, 10)
ax2.set_ylim(0, 6)
ax2.axis('off')
ax2.set_title("SmartNIC / DPU — offload vers le réseau", fontsize=11, fontweight='bold')
composants_dpu = [
(1.5, 5, "Serveur\nhôte (x86)", '#4575b4'),
(5, 5, "DPU / SmartNIC\n(ARM cores + FPGA)", '#d73027'),
(8.5, 5, "Réseau\n(fabric)", '#1a9850'),
]
for x, y, label, col in composants_dpu:
ax2.add_patch(mpatches.FancyBboxPatch((x-0.9, y-0.45), 1.8, 0.9,
boxstyle="round,pad=0.1", facecolor=col, alpha=0.2,
edgecolor=col, linewidth=2))
ax2.text(x, y, label, ha='center', va='center', fontsize=8.5, color=col, fontweight='bold')
for x1, x2 in [(2.4, 4.1), (5.9, 7.6)]:
ax2.annotate("", xy=(x2, 5), xytext=(x1, 5),
arrowprops=dict(arrowstyle='<->', color='#555555', lw=2))
fonctions_offload = [
"OVS/vSwitch (virtual switching)",
"Firewall & ACL stateful",
"IPsec / WireGuard encryption",
"RDMA / RoCE processing",
"Telemetry & monitoring",
"SR-IOV VF management",
]
ax2.text(5, 3.8, "Fonctions offloadées sur le DPU :", ha='center', fontsize=9,
fontweight='bold', color='#333333')
for i, func in enumerate(fonctions_offload):
ax2.text(5, 3.3 - i * 0.42, f"• {func}", ha='center', fontsize=8.5, color='#444444')
plt.tight_layout()
plt.savefig('_static/sriov_dpu.png', dpi=100, bbox_inches='tight')
plt.show()
Tendances : P4, 400GbE, réseaux optiques#
P4 — Programming Protocol-Independent Packet Processors#
P4 est un langage de programmation de la couche de traitement de paquets (plan de données programmable). Contrairement à eBPF qui programme le noyau Linux, P4 programme directement les ASICs et FPGAs des équipements réseau.
# Comparaison des approches de data plane programmable
approches = {
'eBPF/XDP': {
'Abstraction': 'Noyau Linux',
'Cible': 'CPU Intel/AMD',
'Perf (Mpps)': 14,
'Flexibilité': 9,
'Simplicité': 8,
},
'DPDK': {
'Abstraction': 'Userspace Linux',
'Cible': 'CPU (poll-mode)',
'Perf (Mpps)': 30,
'Flexibilité': 10,
'Simplicité': 5,
},
'P4/FPGA': {
'Abstraction': 'FPGA/ASIC',
'Cible': 'Tofino, FPGA',
'Perf (Mpps)': 6400,
'Flexibilité': 8,
'Simplicité': 4,
},
'ASIC fixe': {
'Abstraction': 'Silicium',
'Cible': 'Broadcom Trident',
'Perf (Mpps)': 12800,
'Flexibilité': 1,
'Simplicité': 9,
},
}
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
# Radar chart — Flexibilité vs Simplicité
ax1 = axes[0]
noms = list(approches.keys())
flex = [v['Flexibilité'] for v in approches.values()]
simp = [v['Simplicité'] for v in approches.values()]
perfs_log = [np.log10(v['Perf (Mpps)']) * 3 for v in approches.values()]
cols_rdr = ['#4575b4', '#1a9850', '#d73027', '#984ea3']
for nom, f, s, p_log, col in zip(noms, flex, simp, perfs_log, cols_rdr):
ax1.scatter(f, s, s=p_log*80, color=col, alpha=0.7, edgecolor='white',
linewidth=2, zorder=3, label=nom)
ax1.text(f+0.1, s+0.15, nom, fontsize=9, color=col, fontweight='bold')
ax1.set_xlabel("Flexibilité (1=fixe, 10=totale)", fontsize=11)
ax1.set_ylabel("Facilité d'utilisation", fontsize=11)
ax1.set_title("Flexibilité vs Facilité\n(taille = performances Mpps)", fontsize=11, fontweight='bold')
ax1.set_xlim(0, 11)
ax1.set_ylim(0, 11)
ax1.grid(True, alpha=0.3)
# Évolution des vitesses d'interface
ax2 = axes[1]
années_eth = [1983, 1995, 1999, 2002, 2007, 2010, 2014, 2018, 2021, 2024]
débits_eth = [0.01, 0.1, 1, 10, 10, 40, 100, 400, 400, 800]
étiquettes = ['10M', '100M', '1G', '10G', '10G', '40G', '100G', '400G', '400G', '800G']
ax2.semilogy(années_eth, débits_eth, 'o-', color='#4575b4', linewidth=2.5,
markersize=8, markerfacecolor='white', markeredgewidth=2.5)
for x, y, lab in zip(années_eth[::2], débits_eth[::2], étiquettes[::2]):
ax2.annotate(lab, (x, y), textcoords='offset points', xytext=(5, 5),
fontsize=9, color='#4575b4', fontweight='bold')
ax2.set_xlabel("Année", fontsize=11)
ax2.set_ylabel("Débit (Gbps, échelle log)", fontsize=11)
ax2.set_title("Évolution des vitesses Ethernet", fontsize=12, fontweight='bold')
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f"{y:.0f}" if y >= 1 else f"{y*1000:.0f}M"))
plt.tight_layout()
plt.savefig('_static/trends_networking.png', dpi=100, bbox_inches='tight')
plt.show()
Réseaux optiques reconfigurables#
Les réseaux ROADM (Reconfigurable Optical Add-Drop Multiplexer) permettent de router des longueurs d’onde individuelles dans un réseau WDM (Wavelength Division Multiplexing) sans conversion optique-électronique-optique (O-E-O).
400ZR est le standard cohérent pour les liaisons point-à-point 400G sur 80 km, utilisé dans les interconnexions de data centers.
Simulation topologie spine-leaf avec graphe#
# Simulation et visualisation d'une topologie spine-leaf
# Sans dépendance NetworkX — utilisation de matplotlib seul
def simuler_routing_spine_leaf(n_spines: int = 3, n_leaves: int = 6,
serveurs_par_leaf: int = 4) -> dict:
"""
Simule une topologie spine-leaf et calcule :
- Le nombre de chemins entre toutes les paires de leaves.
- Le nombre maximum de sauts.
"""
# Tous les leaves sont connectés à tous les spines
# Chemins entre leaf_i et leaf_j : passe par chaque spine (n_spines chemins)
paires_leaves = n_leaves * (n_leaves - 1) // 2
total_chemins = paires_leaves * n_spines
return {
'spines': n_spines,
'leaves': n_leaves,
'serveurs_par_leaf': serveurs_par_leaf,
'total_serveurs': n_leaves * serveurs_par_leaf,
'liens_spine_leaf': n_spines * n_leaves,
'paires_serveurs': (n_leaves * serveurs_par_leaf) *
(n_leaves * serveurs_par_leaf - 1) // 2,
'sauts_max': 2, # toujours 2 pour spine-leaf
'chemins_par_paire': n_spines,
'total_chemins': total_chemins,
}
# Analyse scalabilité
configs = [(2,4), (3,6), (4,8), (4,16), (8,32), (16,64)]
résultats = [simuler_routing_spine_leaf(s, l, 8) for s, l in configs]
df_scale = pd.DataFrame(résultats)
fig, axes = plt.subplots(1, 3, figsize=(13, 5))
fig.suptitle("Scalabilité de la topologie Spine-Leaf", fontsize=14, fontweight='bold')
configs_labels = [f"{s}S/{l}L" for s, l in configs]
# Total serveurs
axes[0].bar(configs_labels, df_scale['total_serveurs'], color='#4575b4', alpha=0.8, edgecolor='white')
axes[0].set_title("Total serveurs", fontsize=11, fontweight='bold')
axes[0].set_ylabel("Nombre de serveurs")
axes[0].tick_params(axis='x', rotation=45)
for i, v in enumerate(df_scale['total_serveurs']):
axes[0].text(i, v+1, str(v), ha='center', fontsize=9)
# Liens totaux
axes[1].bar(configs_labels, df_scale['liens_spine_leaf'], color='#1a9850', alpha=0.8, edgecolor='white')
axes[1].set_title("Liens Spine-Leaf", fontsize=11, fontweight='bold')
axes[1].set_ylabel("Nombre de liens")
axes[1].tick_params(axis='x', rotation=45)
for i, v in enumerate(df_scale['liens_spine_leaf']):
axes[1].text(i, v+0.5, str(v), ha='center', fontsize=9)
# Chemins par paire
axes[2].bar(configs_labels, df_scale['chemins_par_paire'], color='#d73027', alpha=0.8, edgecolor='white')
axes[2].set_title("Chemins ECMP par paire de leaves", fontsize=11, fontweight='bold')
axes[2].set_ylabel("Nombre de chemins")
axes[2].tick_params(axis='x', rotation=45)
axes[2].set_ylim(0, max(df_scale['chemins_par_paire']) + 3)
for i, v in enumerate(df_scale['chemins_par_paire']):
axes[2].text(i, v+0.1, str(v), ha='center', fontsize=9)
plt.tight_layout()
plt.savefig('_static/spine_leaf_scalability.png', dpi=100, bbox_inches='tight')
plt.show()
print("Analyse scalabilité Spine-Leaf :")
print(df_scale[['spines', 'leaves', 'total_serveurs', 'liens_spine_leaf',
'chemins_par_paire', 'sauts_max']].to_string(index=False))
Analyse scalabilité Spine-Leaf :
spines leaves total_serveurs liens_spine_leaf chemins_par_paire sauts_max
2 4 32 8 2 2
3 6 48 18 3 2
4 8 64 32 4 2
4 16 128 64 4 2
8 32 256 256 8 2
16 64 512 1024 16 2
Résumé du chapitre#
fig, ax = plt.subplots(figsize=(12, 7))
ax.axis('off')
données_résumé = [
['SD-WAN', 'WAN', 'Séparation ctrl/données\nOverlay sur Internet', 'Flexibilité WAN\nQoS applicative'],
['eBPF/XDP', 'Noyau Linux', 'Programmation kernel\nsandboxée', '14 Mpps\nObservabilité sans overhead'],
['DPDK', 'Userspace', 'Bypass noyau\nPoll-mode drivers', '30+ Mpps\nNFV, télécoms'],
['Spine-Leaf', 'Data center L2/L3', '2 couches + ECMP\nZero SPoF', 'Latence uniforme\nFacilité de scale'],
['VXLAN/BGP EVPN', 'Overlay L2/L3', 'L2 sur UDP/IP\nPlan de contrôle BGP', '16M VNI\nMulti-tenant DC'],
['Kubernetes CNI', 'Container', 'Pod réseau flat\nServices + Ingress', 'Cilium (eBPF)\nCalico (BGP)'],
['SR-IOV / DPU', 'Virtualisation', 'NIC → VMs directement\nOffload vers DPU', '2–5 µs latence\nLibère le CPU hôte'],
['P4', 'Data plane ASIC', 'Protocoles définis par prog.\nFPGA / Tofino', '6400+ Mpps\nReconfigurabilité'],
]
cols = ['Technologie', 'Domaine', 'Principe', 'Bénéfices clés']
table = ax.table(cellText=données_résumé, colLabels=cols,
cellLoc='center', loc='center',
colWidths=[0.18, 0.17, 0.33, 0.28])
table.auto_set_font_size(False)
table.set_fontsize(8.5)
table.scale(1, 2.1)
for (row, col), cell in table.get_celld().items():
if row == 0:
cell.set_facecolor('#2c7bb6')
cell.set_text_props(color='white', fontweight='bold')
elif row % 2 == 0:
cell.set_facecolor('#f5f8fc')
cell.set_edgecolor('#dddddd')
ax.set_title("Synthèse — technologies de réseaux modernes", fontsize=13, fontweight='bold', pad=20)
plt.tight_layout()
plt.savefig('_static/modern_networks_summary.png', dpi=100, bbox_inches='tight')
plt.show()
Points clés du chapitre
SD-WAN découple la logique de routage WAN du matériel et permet de combiner MPLS, Internet et 4G/5G selon des politiques applicatives.
eBPF est la révolution silencieuse du noyau Linux : il permet d’instrumenter, de filtrer et de rediriger les paquets avec une performance proche du matériel, sans modifier le noyau. XDP est son point d’accroche le plus bas.
DPDK pousse le concept plus loin en contournant entièrement le noyau ; utilisé pour les routeurs logiciels, les telcos NFV, les pare-feux haute performance.
Spine-leaf est la topologie standard des data centers modernes : 2 sauts max, ECMP sur tous les chemins, passage à l’échelle horizontal aisé.
VXLAN encapsule du L2 dans de l’UDP pour créer des segments virtuels sur n’importe quelle infrastructure IP ; BGP EVPN en est le plan de contrôle standard.
Kubernetes abstrait le réseau en trois niveaux : pods (flat L3), Services (ClusterIP/NodePort/LB), Ingress. Les CNI (Cilium, Calico, Flannel) implémentent ces abstractions.
SR-IOV et les SmartNIC/DPU déportent le traitement réseau hors du CPU principal, libérant des ressources pour les charges applicatives.
P4 représente le futur du data plane programmable : les protocoles eux-mêmes peuvent être redéfinis sans changer le silicium.