eBPF в AWS Lambda заменил старую схему сбора сетевых flow-логов, которая на плотных хостах упиралась в сотни тысяч правил iptables. Для разработчиков и SRE это не очередная история «мы переписали сервис на Rust», а редкий разбор того, как облачный провайдер пишет сетевую телеметрию для тысяч короткоживущих microVM на одном сервере и не ломает биллинг, аудит и безопасность.
По данным The New Stack, архитектуру описали инженеры AWS Prashant Kumar Singh, Kshitij Gupta и Shivendra Srivastava. Речь идет о внутренней системе AWS Lambda, которая фиксирует сетевые потоки для tenant workload: какая функция с каким endpoint общалась, когда, по какому протоколу и с каким объемом трафика. В serverless это особенно неприятная задача: workload может прожить несколько сотен миллисекунд, завершиться и оставить после себя только логи. Если запись неполная или неверно привязана к tenant, ошибка уезжает сразу в расследования инцидентов, мониторинг, compliance и учет потребления.
Старый подход достался Lambda от менее плотной EC2-эпохи. Он состоял из kernel-side расширения, которое считало пакеты и сопоставляло их с tenant и sandbox, и userspace-демона, который читал счетчики, собирал записи, сериализовал их и отправлял дальше. На небольшом числе VM схема работала. На новом Lambda-хосте с Firecracker microVM — уже нет. Один worker с парой тысяч microVM требовал больше 100 тысяч правил iptables. А iptables проходит цепочки правил почти линейно, поэтому каждый новый sandbox добавлял налог на обработку каждого пакета. Чем плотнее хост, тем дороже телеметрия. Великолепная формула, если ваша цель — объяснить финансовому директору, почему плотность вычислений внезапно стала врагом.
Второй стоп-фактор был проще и жестче: старый kernel module не поддерживал IPv6. Для Lambda, где dual-stack давно перестал быть экзотикой, flow-log, который не видит половину адресного пространства, нельзя считать надежной системой учета. Поэтому AWS переписала pipeline вокруг eBPF и Rust, сохранив совместимость с существующим downstream: новые записи остаются byte-for-byte совместимыми со старым Amazon Ion-форматом, чтобы billing, flow-log и metering-потребители не пришлось переделывать.
Новая система состоит из трех частей. Внизу — eBPF-программы, прикрепленные к traffic control hook на виртуальных сетевых устройствах каждой microVM. Они стоят на ingress и egress, читают заголовки Ethernet, IPv4 или IPv6, TCP, UDP или ICMP и пишут компактное событие в BPF ring buffer. Событие для IPv4 занимает около двух десятков байт. Важная деталь: eBPF-код только наблюдает. У него нет пути, который мог бы скопировать, заблокировать, сбросить или изменить customer packet.
Посередине находится tagger — непривилегированный userspace-процесс на Rust, отдельный для каждой сети microVM. Он читает свой ring buffer, агрегирует per-packet события в per-flow записи и пишет их на диск в прежнем Ion-формате. Сверху работает orchestrator: один привилегированный процесс на хост, который загружает eBPF, настраивает traffic control, запускает tagger-процессы и дает control plane небольшой lifecycle API через gRPC поверх Unix domain socket. Разделение получилось прагматичным: kernel быстро снимает минимум метаданных, Rust агрегирует поток, а права сосредоточены в одном небольшом сервисе.
Отдельно интересна механика изоляции. Kernel event не несет tenant identity, потому что поток уже разделен на уровне сети: у каждой microVM свои устройства и свой ring buffer. Tagger не вылавливает пакеты одного клиента из общей реки, он читает поток, который изначально принадлежит только этой microVM. Это снижает риск неверной атрибуции — самого дорогого класса ошибок для такой системы. Привилегии тоже обрезаны аккуратно: tagger не может загрузить eBPF, трогать traffic control или сам открыть BPF map. Orchestrator открывает ring buffer и передает готовый file descriptor через Unix domain socket с SCM_RIGHTS. Старый Unix-прием, зато работает без героизма.
Размер ring buffer AWS считала от нагрузки, а не «на глаз». В статье приведен пример: если потолок составляет около 62,5 тысячи пакетов в секунду, интервал чтения — 100 миллисекунд, размер события — примерно 24 байта, а направлений два, минимальный буфер выходит около 300 КБ. Так как ring buffer требует степень двойки, базовый размер — 512 КиБ. В текущем развертывании AWS держит запас «порядка пары мегабайт» на сеть, пока подбирает оптимальное значение для разных workload. Еще одна деталь: kernel будит userspace не постоянно, а когда буфер заполнен примерно на 1%, при этом tagger не возвращается читать чаще одного раза в 100 миллисекунд. Тихие функции почти бесплатны, шумные читаются быстро.
Rust здесь выбран не ради моды. По словам инженеров AWS, на одном хосте работают тысячи tagger-процессов, каждый держит небольшой state и должен не создавать пауз, которые превращаются в дырки в логах. Garbage-collected runtime добавил бы непредсказуемость по памяти и задержкам. Rust дает предсказуемое потребление, отсутствие GC и защиту от части ошибок, которые в такой системе легко становятся misattribution. Один tagger укладывается в несколько сотен килобайт RAM при бюджете около 1 МБ. Для активации steady-state recording заявлены задержки меньше 2 мс на p90 и меньше 10 мс на p99.9.
eBPF в AWS Lambda выглядит как частный инфраструктурный разбор, но урок шире. Облачные платформы все чаще упираются не в «можем ли мы запустить еще workload», а в цену наблюдаемости вокруг него: логирование, аудит, безопасность, биллинг и соответствие требованиям должны выдерживать ту же плотность, что и compute. AWS смогла убрать линейный налог iptables, добавить IPv6 и сохранить старые потребители данных без миграции. Следующий вопрос для рынка неприятнее: сколько внутренних платформ до сих пор платят за телеметрию по старой архитектуре и замечают это только тогда, когда плотность становится бизнес-метрикой, а не инженерной амбицией?