Угрозы в многопользовательской среде: эффект noisy neighbor и сбои ядра

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

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

  1. Истощение памяти сетевых сокетов в ядре: Сетевые сокеты выделяют память ядра через slab-аллокатор (skbuff_head_cache). Клиентский сегмент с внутренней сетевой петлей или под воздействием внешней распределенной атаки может захватить гигабайты невыгружаемой буферной памяти ядра (sk_rmem_alloc). В устаревших схемах изоляции это приводит к глобальной панике ядра Out-Of-Memory (OOM), аварийно завершающей соседние туннели других клиентов.
  2. Дефицит вычислительных мощностей CPU из-за криптографии: Процедуры симметричного дешифрования (например, ChaCha20-Poly1305 или AES-256-GCM) требуют значительных процессорных ресурсов. Если один клиент заполняет все потоки процессора непрерывными криптографическими рукопожатиями, передача пакетов соседних клиентов замедляется, вызывая скачки задержки на десятки миллисекунд.
  3. Конкуренция за аппаратные очереди сетевой карты: Несколько сокетов, конкурирующих за аппаратные очереди сетевого адаптера (NIC), переполняют буферы передачи, что провоцирует случайный сброс пакетов по всему серверу.

Хотя механизм cgroups v1 предоставлял базовые средства управления, его разрозненная структура с независимыми деревьями страдала от невозможности учета памяти ядра и высоких накладных расходов. Современная архитектура Control Groups v2 (cgroups v2) ядра Linux предлагает надежные механизмы предсказуемого сдерживания ресурсов.

Внутреннее устройство cgroups v2: архитектура единой иерархии

Разработанная и отточенная в версиях ядра Linux от 4.5 до 6.x, подсистема cgroups v2 объединяет все механизмы контроля в единую древовидную структуру процессов с корнем в /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, памяти и ввода-вывода для клиентских сегментов
echo "+cpu +memory +io" > /sys/fs/cgroup/vpn/cgroup.subtree_control

Учет памяти сокетов и динамика освобождения страниц

Главным преимуществом cgroups v2 стала полная интеграция контроля памяти ядра. В cgroups v1 буферы сокетов (struct sock и struct sk_buff) находились вне сферы действия ограничений без применения нестандартных патчей. В cgroups v2 все выделения памяти под буферы сокетов процессами группы напрямую учитываются в общем потреблении памяти этой группы.

Многоуровневое управление через memory.high и memory.max

Контроллер памяти предоставляет поэтапную защиту от неконтролируемого расхода памяти:

  • memory.min (Гарантированный минимум): Память в пределах этого лимита никогда не освобождается даже при общей нехватке памяти в системе.
  • memory.low (Приоритетная защита): Память ниже этого порога защищается от вытеснения, если в системе имеются другие доступные цели для очистки.
  • memory.high (Порог регулирования): Когда суммарное потребление памяти сегментом превышает memory.high, ядро не вызывает аварийный OOM killer. Вместо этого оно замедляет работу процессов при возврате из системных вызовов и запускает агрессивное асинхронное освобождение страниц памяти.
  • 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.

Сетевые инженеры могут подключать eBPF-фильтры непосредственно к каталогу 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); // Счетчик маркеров token bucket
    __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. Изолированное планирование CPU

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 такая архитектура предотвращает передачу незашифрованных данных за пределы назначенного сегмента, обеспечивая высокую надежность и конфиденциальность корпоративного уровня.