eBPF i NetFlow do monitorowania ruchu: od bare metal po Kubernetes
System oparty na eBPF/XDP oraz NetFlow zapewnia szczegółową telemetrię ruchu sieciowego na wszystkich poziomach – od fizycznych serwerów po klastry Kubernetes. Rozwiązuje ograniczenia tradycyjnych rozwiązań sFlow/NetFlow, takie jak wysokie obciążenie sprzętu, próbkowanie oraz brak możliwości dostosowywania pól. Ruch jest przetwarzany przez jednolity interfejs w trybie router-on-a-stick, co umożliwia XDP przechwytywanie pakietów przychodzących bez modyfikacji jądra.
eBPF uruchamia programy w jądrze systemu operacyjnego bezpiecznie i z minimalnym opóźnieniem, idealnie nadający się do monitoringu w czasie rzeczywistym. Statystyki są zbierane według 5-tuple (adresy IP, porty, protokół), bajtów i pakietów, a następnie eksportowane w formacie NetFlow do kolektorów.
Wybór typu map BPF dla wysokiej wydajności
Mapy BPF przechowują statystyki jako tabele klucz-wartość: klucz to 5-tuple, wartość to liczniki. Testy wykazały problemy z BPF_MAP_TYPE_HASH:
- Konflikty blokad przy jednoczesnym dostępie wielowątkowym z różnych rdzeni.
- Warunki wyścigu prowadzą do nieprecyzyjnych liczników.
Operacje atomowe (__sync_fetch_and_add) eliminują błędy, ale obniżają wydajność ze względu na synchronizację.
Przejście na BPF_MAP_TYPE_PERCPU_HASH tworzy osobną mapę na każdy rdzeń, eliminując blokady. Sumowanie odbywa się w przestrzeni użytkownika. Benchmark na 100 rdzeniach przy różnym obciążeniu potwierdził przewagę map PERCPU.
Przy przepełnieniu (600 tys. 5-tuple na mapę o pojemności 60 tys.) BPF_MAP_TYPE_LRU_PERCPU_HASH usuwa najstarsze wpisy, ale z kosztami dodatkowymi. Standardowe mapy PERCPU_HASH są lepsze dla szybkiego, jednorazowego odczytu.
// Określamy, do której mapy zapisywać
#define SHARD_COUNT 25 // liczba shardów
map_hash_idx = ctx->rx_queue_index % SHARD_COUNT + shard_offset;
Shardowanie map i eksport wielowątkowy
Jedna mapa o 10 milionach rekordów była ograniczona jednowątkowym eksportorem. Shardowanie na 25 map (według rx_queue_index poprzez RSS) umożliwiło równoległe odczytywanie, przyspieszając przetwarzanie trzykrotnie.
Zalety shardowania:
- Skalowalność: dodawanie map bez restartu.
- Paralelizm: czujnik może czytać wiele map jednocześnie.
- Efektywność cache: małe mapy mieszczą się w L1/L2 cache.
Czujnik napisany w Go (biblioteki Cilium) używa BPF_MAP_LOOKUP_AND_DELETE_BATCH do odczytu i czyszczenia map co minutę. Minimalizuje to straty danych przy przepełnieniu.
Próbka i dynamiczna adaptacja obciążenia
Aby osiągnąć równowagę między przesyłaniem a zbieraniem statystyk, zaimplementowano próbkowanie. Dodatkowa stats_map (BPF_MAP_TYPE_HASH, max_entries=128) liczy pakiety po shardach:
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 128); // Maksymalna liczba shardów
__type(key, __u32); // Numer shardu
__type(value, __u64); // Liczniki pakietów/bajtów
__uint(pinning, LIBBPF_PIN_BY_NAME);
} stats_map SEC(".maps");
Timer eBPF okresowo oblicza prędkość (pakiety/s) i aktywuje próbkowanie, gdy próg zostanie przekroczony w shardzie. Timery wykonują zadania w tle, poza kontekstem pakietów: agregację, reset liczników.
To zapewnia stabilność podczas szczytów (DDoS), zachowując telemetrię sesji.
Dostosowanie do wirtualizacji i Kubernetes
Schemat skaluje się z bare metal na wirtualizację i K8s. W klastrach eBPF/XDP wdraża się jako DaemonSet, przechwytując ruch pods poprzez CNI. Shardowanie dostosowuje się do node affinity, a PERCPU_HASH do vCPU.
Eksport NetFlow agreguje metadane (namespace, IP poda), zwiększając widoczność mikroserwisów.
Co ważne:
- PERCPU_HASH eliminuje konflikty blokad, zwiększając przepustowość o 2–3x w porównaniu do HASH.
- Shardowanie przyspiesza eksport trzykrotnie dzięki paralelizmowi.
- Timer eBPF + próbkowanie balansują obciążenie bez utraty przesyłania.
- Wersja LRU do dynamicznych scenariuszy z przepełnieniem.
- Czujnik w Go z bibliotekami Cilium jest optymalny dla intensywnego parsowania.
— Editorial Team
Brak komentarzy.