eBPF: چگونه Linux یک کرنل امن، سریع و قابل برنامه‌ریزی به دست آورد

اشتراک‌گذاری:
eBPF: چگونه Linux یک کرنل امن، سریع و قابل برنامه‌ریزی به دست آورد

مشکل روش قدیمی

دهه‌ها بود که اگر می‌خواستید نحوه پردازش بسته‌ها، فیلتر کردن syscall‌ها یا ردیابی گلوگاه‌های عملکردی در کرنل Linux را تغییر دهید، تنها دو گزینه داشتید: یا یک kernel patch ارسال کنید و سال‌ها منتظر بمانید تا در توزیع‌ها اعمال شود، یا یک kernel module بنویسید. Kernel module‌ها قدرتمندند اما خطرناک. یک باگ در یک ماژول، کل سیستم را از پا در می‌آورد. آن‌ها به نسخه خاصی از کرنل وابسته‌اند، به طوری که ماژولی که برای 5.15 ساخته شده روی 6.1 کار نمی‌کند. استقرار آن‌ها روی یک ناوگان ناهمگون یعنی نگهداری ده‌ها build مجزا. برای شرکتی که صدها هزار سرور اداره می‌کند، این دیگر یک مشکل مهندسی نیست — بلکه یک بار مسئولیت است.

eBPF این مشکل را حل می‌کند. این فناوری به شما اجازه می‌دهد منطق سفارشی را — برای شبکه، امنیت یا observability — به‌صورت ایمن، قابل حمل و بدون ریبوت کردن هیچ چیز، درون کرنل در حال اجرا تزریق کنید.

eBPF در واقع چیست

BPF (Berkeley Packet Filter) در سال ۱۹۹۲ برای سریع‌تر کردن tcpdump ساخته شد. به جای کپی کردن هر بسته به userspace برای فیلتر کردن، BPF یک ماشین مجازی کوچک درون کرنل اجرا می‌کرد تا بسته‌ها را همانجا فیلتر کند. این ابزار از ابتدا محدود طراحی شده بود.

در سال ۲۰۱۴، Alexei Starovoitov و Daniel Borkmann نسخه extended BPF را در Linux 3.18 معرفی کردند. مجموعه دستورالعمل‌ها برای معماری‌های ۶۴ بیتی بازطراحی شد، تعداد رجیسترها افزایش یافت و — مهم‌تر از همه — نقاط اتصال (hook points) بسیار فراتر از فیلتر بسته گسترش یافتند. امروز می‌توانید برنامه‌های eBPF را به مسیرهای ingress و egress شبکه، tracepoint‌ها، kprobe‌ها (پروب‌های پویای توابع کرنل)، uprobe‌ها (پروب‌های توابع userspace) و نقاط ورود/خروج syscall متصل کنید. کرنل قابل برنامه‌ریزی شد.

مدل ایمنی همان چیزی است که این فناوری را در محیط production قابل استفاده می‌کند. یک برنامه eBPF را به زبان C محدودشده می‌نویسید، یا با استفاده از کتابخانه‌های Go یا Rust که eBPF bytecode تولید می‌کنند. پیش از اجرای برنامه، verifier کرنل هر مسیر اجرای ممکن را به‌صورت استاتیک تحلیل می‌کند: هیچ حلقه نامحدودی، هیچ دسترسی خارج از محدوده حافظه، هیچ محاسبه اشاره‌گر ناامنی. برنامه‌هایی که تأیید را رد کنند، کاملاً رد می‌شوند. برنامه‌هایی که تأیید شوند، به کد ماشین بومی JIT-compiled می‌شوند — بنابراین هیچ سربار interpreter در زمان اجرا وجود ندارد. نتیجه، کدی است که درون کرنل با سرعت نزدیک به بومی اجرا می‌شود، با تضمین‌های ایمنی که کرنل به‌صورت خودکار اعمال می‌کند.

XDP و انقلاب شبکه

چشمگیرترین قابلیت eBPF برای تیم‌های زیرساختی XDP — eXpress Data Path است. hook‌های XDP پیش از آن که stack شبکه کرنل بسته‌ای را پردازش کند اجرا می‌شوند. پیش از تخصیص sk_buff. پیش از جستجوی routing. پیش از هر چیزی. یک برنامه XDP می‌تواند یک بسته را در لایه درایور NIC با سرعتی بیش از ۱۰۰ میلیون بسته در ثانیه روی سخت‌افزار معمولی دراپ، redirect یا تغییر دهد.

برای دفاع در برابر DDoS، این همه چیز را تغییر می‌دهد. یک حمله حجمی که مسیر شبکه عادی کرنل را اشباع می‌کند — صف‌های socket را پر می‌کند، CPU را با interrupt handling تخلیه می‌کند — در لایه درایور دراپ می‌شود، پیش از اینکه هیچ‌کدام از آن سربارها رخ دهد. Cloudflare از برنامه‌های eBPF مبتنی بر XDP برای جذب حملات DDoS در مقیاس terabit استفاده می‌کند. بسته هرگز به stack نمی‌رسد. سرور سرپا می‌ماند.

Meta فراتر رفت. آن‌ها کل زیرساخت load balancing خود — که پیش از این بر پایه IPVS ساخته شده بود — را با Katran، یک load balancer متن‌باز eBPF/XDP جایگزین کردند. Katran روی هر سرور در data center‌های Meta اجرا می‌شود و ترافیک در مقیاس Facebook را بدون نیاز به appliance‌های اختصاصی load balancer مدیریت می‌کند. انعطاف‌پذیری eBPF یعنی آن‌ها می‌توانند منطق load balancing را با بارگذاری یک برنامه جدید به‌روزرسانی کنند، نه با ریبوت یا استقرار مجدد سخت‌افزار.

شبکه Kubernetes از طریق Cilium

مشکل شبکه Kubernetes دشوار است. هر pod به یک IP نیاز دارد. Podها باید در سراسر nodeها با هم ارتباط برقرار کنند. network policy‌ها باید اعمال شوند. load balancing سرویس‌ها باید انجام شود. پاسخ سنتی iptables بود — یک موتور قانون که مقیاس‌پذیر نیست. با چند هزار قانون، جستجوی iptables O(n) می‌شود و مصرف CPU به‌وضوح افزایش می‌یابد. با ده‌ها هزار pod، کاملاً از کار می‌افتد.

Cilium iptables را کاملاً با eBPF جایگزین می‌کند. routing بین pod‌ها، load balancing سرویس‌ها و اعمال network policy همگی در برنامه‌های eBPF متصل به رابط‌های شبکه اتفاق می‌افتند. جستجوها از طریق eBPF hash map به‌صورت O(1) انجام می‌شوند. اعمال policy در مسیر سریع کرنل رخ می‌دهد. Cilium همچنین HTTP، gRPC و Kafka را در Layer 7 درک می‌کند — زیرا برنامه‌های eBPF می‌توانند محتوای بسته را بررسی کنند، نه فقط header‌ها را.

میزان پذیرش گویاست. AWS EKS، Google GKE و Azure AKS همگی Cilium را به‌عنوان CNI پیش‌فرض یا توصیه‌شده ارائه می‌دهند. cluster‌های Kubernetes با صدها node و هزاران pod از eBPF برای هر تصمیم بسته استفاده می‌کنند، و گلوگاه iptables از بین رفته است.

Observability بدون Instrumentation

ابزارهای سنتی APM نیاز به تغییر کد دارند: کتابخانه‌ای اضافه کنید، توابع را wrap کنید، مجدداً deploy کنید. observability مبتنی بر eBPF هیچ‌کدام از این‌ها را نمی‌خواهد. یک kprobe یا uprobe را به هر تابع کرنل یا userspace متصل می‌کنید و داده جمع‌آوری می‌کنید — latency، آرگومان‌ها، مقادیر بازگشتی، stack trace‌ها — بدون اینکه application را تغییر دهید.

Netflix از bpftrace در محیط production دقیقاً برای همین استفاده می‌کند. bpftrace یک زبان scripting سطح‌بالا برای eBPF است، قابل مقایسه با آنچه DTrace روی Solaris بود. یک one-liner می‌تواند هر disk I/O بیشتر از 1ms را روی یک host در production ردیابی کند، یا latency‌های اتصال TCP را در یک histogram نمایش دهد، یا پیدا کند کدام پروسه‌ها بیشترین context switch را ایجاد می‌کنند — همه این‌ها با سربار تک‌رقمی بر حسب درصد، نه هزینه ۱۰ تا ۳۰ درصدی profiling سنتی.

Pixie این را برای Kubernetes یک گام فراتر می‌برد و با استفاده از eBPF هر سرویس در یک cluster را به‌صورت خودکار instrumentation می‌کند تا داده‌های request/response، توزیع latency و نرخ خطا را بدون هیچ پیکربندی per-service ضبط کند. بدون sidecar. بدون SDK integration. observability در لایه کرنل تعبیه شده است.

اعمال امنیت در سطح کرنل

seccomp-BPF سال‌هاست که syscall‌ها را در container‌های Linux فیلتر می‌کند — همان چیزی است که Docker، Chrome و Firefox برای محدود کردن system call‌هایی که یک پروسه sandboxed می‌تواند انجام دهد استفاده می‌کنند. این سر باریک امنیت eBPF است.

سر گسترده‌تر، اعمال امنیت در زمان اجرا (runtime) است. Falco از eBPF برای نظارت بر هر syscall در یک cluster Kubernetes استفاده می‌کند و رفتار مشکوک را هشدار می‌دهد — container‌ای که یک shell راه‌اندازی می‌کند، پروسه‌ای که روی /etc می‌نویسد، اتصال شبکه به یک IP غیرمنتظره. Tetragon، از پروژه Cilium، یک گام فراتر می‌رود: نه‌تنها می‌تواند نقض policy را در زمان واقعی تشخیص دهد بلکه می‌تواند آن را با کشتن پروسه متخلف پیش از تکمیل syscall اعمال کند. منطق policy از طریق eBPF در کرنل اجرا می‌شود. هیچ پنجره‌ای بین تشخیص و پاسخ وجود ندارد.

قابلیت حمل: CO-RE و BTF

یکی از شکایات باقی‌مانده درباره eBPF وابستگی به نسخه خاص کرنل بود. یک برنامه eBPF که در برابر header‌های کرنل 5.15 کامپایل شده ممکن است روی 6.1 کار نکند اگر ساختار struct‌ها تغییر کرده باشد. CO-RE — Compile Once, Run Everywhere — این مشکل را حل می‌کند. با BTF (BPF Type Format)، کرنل اطلاعات نوع داخلی خود را در زمان اجرا در معرض دید قرار می‌دهد. eBPF loader از BTF برای relocate کردن دسترسی‌های field در زمان بارگذاری استفاده می‌کند و برنامه کامپایل‌شده را با هر کرنلی که در واقع در حال اجراست سازگار می‌کند. اکنون یک باینری eBPF واحد می‌تواند روی یک ناوگان کاملاً مختلط از نظر نسخه کرنل بدون نیاز به کامپایل مجدد اجرا شود.

آنچه در پیش است

Microsoft به‌طور فعال در حال port کردن eBPF به Windows از طریق پروژه ebpf-for-windows است. فروشندگان SmartNIC در حال افزودن hardware offload برای برنامه‌های eBPF هستند، به‌طوری که فیلتر XDP می‌تواند روی خود NIC اجرا شود و CPU‌های host را کاملاً آزاد کند. runtime‌های eBPF در user-space در حال بلوغ هستند و extensibility به سبک eBPF با sandbox خارج از کرنل را ممکن می‌سازند.

الگوی زیربنایی یکسان است: یک مکانیزم extensibility قابل برنامه‌ریزی با تضمین‌های قوی ایمنی، برای هر چیزی که باید با سرعت production تکامل یابد، از کد استاتیک کرنل بهتر عمل می‌کند. eBPF تنها شبکه و observability در Linux را بهبود نداد — بلکه مدلی را که رفتار کرنل از طریق آن گسترش می‌یابد تغییر داد. تیم‌های زیرساختی که این را زودتر درک کردند، سریع‌تر، ایمن‌تر و در مقیاس بالاتری نسبت به کسانی که هنوز قوانین iptables و kernel module می‌نویسند در حرکتند.

اشتراک‌گذاری:
eBPF: بازشکل‌دهی زیرساخت Linux در مقیاس بزرگ | AIO APEX