eBPF позволяет видеть и менять поведение сети прямо в ядре Linux — без сайдкаров в каждом поде и без пересборки ядра. Для Kubernetes это означает другую модель: вместо тысяч правил iptables и прокси-контейнеров рядом с приложением вся логика сети и наблюдаемости живёт в одном слое на ноде. Cilium — самая зрелая реализация этого подхода. Разберёмся, что такое eBPF на пальцах, зачем менять CNI и что нужно, чтобы пощупать всё это в тестовом кластере.
Содержание
Открыть содержание
- Что такое eBPF на пальцах
- Проблема, которую решает eBPF
- Что меняет Cilium
- Hubble: наблюдаемость без proxy
- eBPF — это не только про сеть
- Сравнение в одной таблице
- Что нужно, чтобы поднять Cilium
- Как проверить, что всё работает
- Ловушки, о которых стоит знать заранее
- Когда переходить, а когда подождать
- Итог
Что такое eBPF на пальцах
Раньше, чтобы вмешаться в работу сети или системных вызовов на уровне ядра, нужно было писать модуль ядра — опасно (ошибка роняет всю ноду) и неудобно (пересборка, перезагрузка). eBPF меняет правила игры: вы загружаете маленькую программу в ядро, и она выполняется в ответ на события — пришёл пакет, открылся сокет, сработал системный вызов.
Ключевое — верификатор. Перед загрузкой ядро проверяет программу: что она завершится (нет бесконечных циклов), не полезет в чужую память, не уронит систему. Только пройдя проверку, программа подключается к точке-хуку. Получается безопасное «расширение ядра на лету»: производительность нативного кода без рисков модуля и без перекомпиляции.
Для сети это значит, что маршрутизацию, балансировку, фильтрацию и сбор метрик можно делать в самой горячей точке — там, где пакет входит в ноду, — а не гонять его через длинные цепочки правил в user space и обратно.
Второй важный кирпичик — eBPF-карты (maps). Сами программы в ядре эфемерны и не имеют «памяти» между вызовами, поэтому состояние хранится в картах: это таблицы вида ключ-значение, к которым обращаются и программа в ядре, и управляющий процесс в user space. Именно так Cilium держит таблицу «сервис → бэкенды» вместо цепочек iptables: при изменении эндпоинтов агент просто обновляет запись в карте, а eBPF-программа на горячем пути делает по ней мгновенный поиск. Через карты же наружу отдаются метрики и события — то, что потом читает Hubble. Понимание этой связки (программа на хуке + карта с состоянием) снимает большую часть «магии»: всё остальное в Cilium — это набор таких программ и карт под конкретные задачи.
Проблема, которую решает eBPF
В классическом Kubernetes за сеть отвечают две вещи, и обе плохо масштабируются.
Первая — kube-proxy на iptables. Каждый Service превращается в набор правил iptables, и ядро обходит их линейно. Пока сервисов десятки — незаметно. Когда их тысячи, цепочка правил становится длинной, а обновление при каждом изменении эндпоинтов — дорогим. Задержка и нагрузка на control plane растут вместе с размером кластера.
Вторая — наблюдаемость через сайдкары. Классический service mesh ставит прокси-контейнер рядом с каждым приложением: он перехватывает трафик и собирает метрики. Это работает, но цена велика — лишний контейнер на каждый под, дополнительные CPU и память, рост задержки на каждый хоп и общая сложность. Для сотен подов «налог на сайдкар» становится заметной строкой в счёте.
eBPF убирает обе проблемы разом: вместо линейных правил — хэш-таблица в ядре с поиском за константу, вместо прокси в каждом поде — один слой dataplane на ноду.
Что меняет Cilium
Cilium — это CNI (сетевой плагин Kubernetes), построенный на eBPF. Поставив его вместо flannel или calico, вы получаете несколько вещей:
- Замена kube-proxy. Cilium может полностью убрать kube-proxy: балансировка сервисов делается через eBPF-карты. Меньше задержка, лучше масштабирование на больших кластерах.
- Identity-based политики. Обычные NetworkPolicy оперируют IP. В динамическом кластере IP подов постоянно меняются, и правила по ним хрупкие. Cilium назначает каждому workload-у стабильную identity на основе лейблов и фильтрует по ней — политика «frontend может ходить к backend» переживает любые переезды подов.
- L7-политики. Можно фильтровать не только по портам, но и по HTTP-методам и путям, gRPC-методам, Kafka-топикам — без полноценного сайдкар-меша.
- Hubble. Встроенный слой наблюдаемости: кто с кем общается, что заблокировано и почему — без единого прокси в подах.
Hubble: наблюдаемость без proxy
Hubble — это «глаза» Cilium. Поскольку весь трафик и так проходит через eBPF-слой, Hubble просто читает эти события и отдаёт их в виде потоков (flows). Вы видите граф сервисов, конкретные соединения и — самое полезное при отладке — причины дропов.
Типичный сценарий: сервис «не ходит» к другому, а почему — непонятно. Вместо tcpdump по подам и чтения iptables вы выполняете одну команду и сразу видите: пакеты роняются с вердиктом DROPPED и причиной Policy denied. То есть это не сеть сломалась, а ваша же NetworkPolicy режет трафик. Такая отладка занимает секунды вместо часов.
Важно, что это наблюдаемость без инструментирования приложения. Вам не нужно встраивать SDK, добавлять прокси или менять код сервиса — eBPF видит трафик на уровне ядра по факту его прохождения. Поэтому Hubble одинаково хорошо показывает и ваш собственный сервис на Go, и закрытый бинарник, и легаси-приложение, которое никто не трогал годами. Метрики потоков (количество запросов, доля дропов, латентность по соединениям) Hubble отдаёт в Prometheus, так что поверх них можно строить обычные дашборды и алерты — без сайдкара под каждым подом.
eBPF — это не только про сеть
Хотя в контексте Cilium eBPF чаще обсуждают как сетевой dataplane, сам подход шире. Те же программы в ядре умеют наблюдать за системными вызовами, запуском процессов, открытием файлов и сетевыми соединениями на уровне отдельного процесса. На этом построен Tetragon — компонент экосистемы Cilium для security observability и runtime-энфорсмента.
На практике это даёт «чёрный ящик» рантайма: вы видите, что внутри контейнера запустился неожиданный процесс, что приложение читает файл, к которому не должно прикасаться, или устанавливает соединение на подозрительный адрес — и всё это без агента внутри контейнера. Можно не только наблюдать, но и блокировать: правило уровня ядра убивает процесс, нарушивший политику, ещё до того, как он успеет навредить. Для команды это означает, что одна и та же технология закрывает и сетевую наблюдаемость, и базовый runtime security — без зоопарка из отдельных агентов.
Сравнение в одной таблице
| kube-proxy + сайдкары | Cilium (eBPF) | |
|---|---|---|
| Маршрутизация Service | iptables, O(N) | eBPF-карта, ~O(1) |
| Наблюдаемость | прокси в каждом поде | Hubble, слой на ноде |
| Основа политик | IP-адреса | identity по лейблам |
| Уровень фильтрации | L3/L4 | L3/L4 и L7 |
| Налог на под | +контейнер, CPU/RAM | нет сайдкара |
| Отладка дропов | tcpdump + iptables | hubble observe |
Что нужно, чтобы поднять Cilium
Проще всего пощупать на локальном kind-кластере. Понадобятся kind, helm и cilium CLI. Создаём кластер без стандартного CNI, чтобы поставить свой:
kind create cluster --config - <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
disableDefaultCNI: true # отключаем kindnet, ставим Cilium
kubeProxyMode: none # kube-proxy заменит Cilium
nodes:
- role: control-plane
- role: worker
EOF
Ставим Cilium с включённым Hubble:
helm repo add cilium https://helm.cilium.io
helm install cilium cilium/cilium --namespace kube-system \
--set kubeProxyReplacement=true \
--set hubble.enabled=true \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true
cilium status --wait # дождаться готовности
Политика: deny egress, кроме одного хоста
Теперь закроем исходящий трафик у пода, оставив доступ только к нужному внешнему API. Cilium умеет L7-правила и фильтрацию по DNS-имени:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: egress-only-api
namespace: default
spec:
endpointSelector:
matchLabels:
app: worker
egress:
- toFQDNs:
- matchName: "api.example.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
- toEndpoints: # разрешим DNS, иначе резолв не пройдёт
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
Под с лейблом app: worker теперь сможет достучаться только до api.example.com:443 (и до DNS). Любой другой исходящий трафик будет отброшен — и это сразу станет видно в Hubble.
Как проверить, что всё работает
Сначала убедимся, что kube-proxy действительно заменён:
cilium status | grep -i kubeproxy
# KubeProxyReplacement: True
Теперь смотрим живые потоки и ищем дропы по нашей политике:
hubble observe --namespace default --verdict DROPPED
# ... worker -> 1.2.3.4:443 DROPPED (Policy denied)
Если попробовать из пода worker сходить на любой адрес, кроме api.example.com, в выводе появится строка с вердиктом DROPPED и причиной — это и есть «трассировка пакета без сайдкаров». А hubble observe --verdict FORWARDED покажет разрешённый трафик. Для наглядной картины есть Hubble UI с графом сервисов (cilium hubble ui).
Ловушки, о которых стоит знать заранее
Cilium мощный, но не «поставил и забыл». Несколько мест, где новички спотыкаются.
- Замена kube-proxy требует совместимого ядра. eBPF-функции зависят от версии ядра ноды. На старых дистрибутивах часть фич (полная замена kube-proxy, некоторые L7-возможности) может быть недоступна — проверяйте требования к ядру перед продом.
- Host-ネットворкинг и хост-процессы. Поды с
hostNetwork: trueживут в сетевом пространстве ноды и под политики Cilium попадают иначе. Это частый источник «политика есть, а трафик всё равно идёт». - DNS в egress-политиках. Если закрываете egress, не забудьте явно разрешить DNS к
kube-dns, иначе приложение не сможет даже резолвить имя и упрётся в непонятный таймаут вместо явногоPolicy denied. toFQDNs— это не магия. Фильтрация по доменному имени работает через перехват DNS-ответов. Если приложение ходит по «голому» IP в обход DNS, правило по имени его не поймает.- Отладка «упало в eBPF». Когда что-то не работает на уровне dataplane, начинайте с
hubble observeиcilium monitor— они показывают вердикты ядра. Лезть в сам bytecode почти никогда не нужно.
Когда переходить, а когда подождать
Переход на Cilium почти всегда окупается, если кластер вырос за пределы «нескольких сервисов» и сеть стала источником регулярной боли: задержки от раздувшихся iptables, хрупкие IP-based политики, многочасовая отладка «почему не ходит». Чем больше сервисов и чем динамичнее поды, тем сильнее выигрыш — и в производительности, и в отлаживаемости.
С другой стороны, на крошечном кластере из пары сервисов выгода будет в основном в наблюдаемости, а не в масштабе, и спешить нет смысла. Имеет смысл подождать или закладывать переход аккуратно, если у вас старое ядро на нодах, тяжёлая зависимость от hostNetwork, или managed-кластер, где CNI зашит провайдером. Хорошая новость в том, что у крупных облаков Cilium всё чаще идёт по умолчанию (GKE Dataplane V2, и опционально в EKS/AKS), так что во многих случаях вы уже на eBPF — просто не пользуетесь его возможностями вроде Hubble.
Итог
eBPF убирает два главных налога классической сети Kubernetes: линейные правила iptables и прокси-контейнер в каждом поде. Cilium упаковывает это в готовый CNI — с заменой kube-proxy, identity-политиками, L7-фильтрацией и наблюдаемостью через Hubble, где трассировка пакета и причина дропа доступны одной командой. Цена входа — смена CNI и внимание к версии ядра и host-сети. Если в кластере уже больше пяти-десяти сервисов и отладка сети регулярно съедает часы, переход почти всегда окупается: вы получаете «глаза» на сеть, которые в sidecar-мире стоили бы заметного оверхеда.