خطرات محیط‌های چندکاربره: پدیده همسایه مزاحم و اختلال هسته

در زیرساخت‌های سازمانی دسترسی از راه دور، گره‌های لبه ابری و سرورهای تجمیع ترافیک، میزبانی بار کاری چند سازمان یا بخش مختلف روی سخت‌افزار فیزیکی مشترک یک رویکرد عملیاتی رایج است. یک سرور فیزیکی واحد اغلب وظیفه مدیریت صدها تونل ارتباطی همزمان، پروکسی‌های جداسازی ترافیک و سرویس‌های تونل‌سازی کانتینری را بر عهده دارد.

با این وجود، راه‌اندازی سامانه‌های چندکاربره (multi-tenant) بدون کنترل دقیق و سطح پایین بر منابع، خطرات جدی برای پایداری کل سرور ایجاد می‌کند:

  1. اتمام حافظه سوکت‌های شبکه در هسته: سوکت‌های شبکه حافظه هسته را از طریق تخصیص‌دهنده اسلب (skbuff_head_cache) مصرف می‌کنند. اگر بخشی از یک سرویس کلاینت دچار چرخه بسته یا حمله توزیع‌شده شود، بافرهای سوکت (sk_rmem_alloc) می‌توانند گیگابایت‌ها حافظه غیرقابل جابه‌جایی هسته را ببلعند. در روش‌های جداسازی سنتی، این وضعیت خطای سیستمی OOM سراسری ایجاد کرده و تمامی تونل‌های فعال دیگر را از کار می‌اندازد.
  2. اشغال توان پردازنده توسط الگوریتم‌های رمزنگاری: توابع رمزگشایی متقارن (مانند ChaCha20-Poly1305 و AES-256-GCM) نیازمند توان پردازشی بالایی هستند. هنگامی که یک کلاینت تمام رشته‌های پردازنده را با درخواست‌های مکرر اشغال کند، ارسال بسته‌های سایر کاربران دچار وقفه شده و تاخیرهای چند ده میلی‌ثانیه‌ای به وجود می‌آید.
  3. رقابت بر سر صف‌های سخت‌افزاری کارت شبکه: رقابت همزمان سوکت‌ها برای دستیابی به صف‌های کارت شبکه فیزیکی (NIC)، بافرهای ارسال را پر کرده و موجب افت تصادفی بسته‌ها در سطح کل میزبان می‌شود.

در حالی که ساختار قدیمی cgroups v1 سازوکارهای پایه‌ای داشت، اما به دلیل شاخه‌های متفرق قادر به ره‌گیری دقیق حافظه هسته نبود. در مقابل، ساختار مدرن Control Groups v2 (cgroups v2) در لینوکس مهار پایدار و تفکیک منابع را تضمین می‌کند.

معماری داخلی cgroups v2: سلسله‌مراتب یکپارچه

زیرسیستم cgroups v2 که در نسخه‌های ۴.۵ تا ۶.x هسته لینوکس به تکامل رسیده است، ساختارهای پراکنده نسخه پیشین را با یک سلسله‌مراتب فرآیندی واحد در مسیر /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     |
|   (سقف: ۲ هسته پردازنده)      |               |   (سقف: ۱ هسته پردازنده)      |
| - 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 می‌نویسد:

# فعال‌سازی کنترل‌کننده‌های پردازنده، حافظه و ورودی/خروجی برای بخش‌های VPN
echo "+cpu +memory +io" > /sys/fs/cgroup/vpn/cgroup.subtree_control

محاسبه دقیق حافظه سوکت‌ها و سازوکار آزادسازی صفحات

نوآوری برجسته cgroups v2 در ره‌گیری مستقیم حافظه مصرفی هسته است. در نسخه پیشین، بافرهای سوکت شبکه (struct sk_buff) تحت محدودیت‌های گروه قرار نمی‌گرفتند. اما در cgroups v2، هرگونه تخصیص بافر سوکت توسط فرآیندهای درون گروه، مستقیماً در تراز مصرفی همان گروه محاسبه می‌گردد.

لایه‌های دفاعی چندگانه با memory.high و memory.max

کنترل‌کننده حافظه خطوط دفاعی پایداری را برای مقابله با مصرف افسارگسیخته تعیین می‌کند:

  • memory.min (تضمین حداقلی): حافظه زیر این سطح هرگز در شرایط کمبود حافظه سراسری سیستم پس گرفته نمی‌شود.
  • memory.low (حفاظت ترجیحی): صفحات زیر این سقف تا زمانی که منابع دیگری برای آزادسازی وجود داشته باشد، محافظت می‌شوند.
  • memory.high (آستانه تعدیل): هنگامی که مصرف کلی یک کاربر (شامل بافرهای تونل UDP و TCP) از این حد فراتر رود، هسته فرآیندها را مسدود نمی‌کند. در عوض، در زمان بازگشت از فراخوان‌های سیستمی فرآیند را اندکی کند کرده و همزمان پاک‌سازی ناهمگام صفحات را آغاز می‌کند.
  • memory.max (سقف قطعی): اگر مصرف به این حد نهایی برسد و آزادسازی حافظه ناموفق باشد، ابزار OOM تنها فرآیندهای متخلف همان بخش را متوقف می‌کند و سایر کاربران روی گیت‌وی بدون هیچ اختلالی به کار خود ادامه می‌دهند.
# پیکربندی آستانه‌های دفاعی برای یک بخش VPN با ظرفیت ۲ گیگابایت
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) روی پل‌های مجازی شبکه بود. در لینوکس نوین، cgroups v2 مستقیماً با eBPF و نوع برنامه BPF_PROG_TYPE_CGROUP_SKB هماهنگ می‌شود.

مدیران شبکه می‌توانند فیلترهای eBPF را از طریق قلاب‌های BPF_CGROUP_INET_INGRESS و BPF_CGROUP_INET_EGRESS مستقیماً به پوشه cgroup متصل کنند. هر سوکتی که توسط فرآیندهای آن گروه باز شود، این قوانین را به شکل خودکار اجرا خواهد کرد.

// نمونه برنامه 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";

اتصال این برنامه به گره گروه کاربری، کنترل پهنای باند را در مسیر داده‌های هسته پیاده‌سازی کرده و بار تعویض زمینه به فضای کاربر را از میان برمی‌دارد.

تقویت امنیتی بخش‌های چندکاربره در RiceGuard

در شبکه گیت‌وی‌های پیشرفته پلتفرم VPNRice، موتور اختصاصی RiceGuard از فناوری cgroups v2 برای ایجاد بخش‌های مجزا و پایدار میان کاربران در خوشه‌های سازمانی بهره می‌گیرد.

منابع سرور فیزیکی (۶۴ هسته پردازنده، ۲۵۶ گیگابایت رم، ۲ درگاه 100GbE)
                               |
                               v
+-------------------------------------------------------------------+
|               مدیر بخش‌های کاربری RiceGuard cgroups v2            |
+-------------------------------------------------------------------+
       |                                             |
       v بخش کاربر Alpha (رده طلایی)                 v بخش کاربر Beta (رده نقره‌ای)
+-----------------------------+               +-----------------------------+
| - 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 نرخ ورودی: 10Gbps    |               | - eBPF نرخ ورودی: 2Gbps     |
+-----------------------------+               +-----------------------------+
       |                                             |
       v                                             v
  عدم سرریز منابع به همسایگان                   عدم سرریز منابع به همسایگان

۱. زمان‌بندی مستقل پردازنده

سامانه RiceGuard با ترکیب سهم متناسب cpu.weight در زمان بار کاری سنگین و سقف قاطع cpu.max از طریق زمان‌بند CFS، تعادل را برقرار می‌سازد. بخش‌های کاربری اولویت‌دار سهم بالاتری دریافت می‌کنند تا عملیات رمزگشایی حتی در زمان اوج ترافیک بدون افت کارایی ادامه یابد.

۲. پایش فشار حافظه بدون قفل

به جای اجرای برنامه‌های سنجش مداوم در فضای کاربر، سامانه RiceGuard مستقیماً داده‌های آماری Pressure Stall Information (PSI) را در هر گروه بررسی می‌کند (/sys/fs/cgroup/vpn/<کاربر>/memory.pressure). با مشاهده علائم اولیه فشار حافظه، اندازه پنجره UDP کلاینت‌ها تنظیم شده و از افت بسته پیشگیری می‌شود.

۳. ترکیب با فضاهای نام شبکه

موتور RiceGuard قابلیت‌های cgroups v2 را با فضاهای نام شبکه لینوکس (Network Namespace) ادغام می‌نماید. سوکت‌های هر کاربر به جدول‌های مسیریابی اختصاصی محدود می‌شوند. این معماری همراه با فیلترهای eBPF مانع از نشت بسته‌ها به محیط‌های اشتراکی شده و حریم خصوصی و ثبات عملکرد را تضمین می‌کند.