Ризики в багатокористувацькому середовищі: ефект noisy neighbor та збої ядра

У корпоративній інфраструктурі віддаленого доступу, на хмарних прикордонних вузлах та в операторських концентраторах тунелів спільне розміщення робочих навантажень різних організацій на одному фізичному обладнанні є стандартною інженерною практикою. Один фізичний сервер обслуговує сотні одночасних клієнтських тунелів, проксі-служб мікросегментації та контейнеризованих шлюзів шифрування.

Однак експлуатація багатокористувацьких (multi-tenant) середовищ без суворої низькорівневої ізоляції ресурсів створює системні ризики:

  1. Вичерпання пам'яті мережевих сокетів ядра: Мережеві сокети виділяють системну пам'ять ядра через slab-алокатор (skbuff_head_cache). Несправний клієнтський сегмент із внутрішньою мережевою петлею або під час DDoS-атаки здатен поглинути гігабайти пам'яті буферів ядра (sk_rmem_alloc), яку неможливо скинути в swap. За відсутності надійної ізоляції це спричиняє глобальний аварійний збій Out-Of-Memory (OOM), що аварійно зупиняє роботу сусідніх тунелів на вузлі.
  2. Дефіцит потужностей CPU через криптографічне перевантаження: Алгоритми симетричного дешифрування (зокрема ChaCha20-Poly1305 або AES-256-GCM) інтенсивно завантажують процесор. Якщо один орендар перевантажує всі доступні потоки безперервними обчисленнями криптографічних рукостискань, обробка пакетів сусідніх клієнтів затримується, спричиняючи різкі стрибки затримок на десятки мілісекунд.
  3. Конкуренція за апаратні черги мережевої карти: Спільне використання сокетами апаратних черг мережевого адаптера (NIC) може переповнювати буфери передавання, викликаючи хаотичний скид пакетів на всьому сервері.

Хоча попередня версія cgroups v1 надавала базові інструменти, її розділена багаторівнева структура страждала від неможливості точного обліку пам'яті ядра та високих витрат на синхронізацію. На противагу цьому сучасна уніфікована архітектура Control Groups v2 (cgroups v2) у ядрі Linux забезпечує детермінований контроль та надійне розмежування ресурсів.

Внутрішня будова cgroups v2: архітектура єдиної ієрархії

Підсистема cgroups v2, вдосконалена у версіях ядра Linux від 4.5 до 6.x, замінює відокремлені дерева v1 єдиною ієрархією процесів із коренем у точці монтування /sys/fs/cgroup.

+-------------------------------------------------------------------------+
|                  Корінь єдиної ієрархії cgroups v2 (/sys/fs/cgroup)     |
+-------------------------------------------------------------------------+
                                    |
      +-----------------------------+-----------------------------+
      | cgroup.subtree_control = "+cpu +memory +io"               |
      v                                                           v
+-------------------------------+               +-------------------------------+
| Сегмент клієнта: Alpha        |               | Сегмент клієнта: Beta         |
| /sys/fs/cgroup/vpn/tenant_a   |               | /sys/fs/cgroup/vpn/tenant_b   |
+-------------------------------+               +-------------------------------+
| - cpu.max = 200000 100000     |               | - cpu.max = 100000 100000     |
|   (Обмеження: 2 ядра CPU)     |               |   (Обмеження: 1 ядро 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 Приєднані процеси                             v Приєднані процеси
+-------------------------------+               +-------------------------------+
|  tun_worker_a (PID 14210)     |               |  tun_worker_b (PID 14502)     |
|  crypto_a     (PID 14211)     |               |  crypto_b     (PID 14503)     |
+-------------------------------+               +-------------------------------+

Принцип відсутності внутрішніх процесів

Ключовим архітектурним правилом cgroups v2 є заборона на одночасне розміщення активних процесів та дочірніх груп у проміжних вузлах. Лише листові групи можуть містити ідентифікатори процесів (cgroup.procs). Це усуває невизначеність під час розподілу ресурсів та гарантує передбачуване ієрархічне планування.

Щоб увімкнути контролери для дочірніх груп, батьківський вузол вносить їхні назви до файлу cgroup.subtree_control:

# Активація контролерів CPU, пам'яті та вводу-виводу для сегментів VPN
echo "+cpu +memory +io" > /sys/fs/cgroup/vpn/cgroup.subtree_control

Облік пам'яті сокетів та динаміка звільнення сторінок

Вирішальною перевагою cgroups v2 є повний контроль пам'яті ядра. У версії v1 буфери сокетів (struct sock та struct sk_buff) випадали з-під загальних обмежень без специфічних змін ядра. У cgroups v2 будь-яке виділення пам'яті під буфери сокетів процесами групи автоматично зараховується до загального обсягу споживання цієї групи.

Багаторівневий захист через memory.high та memory.max

Контролер пам'яті забезпечує поступовий захист від вичерпання ресурсів:

  • memory.min (Гарантований мінімум): Пам'ять нижче цієї межі ніколи не вилучається за загального браку ресурсів у системі.
  • memory.low (Пріоритетний захист): Сторінки нижче цього порогу максимально захищаються від очищення, якщо доступні інші кандидати на звільнення.
  • memory.high (Поріг регулювання): Коли споживання пам'яті клієнтським сегментом перевищує memory.high, ядро не запускає механізм аварійного закриття процесів OOM. Натомість воно уповільнює виконання процесів після системних викликів та розпочинає інтенсивне асинхронне вивільнення пам'яті.
  • memory.max (Жорсткий ліміт): Якщо споживання досягає цієї абсолютної межі, а очищення пам'яті не допомогло, OOM killer зупиняє процеси виключно всередині проблемного сегмента, не зачіпаючи решту клієнтських тунелів на шлюзі.
# Налаштування бар'єрів для сегмента VPN обсягом 2 ГБ
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

Обмеження пропускної здатності за допомогою eBPF cgroup_skb

У класичних системах керування мережевим трафіком між ізольованими сервісами вимагало складних налаштувань Traffic Control (tc) на програмних мостах. У сучасному середовищі Linux підсистема cgroups v2 інтегрується з eBPF за допомогою програм типу BPF_PROG_TYPE_CGROUP_SKB.

Мережеві інженери можуть закріплювати фільтри безпосередньо за директорією cgroup через точки виклику BPF_CGROUP_INET_INGRESS та BPF_CGROUP_INET_EGRESS. Кожен сокет, відкритий процесом у цій групі, автоматично застосовує правила фільтрації незалежно від використовуваного мережевого інтерфейсу.

// Приклад обмеження вихідного трафіку через eBPF cgroup_skb
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, __u64); // Відстеження кількості маркерів
    __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; // Пропустити пакет у разі помилки таблиці

    // Перевірка розміру пакета щодо наявних маркерів
    if (skb->len > *tokens) {
        // Перевищення виділеного ліміту швидкості: скид пакета
        return 0;
    }

    *tokens -= skb->len;
    return 1; // Дозволити передавання
}

char _license[] SEC("license") = "GPL";

Прив'язка такої програми до вузла cgroup гарантує точне обмеження швидкості передавання безпосередньо в мережевому стеку ядра без зайвих перемикань контексту.

Захист багатокористувацьких сегментів у RiceGuard

У розподіленій інфраструктурі VPNRice рушій RiceGuard спирається на cgroups v2 для забезпечення ізоляції та стабільності роботи клієнтів на високощільних серверних кластерах.

Фізичні ресурси сервера (64 ядра CPU, 256 ГБ RAM, два порти 100GbE)
                               |
                               v
+-------------------------------------------------------------------+
|               Менеджер сегментів RiceGuard cgroups v2             |
+-------------------------------------------------------------------+
       |                                             |
       v Сегмент Alpha (Рівень Gold)                 v Сегмент Beta (Рівень Silver)
+-----------------------------+               +-----------------------------+
| - 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 Вхідний потік: 10G   |               | - eBPF Вхідний потік: 2G    |
+-----------------------------+               +-----------------------------+
       |                                             |
       v                                             v
  Усунено витік ресурсів                        Усунено витік ресурсів

1. Ізольоване планування процесора

RiceGuard координує обчислювальні процеси, комбінуючи зважений поділ ресурсів cpu.weight за умов високого навантаження з фіксованими лімітами cpu.max планувальника CFS. Пріоритетні корпоративні сегменти отримують більшу вагу, що гарантує надання необхідних процесорних циклів для шифрування навіть під час різких сплесків трафіку.

2. Моніторинг тиску на пам'ять без блокувань

Замість періодичних запитів у просторі користувача, RiceGuard підключається до метрик Pressure Stall Information (PSI) всередині кожної cgroup (/sys/fs/cgroup/vpn/<клієнт>/memory.pressure). Щойно тиск на пам'ять вказує на наближення до лімітів, рушій коригує розмір вікна UDP клієнтів, упереджуючи втрату пакетів.

3. Комплексна ізоляція з мережевими просторами назв

RiceGuard поєднує можливості cgroups v2 з відокремленими просторами назв Linux network namespace. Мережеві сокети кожного клієнта прив'язані до персональних таблиць маршрутизації. У парі з eBPF-фільтрами це запобігає виходу незашифрованих пакетів за межі виділеного сегмента, гарантуючи високу надійність та безпеку зв'язку під екстремальними навантаженнями.