Перейти к содержимому
Hogin Hogin
Назад

eBPF без боли: Cilium и сетевая наблюдаемость в Kubernetes

9 мин чтения

eBPF позволяет видеть и менять поведение сети прямо в ядре Linux — без сайдкаров в каждом поде и без пересборки ядра. Для Kubernetes это означает другую модель: вместо тысяч правил iptables и прокси-контейнеров рядом с приложением вся логика сети и наблюдаемости живёт в одном слое на ноде. Cilium — самая зрелая реализация этого подхода. Разберёмся, что такое eBPF на пальцах, зачем менять CNI и что нужно, чтобы пощупать всё это в тестовом кластере.

Содержание

Открыть содержание

Что такое eBPF на пальцах

Раньше, чтобы вмешаться в работу сети или системных вызовов на уровне ядра, нужно было писать модуль ядра — опасно (ошибка роняет всю ноду) и неудобно (пересборка, перезагрузка). eBPF меняет правила игры: вы загружаете маленькую программу в ядро, и она выполняется в ответ на события — пришёл пакет, открылся сокет, сработал системный вызов.

Ключевое — верификатор. Перед загрузкой ядро проверяет программу: что она завершится (нет бесконечных циклов), не полезет в чужую память, не уронит систему. Только пройдя проверку, программа подключается к точке-хуку. Получается безопасное «расширение ядра на лету»: производительность нативного кода без рисков модуля и без перекомпиляции.

Для сети это значит, что маршрутизацию, балансировку, фильтрацию и сбор метрик можно делать в самой горячей точке — там, где пакет входит в ноду, — а не гонять его через длинные цепочки правил в user space и обратно.

Второй важный кирпичик — eBPF-карты (maps). Сами программы в ядре эфемерны и не имеют «памяти» между вызовами, поэтому состояние хранится в картах: это таблицы вида ключ-значение, к которым обращаются и программа в ядре, и управляющий процесс в user space. Именно так Cilium держит таблицу «сервис → бэкенды» вместо цепочек iptables: при изменении эндпоинтов агент просто обновляет запись в карте, а eBPF-программа на горячем пути делает по ней мгновенный поиск. Через карты же наружу отдаются метрики и события — то, что потом читает Hubble. Понимание этой связки (программа на хуке + карта с состоянием) снимает большую часть «магии»: всё остальное в Cilium — это набор таких программ и карт под конкретные задачи.

Проблема, которую решает eBPF

В классическом Kubernetes за сеть отвечают две вещи, и обе плохо масштабируются.

Первая — kube-proxy на iptables. Каждый Service превращается в набор правил iptables, и ядро обходит их линейно. Пока сервисов десятки — незаметно. Когда их тысячи, цепочка правил становится длинной, а обновление при каждом изменении эндпоинтов — дорогим. Задержка и нагрузка на control plane растут вместе с размером кластера.

Маршрутизация сервиса: kube-proxy против eBPF

Вторая — наблюдаемость через сайдкары. Классический service mesh ставит прокси-контейнер рядом с каждым приложением: он перехватывает трафик и собирает метрики. Это работает, но цена велика — лишний контейнер на каждый под, дополнительные CPU и память, рост задержки на каждый хоп и общая сложность. Для сотен подов «налог на сайдкар» становится заметной строкой в счёте.

Наблюдаемость: сайдкар в каждом поде против eBPF в ядре

eBPF убирает обе проблемы разом: вместо линейных правил — хэш-таблица в ядре с поиском за константу, вместо прокси в каждом поде — один слой dataplane на ноду.

Что меняет Cilium

Cilium — это CNI (сетевой плагин Kubernetes), построенный на eBPF. Поставив его вместо flannel или calico, вы получаете несколько вещей:

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)
Маршрутизация Serviceiptables, O(N)eBPF-карта, ~O(1)
Наблюдаемостьпрокси в каждом подеHubble, слой на ноде
Основа политикIP-адресаidentity по лейблам
Уровень фильтрацииL3/L4L3/L4 и L7
Налог на под+контейнер, CPU/RAMнет сайдкара
Отладка дроповtcpdump + iptableshubble 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 мощный, но не «поставил и забыл». Несколько мест, где новички спотыкаются.

Когда переходить, а когда подождать

Переход на 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-мире стоили бы заметного оверхеда.


Поделиться:

Предыдущая статья
OpenTelemetry Collector: минимальный setup, который не стыдно в прод
Следующая статья
SLSA Level 2: что такое build provenance и зачем он не SBOM