eBPF давно перестал быть узкоспециализированной игрушкой для kernel-энтузиастов и исследователей внутренностей Linux. Сегодня с ним, так или иначе, сталкиваются не только SRE, но вообще все, кто разрабатывает системы, близкие к сети, производительности или безопасности: от авторов сетевых плагинов (CNI) и прокси, до разработчиков кастомных агентских решений, observability-инструментов и low-level инфраструктурных компонентов. Даже если вы никогда не писали eBPF-код руками, есть хороший шанс, что он уже работает в вашей системе — тихо, незаметно и с довольно широкими полномочиями.
Чаще всего eBPF проявляется через удобные CLI, библиотеки и дашборды: установили агент, включили, и, внезапно, система знает о происходящем больше, чем strace, tcpdump и половина метрик вместе взятых. Но за этим комфортом скрывается нетривиальный механизм исполнения пользовательского кода прямо внутри ядра Linux — с жёсткими правилами валидации, ограниченной моделью исполнения и целым набором архитектурных компромиссов, о которых обычно не принято говорить в маркетинговых описаниях.
В этой статье мы заглянем под капот eBPF и разберёмся, как именно eBPF-программы попадают в ядро, что с ними происходит до и во время выполнения, и почему одни сценарии применения действительно оправданы, а другие выглядят впечатляюще только на слайдах. Цель простая и практичная: сформировать понимание, где eBPF приносит реальную пользу при эксплуатации и разработке систем, а где разумнее выбрать более скучный, но предсказуемый инструмент — и спокойно спать по ночам.
План на статью получился довольно большой и чтобы сразу понимать чего стоит ожидать:
Как и зачем eBPF появился в ядре Linux: какие ограничения старых механизмов он убрал (историческая справка)
Базовая модель eBPF: что такое программы, карты и точки подключения в ядре
Гарантии безопасности eBPF: почему код в ядре не кладёт систему при каждой ошибке
Производительность eBPF: почему это не медленный интерпретатор, а вполне быстрый dataplane
Ограничения eBPF как платформы: что можно делать, а что принципиально запрещено
Типичные сценарии использования eBPF в Linux: сеть, трейсинг, безопасность
Где eBPF выигрывает, а где nftables и netfilter проще и надёжнее
Почему сети Kubernetes — хороший пример для eBPF, но не единственный
Простой eBPF на практике: XDP-фильтр, который сложно повторить стандартными средствами dataplane linux
Как eBPF используется в Cilium и Calico на высоком уровне, без ухода в детали реализации CNI
Чем eBPF-подход отличается от классического сетевого стека с точки зрения ядра
Практические выводы для SRE и Dev: чему действительно стоит научиться, чтобы понимать eBPF-решения
Как и зачем eBPF появился в ядре Linux: какие ограничения старых механизмов он убрал
При этом eBPF не появился на пустом месте. До него в Linux уже существовал BPF, но в куда более приземлённом виде. Классический BPF (сейчас его обычно называют cBPF — classic BPF) пришёл в Linux из BSD в конце 90-х и изначально решал ровно одну задачу: фильтрацию сетевых пакетов. Первое появление BPF в Linux датируется примерно ядром 2.1.x (1997–1998 годы), а широкое практическое применение он получил в 2.2, когда tcpdump и libpcap стали стандартным инструментом диагностики. По сути, BPF представлял собой небольшой стековый виртуальный процессор с жёстко ограниченным набором инструкций, без циклов и с крайне простым execution model. Его ключевая ценность была не в выразительности, а в безопасности: фильтр можно было загрузить в ядро, не опасаясь, что он зависнет или полезет куда не следует.
Однако у cBPF довольно быстро проявились архитектурные ограничения:
узкая специализация — только packet filtering
фиксированный контекст выполнения — пакет на входе, verdict на выходе
ограниченный instruction set без возможности нормальной логики
отсутствие доступа к состоянию ядра за пределами заранее заданных полей
Фактически cBPF был хорошим калькулятором условий, но не инструментом расширения ядра. Переломный момент случился сильно позже. В 2013–2014 годах в ядре начали появляться патчи от Алексея Старовойтова, которые сначала выглядели как просто улучшенный BPF. Ключевой коммит — bpf: introduce extended BPF, попавший в Linux 3.18 (декабрь 2014). Именно тогда появился термин eBPF, хотя поначалу он воспринимался скорее как внутренняя эволюция, а не новая платформа.
Отличия eBPF от cBPF были принципиальными:
64-битная RISC-архитектура вместо простого интерпретатора
регистровая модель (10 регистров), что резко упростило JIT
появление верификатора, который анализирует control flow, работу с памятью и гарантирует завершение программы
контекстно-независимая модель — одна и та же VM, но ��азные точки привязки (kprobe, tracepoint, socket, TC, XDP, LSM и т.д.)
maps как безопасный механизм обмена состоянием между ядром и user space.
Важно, что eBPF изначально проектировался не как фильтр, а как универсальный in-kernel execution engine, при этом с жёсткими гарантиями безопасности. Именно верификатор стал тем компромиссом, который позволил загружать код в ядро без модуля, но при этом не превращать систему в минное поле.
Дальнейшая эволюция шла итеративно:
Linux 4.1 — стабилизация verifier’а и JIT
4.14 — maps, tail calls и более сложные программы
4.18+ — XDP, cgroup hooks, socket filters
5.x — BTF, CO-RE и постепенное превращение eBPF в runtime внутри ядра
В итоге eBPF стал логическим продолжением cBPF, но по факту — совершенно другим инструментом. Если classic BPF отвечал на вопрос пропускать этот пакет или нет, то eBPF отвечает на вопрос, что мы вообще хотим сделать в этом месте ядра и можно ли это сделать безопасно. Именно поэтому eBPF закрыл тот самый разрыв между идеей и реализацией. Там, где раньше требовался патч ядра или модуль с сомнительным жизненным циклом, теперь достаточно загрузить проверяемую программу. Kernel-hacking никуда не делся, но перестал быть обязательным этапом для каждой новой идеи — и это, пожалуй, главное изменение за последние десять лет.
Базовая модель eBPF: что такое программы, карты и точки подключения в ядре

Базовая модель eBPF в ядре Linux состоит из трёх ключевых сущностей: eBPF-программ, карт (maps) и точек подключения (hooks), каждая из которых решает строго определённую задачу. eBPF-программа — это код, скомпилированный в eBPF bytecode и загружаемый в ядро Linux. Перед запуском он проходит верификатор, который проверяет безопасность выполнения: отсутствие бесконечных циклов, корректный доступ к памяти и соблюдение лимитов. Программа исполняется строго в контексте события, к которому она привязана, и не может работать произвольно в фоне. Например, eBPF-программа, подключённая через kprobe к функции do_sys_openat2, может читать аргументы системного вызова и анализировать, какие файлы и какими процессами открываются.
Точки подключения (hooks)

Hooks определяют, когда и в каком контексте выполняется eBPF-программа. Наиболее распространённые варианты — kprobes, позволяющие подключаться к конкретным функциям ядра; tracepoints, предоставляющие стабильный ABI для событий трассировки; XDP-хуки, срабатывающие на самой ранней стадии обработки сетевого пакета; а также cgroup-хуки, применяемые для контроля сетевых и ресурсных операций групп процессов. Выбор точки подключения напрямую влияет на доступный контекст и стоимость выполнения: XDP работает максимально быстро, но с ограниченным API, тогда как kprobe даёт больше гибкости ценой большего overhead.
Карты eBPF (maps)

Карты eBPF — это механизм хранения состояния и обмена данными между вызовами программ и между ядром и user space. Они представляют собой типизированные структуры данных: hash-таблицы, массивы, per-CPU карты, LRU-кэши и другие варианты. Например, eBPF-программа может использовать BPF_MAP_TYPE_HASH, где ключом является PID процесса, а значением — счётчик вызовов read(). При каждом срабатывании программы значение обновляется, а user space-приложение периодически считывает карту для агрегации и визуализации метрик.
Верификатор eBPF (Verification)

Перед тем как eBPF-программа начнёт выполняться в ядре, она проходит через eBPF-верификатор — один из самых строгих компонентов всей подсистемы. Его задача — доказать, что программа безопасна: все пути выполнения конечны, обращения к памяти валидны, а типы данных согласованы с контекстом и используемыми картами. Верификатор статически анализирует bytecode, отслеживает состояние регистров и гарантирует, что программа не выйдет за пределы разрешённого API. Например, попытка обратиться к полю структуры, недоступному для конкретного hook’а, будет отклонена ещё на этапе загрузки, а программа просто не попадёт в ядро. Именно верификатор определяет, что eBPF — это контролируемое выполнение кода, а не произвольный модуль ядра.
JIT-компиляция
После успешной верификации eBPF-программа может быть выполнена интерпретатором либо скомпилирована JIT-компилятором в нативный машинный код для текущей архитектуры. На практике почти всегда используется JIT, поскольку он радикально снижает overhead исполнения и делает eBPF пригодным для hot-path сценариев, таких как XDP или high-frequency tracepoints. Важный момент: JIT-компиляция происходит уже после всех проверок, поэтому в ядро попадает только код, доказанно корректный с точки зрения модели безопасности.
BPF helpers calls

eBPF-программы намеренно ограничены в возможностях: они не могут напрямую вызывать произвольные функции ядра. Вместо этого используется строго определённый интерфейс BPF helpers — набор функций, которые ядро разрешает вызывать из eBPF-кода. Через helpers реализуется вся практическая логика: чтение памяти, работа с картами , получение метаданных, вывод событий в user space и управление сетевыми пакетами. Набор доступных helpers зависит от типа hook’а, что дополнительно ограничивает контекст выполнения и упрощает верификацию. Фактически helpers — это контракт между eBPF-программой и ядром, который задаёт границы допустимого поведения.
С учётом всех компонентов полный жизненный цикл eBPF выглядит так: программа загружается → проходит верификацию → при необходимости JIT-компилируется → привязывается к hook’у → при наступлении события выполняется с использованием helpers → сохраняет или читает состояние из карт. Такое разделение ответственности позволяет ядру гарантировать безопасность, разработчику — контролировать логику, а эксплуатации — получать высокопроизводительные инструменты наблюдаемости и контроля без вмешательства в код ядра.
Гарантии безопасности eBPF: почему код в ядре не кладёт систему при каждой ошибке
Один из главных страхов при словах код в ядре — что любая опечатка мгновенно превратит систему в красиво мигающий kernel panic. С eBPF этот сценарий не работает не потому, что разработчики стали осторожнее, а из-за жёсткой верификации при загрузке программы.
Перед выполнением eBPF-код проходит через verifier, который статически перебирает все возможные пути выполнения и формально доказывает, что программа всегда завершается, не выходит за границы памяти, не обращается к произвольным адресам и использует только разрешённые helper-функции. Каждый регистр отслеживается с точки зрения типа и допустимого диапазона, а любые недоказуемые предположения — будь то проверка указателя или условный if — трактуются против программы. Если verifier не может гарантировать безопасность хотя бы одного пути, код просто не загружается в ядро.
В результате даже логически ошибочная eBPF-программа заканчивается максимум отказом в загрузке или некорректной метрикой, но не падением системы. Это и делает eBPF практичным инструментом, а не игрой в перезагрузи сервер, если повезёт. Для тех, кто хочет глубже разобраться в работе verifier’а и не боится C, есть подробные разборы.
Производительность eBPF: почему это не медленный интерпретатор, а вполне быстрый dataplane
Важно понять, за счёт чего достигается эта производительность. В типичных dataplane-сценариях eBPF выигрывает не потому, что быстрее C, а потому что позволяет вырезать лишние слои обработки. XDP-хук срабатывает до создания skb, до netfilter и до большинства сетевых абстракций, которые удобны для универсальности, но дороги по CPU.
В результате путь пакета упрощается до минимального набора действий: загрузили заголовки, приняли решение, либо дропнули пакет, либо передали дальше — без аллокаций, без локов и без прыжков между подсистемами. Дополнительно eBPF хорошо масштабируется по ядрам, потому что его модель исполнения изначально заточена под параллельность. Отсутствие блокирующих операций, короткое время жизни программ и явное управление состоянием через карты позволяют обрабатывать трафик почти линейно с ростом числа CPU. Именно поэтому eBPF часто выигрывает даже у тщательно оптимизированного userspace-dataplane: там, где DPDK или AF_XDP требуют сложной настройки потоков, eBPF просто встаёт в нужную точку ядра и начинает работать.
Это не означает, что eBPF универсально быстрее всего остального. Как только dataplane начинает требовать сложных алгоритмов, динамических структур данных или длинных цепочек решений, преимущество быстро теряется. Но в своей нише — быстрые и детерминированные решения на каждом пакете — eBPF настолько эффективно закрывает разрыв между гибкостью и скоростью, что его уже корректнее рассматривать не как оптимизацию, а как полноценный элемент сетевой архитектуры Linux.
Ограничения eBPF как платформы: что можно делать, а что принципиально запрещено
Продолжая разговор о производительности, важно отметить, что eBPF плохо переносит рост сложности не только по объёму ко��а, но и по времени выполнения. Программа может быть компактной, но если её логика требует множества условных переходов, вложенных проверок или сложного накопления состояния между событиями, verifier начинает выступать не как формальный проверяющий, а как активный архитектурный ограничитель.
Типичный симптом — программа логически корректна, но не проходит верификацию из-за слишком большого числа путей исполнения или невозможности доказать их конечность.
Отсюда следует важный практический вывод: eBPF рассчитан на локальные, статически анализируемые решения, где поведение на каждом событии можно описать как короткую и предсказуемую функцию. Он отлично справляется с задачами вида: посмотрел → решил → зафиксировал, но быстро ломается, когда его пытаются превратить в систему с богатым состоянием или сложной логикой. Именно поэтому maps используются не как носитель логики, а как минимальный слой состояния, а все нетривиальные вычисления выносятся за пределы ядра.
В этом смысле ограничения eBPF — не столько технический барьер, сколько архитектурный контракт: ядро готово выполнять ваш код часто и быстро, но только при условии, что этот код остаётся простым, детерминированным и легко проверяемым. Всё остальное — пожалуйста, но уже в userspace, где цена ошибки и сложность управления существенно ниже.
Типичные сценарии использования eBPF в Linux: сеть, трейсинг, безопасность
Если перейти от теории к практике, eBPF в Linux давно перестал быть нишевым инструментом и уверенно обжился сразу в нескольких ключевых доменах. В сетях это прежде всего XDP и TC: фильтрация пакетов на ранних этапах, защита от DDoS, L3/L4-балансировка, NAT и телеметрия трафика без походов в userspace.
В трейсинге eBPF фактически стал стандартом: kprobes, uprobes и tracepoints позволяют снимать latency, системные вызовы и поведение приложений с минимальным оверхедом. Именно поэтому инструменты вроде bpftrace, perf и современных observability-платформ опираются на него как на базовый механизм.
В безопасности сценарий похожий: eBPF-программы перехватывают системные вызовы, сетевые события и изменения в файловой системе, реализуя runtime-политику — что разрешено, а что нет — без патчей ядра и перезапуска сервисов. В итоге eBPF закрывает сразу три класса задач: быстрый dataplane, глубокую наблюдаемость и гибкий runtime-контроль, и делает это в рамках одной платформы.
Важно, что во всех этих сценариях eBPF используется не как smart хук, а как универсальный механизм встраивания логики в критические точки ядра. Разница лишь в том, на какие события реагирует программа и какие данные она обрабатывает. С точки зрения эксплуатации это даёт редкое для Linux свойство — единый подход к сетям, наблюдаемости и безопасности без зоопарка несовместимых инструментов и патчей.
Где eBPF выигрывает, а где nftables и netfilter проще и надёжнее
По мере роста популярности eBPF возникает риск поддаться индустриальному энтузиазму: кажется, что эта технология должна заменить всё подряд. На самом деле инженерный выбор гораздо приземлённее: eBPF и классический Linux-стек сети дополняют друг друга, а не конкурируют напрямую. Стандартные механизмы Linux — netfilter и nftables — уже много лет решают точечные сетевые задачи эффективно, предсказуемо и с богатой декларативной моделью правил. Они отлично справляются с stateful-фильтрацией, NAT и другими задачами, которые мы обычно выражаем как если условие A — сделать B.
Сценарии, в которых стоит предпочесть стандартный dataplane:
Простые политики доступа, firewall, NAT и трансляция портов. Если логику можно выразить набором правил без программирования, nftables обеспечивает понятное поведение, лёгкую отладку и совместимость с инструментами администрирования.
Application-layer политика без глубокого анализа трафика. Например, глубокая инспекция HTTP-сессий, L7-фильтрация или сложные ACL часто проще и эффективнее делаются через conntrack и сопутствующие механизмы, чем программировать eBPF-байткод под такие задачи.
Где выгоднее использовать существующие оптимизации ядра. В ряде случаев стандартные таблицы и наборы (ipset) показывают лучшую пропускную способность на больших наборах IP-адресов без накладных расходов интерпретатора eBPF-виртуальной машины.
Когда eBPF действительно имеет смысл:
XDP и раннее принятие решения. eBPF позволяет обрабатывать пакеты до образования skb-структур и прохождения классического сетевого пути, что критично для защиты от DDoS, L3/L4-фильтрации на входе и снижения задержек.
Оптимизация маршрутизации и балансировки. В ядре возможно реализовать подключение пользователейских алгоритмов балансировки соединений и даже обход NAT-шагов, что снижает накладные расходы обработки сервисного трафика.
Наблюдаемость и глубокий трейсинг. Возможность встраивать инструменты наблюдаемости непосредственно в ядро и отслеживать события в контексте процесса даёт доступ к данным, недоступным классическим userspace-агентам.
eBPF раскрывает потенциал именно там, где нужна усиленная гибкость или очень раннее вмешательство в обработку пакета. Важно понимать, что eBPF — это не только скорость, но и стоимость исполнения. Программы выполняются через виртуальную машину внутри ядра, что добавляет некоторый оверхед и требует аккуратного проектирования логики. Если eBPF-подход приводит к тому, что он занимает значительную долю CPU при малой выигрышной задаче, то стандартный стек сети может оказаться рациональнее с точки зрения эксплуатационных затрат.
Хорошее правило инженерной дисциплины: если задачу можно выразить декларативно и она хорошо ложится на модель таблиц, nftables предпочтительнее; если же требуется ранний контроль, глубокий доступ к состоянию ядра или нестандартная логика, тогда eBPF предоставляет уникальные преимущества.
Почему сети Kubernetes — хороший пример для eBPF, но не единственный
Kubernetes часто приводят как канонический пример для eBPF не потому, что он требует каких-то уникальных возможностей ядра, а потому что в нём сходятся почти все болевые точки классического сетевого стека. Высокая плотность сервисов, короткоживущие endpoint’ы, постоянные обновления правил и необходимость принимать решения на каждом пакете делают таблицы правил и длинные цепочки хуков плохо масштабируемыми.
В такой среде eBPF полезен не столько как ускоритель, сколько как способ удержать сложность под контролем, не превращая сеть в набор взаимозависимых костылей. При этом важно не путать причину и следствие: eBPF не стал популярным из-за Kubernetes — Kubernetes просто раньше остальных показал, что модель с тяжёлым control plane и перегруженным dataplane перестаёт работать.
Cilium, Calico и подобные решения используют eBPF ровно там, где требуется быстрое и локальное принятие решений, а не потому, что это обязательный атрибут. Те же самые подходы одинаково применимы на bare metal, в высоконагруженных прокси и в системах, где Kubernetes вообще не фигурирует. В этом смысле Kubernetes — не цель и не ограничение, а удобная лупа, делающая архитектурные проблемы Linux-сетей заметными раньше и жёстче.
Простой eBPF на практике: XDP-фильтр, который сложно повторить стандартными средствами
Чтобы eBPF перестал выглядеть как абстрактная концепция, имеет смысл посмотреть на минимальный, но показательный пример. Возьмём XDP-программу, которая отбрасывает пакеты на раннем этапе обработки — прямо на входе в сетевой драйвер (NIC). Такой фильтр, например, может дропать TCP-пакеты на определённый порт до того, как они попадут в netfilter, conntrack и весь остальной привычный сетевой dataplane linux. Повторить это с iptables или nftables невозможно: они работают существенно позже.
// xdp_drop.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
SEC("xdp")
int drop_tcp_8080(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != __constant_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol != IPPROTO_TCP)
return XDP_PASS;
struct tcphdr *tcp = (void *)ip + ip->ihl * 4;
if ((void *)(tcp + 1) > data_end)
return XDP_PASS;
if (tcp->dest == __constant_htons(8080))
return XDP_DROP;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";Необходимо установить Clang чтобы скомпилировать C:
clang -O2 -g -target bpf -c xdp_drop.c -o xdp_drop.oСкрипт для просмотра всех доступных сетевых инструментов (вдруг и такие будут читать):
ip -4 -o addr show | awk '{print $2, $4}' | cut -d/ -f1Загружаем программу в ядро и цепляемся к интерфейсу (например, eth0):
ip link set dev eth0 xdp obj xdp_drop.o sec xdpС этого момента все TCP-пакеты на порт 8080 будут отбрасываться ещё до аллокации skb. Ни conntrack, ни iptables/nftables, ни даже IP-стек Linux об этом не узнают — пакета для них просто не существовало. Чтобы выгрузить код из ядра:
ip link set dev eth0 xdp offПример хорошо иллюстрирует ключевую особенность eBPF: он позволяет внедрять логику в такие точки ядра, куда традиционные сетевые механизмы просто не дотягиваются. И да, именно поэтому eBPF так органично прижился в высоконагруженных сетях, включая Kubernetes CNI, но этим его полезность вовсе не ограничивается. Далее разберемся где именно был перехват пакета визуально.

Если посмотреть на схему прохождения пакета в Linux, становится хорошо видно, насколько рано срабатывает XDP. На картинке этот момент находится в самом начале пути: сразу после приёма пакета сетевой картой и ещё до появления skb. Всё, что изображено правее — ingress qdisc, netfilter (raw, mangle, nat, filter), conntrack, routing decision, IP-стек и, тем более, application layer — начинает существовать только если XDP-программа вернула XDP_PASS. В этом случае пакет допускается к дальнейшей обработке и продолжает движение по привычному сетевому dataplane Linux.
В нашем примере для TCP-пакетов на порт 8080 этого не происходит: программа возвращает XDP_DROP, и пакет отбрасывается в точке, которая на схеме предшествует вообще всем классическим сетевым слоям. Никаких правил firewall, состояний conntrack или маршрутизации для него не возникает — до этих этапов выполнение просто не доходит. Именно в этом и заключается практический смысл XDP: решение о судьбе пакета принимается максимально рано и максимально дёшево. XDP_PASS позволяет пропустить трафик дальше без вмешательства, а XDP_DROP — мгновенно вычеркнуть нежелательные пакеты из dataplane, не тратя ресурсы ядра на последующую обработку.
Как eBPF используется в Cilium и Calico на высоком уровне, без ухода в детали реализации CNI
Отдельно важно понимать, что Calico не привязан исключительно к eBPF-dataplane. Его можно развернуть как поверх классического Linux network stack (iptables/nftables + routing), так и в eBPF-режиме, переключая dataplane без смены самого CNI. Это даёт редкую для Kubernetes-мира опцию: начать с максимально консервативной схемы, а затем включить eBPF там, где он действительно нужен, не переделывая архитектуру кластера. На высоком уровне eBPF в Cilium и Calico решает одну и ту же задачу — перенос сетевой логики из userspace и iptables/nftables ближе к ядру Linux, пока пакет ещё не потерял контекст и не прошёл через десятки слоёв абстракций. Но дальше начинаются принципиальные различия.
Cilium — eBPF-first по дизайну. Он изначально проектировался как система, в которой eBPF — основной механизм работы. Политики безопасности, сервисная балансировка, наблюдаемость и значительная часть L7-логики реализованы как eBPF-программы, привязанные к ключевым хукам сетевого стека (TC, XDP, socket hooks). В результате dataplane Cilium — это цельная eBPF-модель, где iptables/nftables с netfilter просто не предполагается в архитектуре.
Calico пришёл к eBPF эволюционно. Исторически он строился вокруг iptables и стандартного Linux routing, и это до сих пор валидный и поддерживаемый вариант (на момент версии 3.31). eBPF-dataplane в Calico — это не переписывание всего стека, а замена наиболее дорогих мест: политик, service handling, части маршрутизации. Остальная логика продолжает опираться на привычные примитивы ядра. Из-за этого Calico в eBPF-режиме часто выглядит как ускоренный Linux networking, а не как полностью отдельный dataplane.
Отсюда важный вывод: оба CNI поддерживают eBPF, но используют его по-разному.
В Cilium eBPF определяет архитектуру системы. В Calico eBPF — это альтернативная реализация dataplane, которую можно включить или выключить в зависимости от требований к производительности, отладке и зрелости команды.
Практический момент, который часто недооценивают: при использовании eBPF-режима и в Cilium, и в Calico необходимость в kube-proxy фактически отпадает. Классическая логика kube-proxy — сервисы, балансировка, DNAT/SNAT — реализуется непосредственно в dataplane ядра. Service IP обрабатывается eBPF-программами на пути прохождения пакета, без iptables/nftables или IPVS (v1.35.0 deprecated).
Чем eBPF-подход отличается от классического сетевого стека с точки зрения ядра
С точки зрения ядра Linux классический сетевой стек — это длинный и хорошо знакомый пайплайн: пакет заходит через драйвер, проходит netfilter и стек протоколов, и лишь в заранее определённых точках его можно перехватить или изменить. Кому интересно — могут прочитать мою статью по сетям в линукс. eBPF ломает эту линейность, позволяя встраивать собственную логику прямо в критические участки обработки пакетов — на уровне XDP, TC или сокетов — минуя часть традиционного пути.
Для ядра это не плагин к сетевому стеку, а запуск байткода в строго контролируемом окружении: без непредсказуемых циклов и без произвольного доступа к памяти, но с возможностью работать максимально близко к железу. Обратная сторона такого подхода — отладка. Привычные tcpdump и трассировки nftables перестают давать полную картину, а диагностика смещается в сторону perf, bpftool и анализа tracepoint’ов.
Входной порог здесь заметно выше, чем у привычных решений, и eBPF быстро отрезвляет тех, кто ожидал просто ещё один способ фильтровать пакеты. Это полноценная работа на уровне ядра — со всеми плюсами и со всей ответственностью.
Практические выводы для SRE и Dev: чему действительно стоит научиться, чтобы понимать eBPF-решения
Если свести всё вышесказанное к прикладному уровню, то главный вывод довольно прозаичен: для работы с eBPF недостаточно знать, что он существует. Чтобы перестать воспринимать eBPF-решения как чёрный ящик, стоит в первую очередь разобраться в модели выполнения внутри ядра — где именно исполняется программа, на каком этапе жизненного цикла пакета или события, и что именно она может и не может делать. Без этого любые разговоры про производительность, безопасность или почему здесь быстрее будут напоминать гадание на таро.
Второй важный навык — чтение и понимание eBPF-кода, даже если вы не планируете писать его с нуля. Умение открыть XDP-программу, понять логику работы с map’ами, увидеть, где принимается ключевое решение (drop, redirect, pass), резко снижает уровень напряжения при отладке. Для SRE это особенно критично: в какой-то момент проблема окажется ниже iptables/nftables, и вариантов кроме bpftool и tracepoint’ов просто не останется.
Третье — инструментарий и мышление для отладки. eBPF требует смириться с тем, что привычные средства наблюдаемости работают не всегда или работают иначе. Понимание perf, kprobe/tracepoint-ов, BPF-maps, как канала передачи состояния — это не опциональный бонус, а базовый минимум, если вы хотите уверенно эксплуатировать решения на eBPF.
И, наконец, стоит чётко осознать границы технологии. eBPF — не универсальная замена сетевого стека, не волшебная таблетка от latency и не повод писать весь dataplane с нуля. Он силён там, где нужно быстрое, предсказуемое и безопасное выполнение логики рядом с ядром, и довольно беспощаден к тем, кто приходит без понимания ограничений. В этом смысле лучший навык для инженера — не уметь писать eBPF, а понимать, когда его действительно стоит использовать, а когда классические механизмы будут проще, надёжнее и дешевле в поддержке. Кого заинтересовало и хочет разрабатывать собственные решения — добро пожаловать на офф сайт eBPF.
