отлаживать задержки и сетевые проблемы на уровне ядра в продакшене

eBPF в продакшене: наблюдаемость и отладка на уровне ядра для DevOps-команд

14 минут

Метрики приложений не объясняют повторные передачи TCP, задержки планирования cgroup или горячие точки системных вызовов. eBPF запускает изолированные программы в ядре Linux и снимает эти сигналы с минимальными накладными расходами — без strace, sidecar и пересборки ядра.

Почему метрики приложений не видят сбои на уровне ядра

Prometheus и APM-агенты хорошо показывают задержку сервисов, долю ошибок и насыщение ресурсов. Они не видят повторные передачи TCP, throttling cgroup, давление на inode или конкуренцию за блокировки внутри ядра. strace и tcpdump отвечают на точечные вопросы, но добавляют риск и нагрузку на CPU на живых узлах. Кратковременные симптомы — всплески давления на память, штормы futex, схлопывание TCP-окна — часто исчезают до того, как инженер подключится по SSH. Команды понимают, что нужны данные ядра, только после часов прикладного «копания в логах» во время P1. eBPF закрывает этот разрыв: проверенные программы цепляются к точкам перехвата в ядре и экспортируют структурированные события в user space с накладными расходами в однозначных процентах при корректной области применения.

Промышленный стек: программы ядра, агрегация и визуализация

Типичная схема состоит из трёх уровней. Программы ядра цепляются к tracepoints, kprobes, uprobes или LSM-хукам и пишут в BPF-карты — hash map, ring buffer или per-CPU массивы. Уровень агрегации читает эти карты: Cilium Hubble для сетевых потоков и вердиктов политик в Kubernetes, Falco или Tetragon для событий runtime security, Pixie для автотелеметрии без правок кода, Parca или Pyroscope для непрерывных профилей CPU и памяти, bpftrace для разовых one-liner на узле. Визуализация потребляет агрегированные данные через Grafana, scrape Prometheus или OTLP-экспортеры. Cilium с Hubble подходит командам, которые заменяют или дополняют CNI для обзора потоков в кластере. Falco и Tetragon — для security, но их лучше вводить после read-only observability. bpftrace — инструмент break-glass с ограничением по времени, а не постоянный DaemonSet.

bpftrace: горячие точки syscalls у «шумного» пода

Когда CPU пода высокий, а метрики приложения выглядят ровно, узкое место часто в системных вызовах, а не в бизнес-логике. Установите bpftrace на узле или запустите официальный контейнер с host PID и смонтированным debugfs. Получите PID контейнера через crictl или kubectl debug и посчитайте входы в syscalls для процесса. Доминирующие futex или epoll_wait указывают на конкуренцию за блокировки или event loop, а не на вычисления. Ограничивайте ad-hoc запуски по времени, сохраняйте вывод в тикет и избегайте пиковых окон без записи об изменении.

Bash · установка bpftrace на узле
# Ubuntu / Debian
sudo apt-get install -y bpftrace

# Amazon Linux 2023
sudo dnf install -y bpftrace

# Эфемерный контейнер на узле (нужен privileged debug)
docker run --rm --privileged --pid=host \
  -v /sys/kernel/debug:/sys/kernel/debug:ro \
  quay.io/bpftrace/bpftrace:latest bpftrace --version
Bash · PID контейнера и подсчёт syscalls
PID=$(crictl inspect <container-id> | jq -r .info.pid)

bpftrace -p "$PID" -e 'tracepoint:raw_syscalls:sys_enter { @[probe] = count(); }'
Пример · подсчёт syscalls указывает на lock contention
@[tracepoint:raw_syscalls:sys_enter]: 45210
@[kprobe:sys_futex]: 380112
@[kprobe:sys_clock_nanosleep]: 1200
@[kprobe:sys_epoll_wait]: 890455
Bash · гистограмма задержки TCP-отправки на целевой IP
bpftrace -e '
kprobe:tcp_sendmsg
  /ntop(AF_INET, args->sk->__sk_common.skc_daddr) == "10.0.1.45"/
{
  @start[tid] = nsecs;
}
kretprobe:tcp_sendmsg
  /@start[tid]/
{
  @latency_us = hist((nsecs - @start[tid]) / 1000);
  delete(@start[tid]);
}'

Cilium Hubble: сетевые потоки и политики на уровне кластера

Для Kubernetes Cilium с Hubble показывает потоки L3/L4, DNS-метаданные и вердикты политик без инструментирования приложений. Включите Hubble relay, UI и метрики при установке через Helm, затем фильтруйте отброшенные потоки через Hubble CLI по namespace и протоколу — ошибочная NetworkPolicy сразу видна как вердикт DROPPED. Снимайте метрики Hubble с подов Cilium на порту 9965 в Prometheus для дашбордов и алертов. Считайте Cilium платформенным решением: проверьте версию ядра, наличие BTF и последствия замены kube-proxy в staging перед продакшеном.

Bash · установка Cilium с Hubble через Helm
helm repo add cilium https://helm.cilium.io
helm repo update

helm upgrade --install cilium cilium/cilium \
  --version 1.16.0 \
  --namespace kube-system \
  --set hubble.enabled=true \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true \
  --set hubble.metrics.enabled="{dns,http,tcp,flow,drop}" \
  --set prometheus.enabled=true
Bash · Hubble CLI и наблюдение отброшенных TCP-потоков
export HUBBLE_VERSION=$(curl -fsSL https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --remote-name-all \
  "https://github.com/cilium/hubble/releases/download/${HUBBLE_VERSION}/hubble-linux-amd64.tar.gz"
tar xzf hubble-linux-amd64.tar.gz
sudo mv hubble /usr/local/bin

hubble observe --namespace production --protocol tcp --verdict DROPPED
Пример · поток Hubble с отказом политики
Jul  3 10:23:45.123: 10.0.1.45:54321 (default/payment-svc) -> 10.0.2.12:5432 (default/payment-db) to-stack FORWARDED (TCP)
Jul  3 10:23:45.234: 10.0.3.88:42110 (prod/order-svc) -> 10.0.2.12:5432 (default/payment-db) Policy denied DROPPED (TCP)
YAML · scrape Prometheus для метрик Hubble
scrape_configs:
  - job_name: hubble
    kubernetes_sd_configs:
      - role: pod
        namespaces:
          names: [kube-system]
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_label_k8s_app]
        regex: cilium
        action: keep
      - source_labels: [__meta_kubernetes_pod_ip]
        target_label: __address__
        replacement: ${1}:9965

Операционные практики безопасного внедрения eBPF в продакшене

Начните с read-only observability — Hubble, break-glass bpftrace, профилирование Parca — и только потом включайте enforcement в Tetragon или строгие режимы Cilium NetworkPolicy. Разверните непрерывное профилирование через Parca или Grafana Pyroscope, чтобы видеть, какие пути ядра и user space съедают CPU; храните профили на persistent volume для сравнения регрессий. Ограничивайте каждую долгоживущую BPF-карту: неограниченные hash map — частая причина давления на память ядра. CO-RE и BTF требуют ядро 5.2+ с доступной отладочной информацией; интеграция cgroup v2 — 5.7+. Перед rollout выполните bpftool feature probe и зафиксируйте minor-версии ядра в образах узлов. Храните BPF-программы в git, документируйте, что измеряет каждый probe, и тестируйте в staging на том же билде ядра, что и в продакшене. eBPF дополняет Prometheus, трейсы OpenTelemetry и агрегаторы логов, а не заменяет их. Типичный drill-down: высокая задержка в Prometheus, доминирующие futex в bpftrace, retransmit или DROPPED в Hubble, исправление NetworkPolicy или маршрутизации. Следите за самим eBPF через JSON-вывод bpftool prog и map, контролируйте дропы ring buffer и согласуйте security-инструменты с общим hardening кластера.

Bash · развёртывание Parca для непрерывного профилирования
helm upgrade --install parca oci://ghcr.io/parca-dev/parca/charts/parca \
  --namespace observability --create-namespace \
  --set persistentVolume.enabled=true \
  --set persistentVolume.size=50Gi
Bash · ограниченная LRU-карта в bpftrace
bpftrace -e '
BEGIN { @latency = lruhash(10240); }
// populate @latency in probes; old entries evict automatically
'
Bash · проверка возможностей ядра и загруженных программ
bpftool feature probe kernel

bpftool prog show --json | jq -r '.[] | "\(.name): xlated \(.bytes_xlated // 0) bytes"'
bpftool map show --json | jq -r '.[] | "\(.name): max_entries=\(.max_entries // "n/a")"'

Наложите сигналы ядра на прикладной baseline из гайда по observability для небольших платформенных команд.

Когда распределённые трейсы обрываются на границе syscall, продолжите расследование с распределённым трассированием OpenTelemetry в Kubernetes.