Le défi multi-locataires : Voisins bruyants et saturation du noyau
Au sein des infrastructures d'accès distant d'entreprise, des nœuds de périphérie cloud et des concentrateurs de tunnels de niveau opérateur, l'hébergement de charges de travail appartenant à plusieurs organisations clientes sur un serveur physique partagé constitue la norme d'exploitation. Un même hôte matériel supervise fréquemment des centaines de tunnels simultanés, de relais de micro-segmentation et de passerelles de chiffrement conteneurisées.
Cependant, faire fonctionner des architectures VPN multi-locataires (multi-tenant) sans un cloisonnement matériel et logiciel strict expose l'infrastructure à des défaillances systémiques :
- Épuisement de la mémoire des sockets dans le noyau : Les sockets réseau allouent de la mémoire noyau au moyen du mécanisme slab (
skbuff_head_cache). Une instance cliente défaillante, prise dans une boucle de paquets ou ciblée par une attaque DDoS, peut consommer plusieurs gigaoctets de mémoire tampon non échangeable (sk_rmem_alloc). En l'absence d'isolation moderne, cette situation déclenche un arrêt d'urgence Out-Of-Memory (OOM) global qui neutralise l'ensemble des passerelles voisines sur la machine. - Monopolisation des cœurs de processeur par le chiffrement : Les algorithmes de déchiffrement symétrique (tels que ChaCha20-Poly1305 ou AES-256-GCM) sollicitent intensivement les ressources de calcul. Si un locataire sature tous les threads CPU disponibles avec des négociations cryptographiques massives, l'acheminement des paquets des autres clients est retardé, générant des pics de latence de plusieurs dizaines de millisecondes.
- Engorgement des anneaux matériels de transmission : Plusieurs sockets en concurrence pour les files d'attente d'une carte réseau peuvent saturer les tampons de sortie, causant des rejets aléatoires de trames à l'échelle de l'hôte.
Si la première version des cgroups sous Linux offrait des garde-fous partiels, sa structure fragmentée en arborescences multiples empêchait une imputation cohérente des ressources du noyau. La version unifiée Control Groups v2 (cgroups v2) apporte la rigueur architecturale indispensable à une isolation prédictible.
Architecture interne de cgroups v2 : La hiérarchie unifiée
Finalisée et enrichie des versions 4.5 à 6.x du noyau Linux, la technologie cgroups v2 regroupe tous les contrôleurs sous une unique arborescence de processus implantée à la racine /sys/fs/cgroup.
+-------------------------------------------------------------------------+
| Racine unifiée cgroups v2 (/sys/fs/cgroup) |
+-------------------------------------------------------------------------+
|
+-----------------------------+-----------------------------+
| cgroup.subtree_control = "+cpu +memory +io" |
v v
+-------------------------------+ +-------------------------------+
| Tranche locataire : Alpha | | Tranche locataire : Beta |
| /sys/fs/cgroup/vpn/tenant_a | | /sys/fs/cgroup/vpn/tenant_b |
+-------------------------------+ +-------------------------------+
| - cpu.max = 200000 100000 | | - cpu.max = 100000 100000 |
| (Plafond : 2 cœurs CPU) | | (Plafond : 1 cœur CPU) |
| - memory.high = 2G | | - memory.high = 1G |
| - memory.max = 3G | | - memory.max = 1.5G |
| - eBPF cgroup_skb ingress | | - eBPF cgroup_skb ingress |
| - eBPF cgroup_skb egress | | - eBPF cgroup_skb egress |
+-------------------------------+ +-------------------------------+
| |
v Processus rattachés v Processus rattachés
+-------------------------------+ +-------------------------------+
| tun_worker_a (PID 14210) | | tun_worker_b (PID 14502) |
| crypto_a (PID 14211) | | crypto_b (PID 14503) |
+-------------------------------+ +-------------------------------+
La règle de non-coexistence des processus internes
Une caractéristique fondamentale de cgroups v2 réside dans l'interdiction faite aux nœuds intermédiaires de contenir simultanément des processus membres et des sous-groupes enfants. Seules les feuilles de l'arbre accueillent des identifiants de processus (cgroup.procs). Cette règle supprime toute ambiguïté sur l'origine de la consommation des ressources et simplifie l'ordonnancement.
Pour activer les contrôleurs au bénéfice des tranches enfants, le parent déclare les modules dans le fichier cgroup.subtree_control :
# Activation des contrôleurs CPU, mémoire et entrées/sorties pour les tranches VPN
echo "+cpu +memory +io" > /sys/fs/cgroup/vpn/cgroup.subtree_control
Comptabilisation de la mémoire des sockets et dynamique de libération
L'avancée majeure de cgroups v2 repose sur l'intégration transparente de la mémoire noyau. Sous cgroups v1, la mémoire des structures sk_buff échappait aux quotas standards. Avec cgroups v2, toute allocation de tampon réseau initiée par un processus au sein d'un groupe est directement imputée à la consommation globale de ce groupe.
Protection échelonnée via memory.high et memory.max
Le contrôleur de mémoire établit une défense progressive pour désamorcer les saturations :
memory.min(Garantie stricte) : La mémoire située en deçà de ce seuil n'est jamais réquisitionnée, même en cas de pénurie globale de RAM.memory.low(Protection préférentielle) : Les pages situées sous ce seuil sont préservées au maximum lors des nettoyages.memory.high(Seuil de modération) : Dès que l'empreinte mémoire d'un locataire (y compris ses tampons de tunnels UDP et TCP) franchit ce cap, le noyau n'invoque pas l'arrêt brutal des processus par le mécanisme OOM. Au lieu de cela, il applique un ralentissement proportionnel lors du retour des appels système et lance une récupération asynchrone de pages mémoire.memory.max(Plafond absolu) : Si la mémoire atteint cette limite infranchissable et que la libération échoue, seuls les processus de la tranche incriminée sont ciblés par le gestionnaire OOM, protégeant ainsi l'intégrité de toutes les autres instances de la passerelle.
# Définition des barrières pour une tranche VPN de 2 Go
mkdir -p /sys/fs/cgroup/vpn/tenant_alpha
echo "512M" > /sys/fs/cgroup/vpn/tenant_alpha/memory.min
echo "2G" > /sys/fs/cgroup/vpn/tenant_alpha/memory.high
echo "3G" > /sys/fs/cgroup/vpn/tenant_alpha/memory.max
Régulation du trafic réseau via les programmes eBPF cgroup_skb
Historiquement, limiter le débit entre services voisins exigeait la mise en place d'arbres de disciplines de trafic (tc) complexes adossés à des ponts réseau logiciels. Dans l'écosystème Linux moderne, cgroups v2 communique nativement avec eBPF via le type de programme BPF_PROG_TYPE_CGROUP_SKB.
Un administrateur peut attacher un filtre eBPF directement au dossier d'un groupe grâce aux points d'ancrage BPF_CGROUP_INET_INGRESS et BPF_CGROUP_INET_EGRESS. Tout socket ouvert par un processus du groupe applique instantanément ce filtrage, quel que soit le périphérique réseau employé.
// Exemple de régulation eBPF cgroup_skb sur le flux sortant
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64); // Suivi du réservoir de jetons (tokens)
__uint(max_entries, 1);
} token_bucket SEC(".maps");
SEC("cgroup_skb/egress")
int throttle_tenant_egress(struct __sk_buff *skb)
{
__u32 key = 0;
__u64 *tokens = bpf_map_lookup_elem(&token_bucket, &key);
if (!tokens)
return 1; // Autorise le paquet en cas d'absence dans la table
// Compare la taille du paquet aux jetons disponibles
if (skb->len > *tokens) {
// Dépassement du quota de bande passante alloué : rejet propre
return 0;
}
*tokens -= skb->len;
return 1; // Transmission acceptée
}
char _license[] SEC("license") = "GPL";
L'application de ce code au nœud d'un locataire garantit une modération stricte du débit directement au cœur du chemin de données du noyau, évitant tout surcoût de changement de contexte vers l'espace utilisateur.
Sécurisation des tranches multi-tenants avec RiceGuard
Au sein des passerelles distribuées haute performance de VPNRice, le moteur propriétaire RiceGuard met à profit cgroups v2 pour assurer une séparation stricte et équitable entre clients sur des serveurs partagés.
Ressources matérielles (64 Cœurs CPU, 256 Go RAM, Double port 100GbE)
|
v
+-------------------------------------------------------------------+
| Gestionnaire de tranches RiceGuard cgroups v2 |
+-------------------------------------------------------------------+
| |
v Tranche Alpha (Palier Or) v Tranche Beta (Palier Argent)
+-----------------------------+ +-----------------------------+
| - cpu.weight : 200 | | - cpu.weight : 50 |
| - cpu.max : 800000 100000 | | - cpu.max : 200000 100000 |
| - memory.min : 4G | | - memory.min : 1G |
| - memory.high : 16G | | - memory.high : 4G |
| - eBPF Débit entrant : 10G | | - eBPF Débit entrant : 2G |
+-----------------------------+ +-----------------------------+
| |
v v
Aucun débordement de ressources Aucun débordement de ressources
1. Ordonnancement CPU hermétique
RiceGuard gère les priorités en associant cpu.weight (part proportionnelle garantie en cas de forte charge) à cpu.max (plafond rigide imposé par l'ordonnanceur CFS). Les tranches des clients stratégiques disposent de pondérations élevées, préservant la capacité de traitement cryptographique même lors de vagues massives de trafic imprévu.
2. Surveillance sans verrou de la pression mémoire
Plutôt que d'exécuter des outils de métrologie périodiques en espace utilisateur, RiceGuard consulte directement les métriques Linux Pressure Stall Information (PSI) associées à chaque groupe (/sys/fs/cgroup/vpn/<locataire>/memory.pressure). Lorsqu'un risque d'engorgement se profile, le système régule automatiquement la fenêtre UDP des clients, empêchant les pertes de paquets en amont.
3. Isolation conjuguée aux espaces de noms réseau
RiceGuard intègre les mécanismes de confinement de cgroups v2 avec des espaces de noms réseau Linux distincts. Les sockets d'un locataire sont strictement bornés à sa table de routage dédiée. Cette synergie technique empêche toute fuite de données en clair hors des limites assignées, garantissant une confidentialité totale et une fiabilité exemplaire sous des charges industrielles denses.



