Ir al contenido

eBPF para SREs: Guía de Perfilado en Producción

· 13 min read · default
ebpfperformanceprofilingsrelinuxobservabilitymonitoring-&-observability

Introducción

El perfilado en producción siempre ha sido el dominio de los valientes. Durante años, los SREs enfrentaron un compromiso incómodo: adjuntar herramientas de perfilado pesadas que introducían latencia medible y arriesgaban desestabilizar las cargas de trabajo en producción, o volar a ciegas y depender de dashboards de métricas que mostraban síntomas pero nunca las causas raíz. La aparición de eBPF como tecnología mainstream del kernel Linux ha cambiado fundamentalmente este cálculo. Con eBPF, puedes instrumentar casi cada capa del kernel y el espacio de usuario con una sobrecarga insignificante, generando el tipo de datos de observabilidad profunda que anteriormente solo estaban disponibles en entornos de staging.

eBPF, que significa extended Berkeley Packet Filter, evolucionó desde sus orígenes como mecanismo de filtrado de paquetes de red hasta convertirse en una máquina virtual de propósito general dentro del kernel. Los programas escritos para eBPF son verificados por el kernel antes de su ejecución, asegurando que no pueden hacer que el sistema se bloquee, entrar en bucles infinitos o acceder a memoria no autorizada. Esta garantía de seguridad es lo que hace que eBPF sea idóneo de forma única para el perfilado en producción. Puedes adjuntar sondas a funciones del kernel, tracepoints, entradas de funciones de espacio de usuario y contadores de rendimiento de hardware sin recompilar el kernel ni reiniciar servicios.

Esta guía cubre los flujos de trabajo prácticos que los equipos SRE usan diariamente para diagnosticar problemas de rendimiento en sistemas Linux en producción. Recorreremos el perfilado de CPU, análisis de latencia, detección de fugas de memoria, rastreo de red y perfilado de E/S usando la cadena de herramientas estándar de eBPF. Cada comando y script mostrado aquí ha sido usado en entornos de producción ejecutando kernel 5.15 y posterior.

Por Qué eBPF Cambia el Perfilado en Producción

Las herramientas de perfilado tradicionales imponen costos que las hacen impracticables en producción. Ejecutar strace en un servicio ocupado puede ralentizarlo en un factor de 100x o más porque strace usa ptrace para interceptar cada syscall, alternando el contexto entre el proceso rastreado y el proceso rastreador para cada invocación. Incluso perf, que usa contadores de rendimiento de hardware y es mucho más ligero que strace, aún requiere escribir datos de muestreo en disco y puede generar presión de E/S sustancial bajo altas tasas de muestreo.

eBPF cambia la ecuación de tres maneras importantes. Primero, los programas eBPF se ejecutan dentro del kernel, eliminando la sobrecarga de cambio de contexto de las herramientas basadas en ptrace. Cuando adjuntas un kprobe o tracepoint, el programa eBPF se ejecuta en línea con la ruta de código del kernel, típicamente agregando solo decenas de nanosegundos por invocación. Segundo, los programas eBPF pueden agregar datos dentro del kernel usando maps, lo que significa que puedes calcular histogramas, conteos y resúmenes sin copiar cada evento al espacio de usuario. Un histograma de latencia que generaría millones de eventos por segundo con strace produce una sola lectura de map por intervalo con eBPF. Tercero, el verificador de eBPF garantiza la seguridad: tu programa no puede desreferenciar punteros nulos, acceder a memoria fuera de límites ni ejecutar bucles infinitamente.

El impacto práctico es dramático. Herramientas como biolatency pueden rastrear cada solicitud de E/S de bloque en un sistema manejando cientos de miles de IOPS, produciendo un histograma de latencia con menos del 1% de sobrecarga de CPU. Puedes ejecutar funclatency contra una función caliente en tu servidor de aplicaciones mientras sirves tráfico pico. Esto simplemente no era posible con generaciones anteriores de herramientas de rastreo.

Configuración de la Cadena de Herramientas eBPF

El ecosistema eBPF se ha consolidado alrededor de tres conjuntos de herramientas principales: bcc-tools, bpftrace y programas CO-RE basados en libbpf. Cada uno sirve un caso de uso diferente, y una estación de trabajo SRE bien equipada debería tener los tres disponibles.

En sistemas Ubuntu y Debian, instala la cadena de herramientas completa:

sudo apt-get update
sudo apt-get install -y bpfcc-tools bpftrace linux-headers-$(uname -r)
sudo apt-get install -y libbpf-dev bpftool

En sistemas RHEL y Fedora:

sudo dnf install -y bcc-tools bpftrace kernel-devel-$(uname -r)
sudo dnf install -y libbpf-devel bpftool

Verifica que tu kernel soporta BTF, que es requerido para las funciones modernas de bpftrace y programas CO-RE:

ls /sys/kernel/btf/vmlinux
bpftool btf dump file /sys/kernel/btf/vmlinux format raw | head -c 100

Si BTF no está disponible, necesitarás asegurarte de que tu kernel fue compilado con CONFIG_DEBUG_INFO_BTF=y. La mayoría de los kernels de distribución desde 2022 en adelante incluyen soporte BTF.

El paquete bcc-tools proporciona docenas de herramientas listas para usar que cubren los escenarios de perfilado más comunes. Estas típicamente se instalan como ejecutables con un sufijo -bpfcc en sistemas basados en Debian o directamente bajo /usr/share/bcc/tools/ en RHEL. Bpftrace proporciona un lenguaje de scripting de alto nivel para escribir one-liners y scripts cortos personalizados. Libbpf y los programas CO-RE (Compile Once, Run Everywhere) se usan para construir herramientas eBPF portables de grado de producción que se distribuyen como binarios autocontenidos.

Flujos de Trabajo de Perfilado de CPU

El perfilado de CPU es el punto de partida más común al investigar problemas de rendimiento. El objetivo es identificar qué funciones consumen más tiempo de CPU, ya sea en espacio de kernel o de usuario, y generar gráficos de llamas que hagan los puntos calientes visualmente obvios.

El enfoque más simple usa la herramienta profile de bcc para muestrear trazas de pila a una frecuencia fija:

sudo profile-bpfcc -F 99 -a --stack-storage-size 16384 30 > /tmp/cpu-stacks.txt

Esto muestrea todas las CPUs a 99 Hz durante 30 segundos. La frecuencia de 99 Hz evita el aliasing con actividad basada en temporizadores que frecuentemente se ejecuta a 100 Hz. La salida contiene trazas de pila plegadas que pueden alimentarse directamente en las herramientas FlameGraph de Brendan Gregg:

git clone https://github.com/brendangregg/FlameGraph.git
cat /tmp/cpu-stacks.txt | FlameGraph/stackcollapse-bpf.pl | FlameGraph/flamegraph.pl > cpu-flame.svg

Para perfilado más dirigido, bpftrace te permite perfilar un proceso específico y filtrar por CPU:

sudo bpftrace -e 'profile:hz:99 /pid == 12345/ { @[ustack(perf), comm] = count(); }' > stacks.bt

Cuando necesitas entender el comportamiento de planificación de CPU en lugar del tiempo de ejecución, la herramienta cpudist muestra cuánto tiempo los hilos se ejecutan en CPU antes de ser desplanificados:

sudo cpudist-bpfcc -p 12345 10 1

Esto imprime un histograma de duraciones en CPU para el proceso 12345 durante un intervalo de 10 segundos. Tiempos cortos en CPU combinados con altos cambios de contexto sugieren contención de locks. Tiempos largos en CPU con bajo throughput sugieren cuellos de botella computacionales.

Para investigar problemas de migración de CPU en sistemas NUMA, puedes rastrear eventos del planificador:

sudo bpftrace -e 'tracepoint:sched:sched_migrate_task {
    printf("pid=%d comm=%s from_cpu=%d to_cpu=%d\n",
        args->pid, args->comm, args->orig_cpu, args->dest_cpu);
}'

Análisis de Latencia

El análisis de latencia es donde eBPF realmente brilla porque puede medir el tiempo entre eventos arbitrarios sin perturbar la ruta de código medida. La colección bcc-tools incluye varias herramientas de latencia construidas específicamente.

La latencia de E/S de bloque se mide con biolatency, que rastrea el tiempo desde la solicitud de E/S de bloque hasta su completación:

sudo biolatency-bpfcc -D 10 1

La bandera -D desglosa la latencia por dispositivo de disco, facilitando la identificación de qué unidades son lentas. La salida es un histograma de potencia de dos que muestra la distribución de latencias en microsegundos.

La latencia de cola de ejecución, que mide cuánto tiempo los hilos esperan en la cola del planificador antes de obtener tiempo de CPU, se mide con runqlat:

sudo runqlat-bpfcc -p 12345 10 1

Alta latencia de cola de ejecución significa que tus procesos están esperando CPU, lo que indica saturación de CPU. Si ves latencias superiores a 10 milisegundos durante operación normal, necesitas más capacidad de CPU o necesitas investigar qué está consumiendo CPU.

La latencia de función mide el tiempo de ejecución de una función específica del kernel o espacio de usuario:

sudo funclatency-bpfcc -p 12345 'c:malloc' 10 1

Esto rastrea las llamadas a malloc en la libc del proceso 12345 y muestra un histograma de latencia. Puedes rastrear cualquier función que tenga un símbolo en el binario o biblioteca compartida. Para funciones del kernel:

sudo funclatency-bpfcc 'vfs_read' 10 1

Para rastreo de latencia a nivel de aplicación con bpftrace, puedes medir el tiempo entre dos puntos de sondeo:

sudo bpftrace -e '
uprobe:/usr/bin/myapp:process_request { @start[tid] = nsecs; }
uretprobe:/usr/bin/myapp:process_request /@start[tid]/ {
    @latency_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}
END { clear(@start); }
'

Análisis de Memoria

Los problemas de memoria en producción van desde fugas graduales que causan OOM kills durante días hasta ineficiencias de caché que degradan el rendimiento. eBPF proporciona varias herramientas para cada categoría.

La herramienta memleak rastrea las llamadas de asignación y liberación de memoria, rastreando las asignaciones pendientes para identificar fugas:

sudo memleak-bpfcc -p 12345 --combined-only 30

Esto se adjunta al proceso 12345 y después de 30 segundos imprime las trazas de pila de asignaciones que no fueron liberadas, ordenadas por el total de bytes pendientes. Esto es invaluable para detectar fugas en servicios de larga ejecución sin reiniciarlos con depuración especializada de asignador habilitada.

Para rastreo de asignación de memoria del kernel:

sudo memleak-bpfcc --combined-only 30

Sin la bandera de pid, memleak rastrea asignaciones del kernel vía kmalloc y kfree, lo que puede identificar fugas de memoria del kernel causadas por drivers o módulos del kernel.

El comportamiento de caché tiene un impacto enorme en el rendimiento de la aplicación. La herramienta cachestat proporciona estadísticas por segundo sobre la caché de páginas:

sudo cachestat-bpfcc 5

La salida incluye hits, misses, páginas sucias y ratios de lectura/escritura. Un alto ratio de misses indica que tu conjunto de trabajo excede la memoria disponible, y deberías considerar si tu aplicación necesita más RAM o mejores patrones de acceso.

Los OOM kills pueden rastrearse en tiempo real para entender qué procesos están siendo eliminados y por qué:

sudo bpftrace -e 'kprobe:oom_kill_process {
    printf("OOM kill: pid=%d comm=%s pages=%d\n",
        ((struct task_struct *)arg1)->pid,
        ((struct task_struct *)arg1)->comm,
        arg0);
}'

Para un enfoque más simple, la herramienta oomkill de bcc captura esto automáticamente:

sudo oomkill-bpfcc

Esto imprime una línea cada vez que se invoca el OOM killer, incluyendo los detalles del proceso desencadenante y el proceso eliminado.

Rastreo de Red

Los problemas de rendimiento de red son notoriamente difíciles de diagnosticar porque involucran interacciones entre la aplicación, la pila TCP del kernel y la infraestructura de red. eBPF proporciona herramientas quirúrgicas para cada capa.

La herramienta tcplife rastrea los tiempos de vida de sesiones TCP, mostrando cuándo las conexiones se establecen y cierran junto con los bytes transferidos:

sudo tcplife-bpfcc -D

La bandera -D incluye marcas de tiempo. Cada línea muestra el PID, nombre del proceso, direcciones local y remota, puertos, duración y bytes enviados/recibidos. Esto es esencial para identificar churning de conexiones, conexiones inesperadamente de corta duración o servicios que mantienen conexiones abiertas más de lo esperado.

Las retransmisiones TCP son un indicador crítico de la salud de la red:

sudo tcpretrans-bpfcc -l

La bandera -l incluye tail loss probes. Cada evento de retransmisión se imprime con las direcciones de origen y destino, el estado y la función del kernel que desencadenó la retransmisión. Grupos de retransmisiones a un destino específico indican problemas en la ruta de red.

Los paquetes descartados a nivel TCP pueden rastrearse con tcpdrop:

sudo tcpdrop-bpfcc

Cada paquete descartado se imprime con su traza de pila del kernel, que te dice exactamente por qué el kernel descartó el paquete. Las causas comunes incluyen desbordamiento de buffer de socket, resets de conexión y presión de memoria.

Para análisis detallado del estado de conexión TCP, puedes escribir un script bpftrace que rastree las transiciones de estado:

sudo bpftrace -e '
tracepoint:tcp:tcp_set_state {
    printf("pid=%d sport=%d dport=%d oldstate=%d newstate=%d\n",
        pid, args->sport, args->dport, args->oldstate, args->newstate);
}
'

La latencia de resolución DNS es frecuentemente una fuente pasada por alto de latencia de aplicación. Puedes rastrear las búsquedas DNS a nivel de resolver:

sudo bpftrace -e '
uprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo { @start[tid] = nsecs; }
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo /@start[tid]/ {
    @dns_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}
'

Perfilado de E/S

La E/S de almacenamiento es una de las fuentes más comunes de latencia en producción, y eBPF proporciona herramientas que rastrean la E/S en múltiples niveles de la pila de almacenamiento.

La herramienta biosnoop rastrea operaciones individuales de E/S de bloque con marcas de tiempo, latencias y atribución de procesos:

sudo biosnoop-bpfcc -d sda 10

Esto rastrea toda la E/S de bloque al dispositivo sda durante 10 segundos, mostrando el PID de cada operación, nombre del proceso, disco, tipo de operación, sector, bytes y latencia. Esta es la herramienta preferida cuando necesitas entender exactamente qué está generando E/S en un disco específico.

El rastreo a nivel de sistema de archivos proporciona contexto de más alto nivel. La herramienta ext4slower rastrea operaciones ext4 que exceden un umbral de latencia:

sudo ext4slower-bpfcc 10

Esto imprime todas las operaciones ext4 más lentas de 10 milisegundos, incluyendo lecturas, escrituras, aperturas y syncs. Esto identifica inmediatamente operaciones lentas del sistema de archivos sin requerir que correlaciones trazas a nivel de bloque con metadatos del sistema de archivos.

Para rastreo general de sistema de archivos a través de todos los tipos de sistema de archivos, fileslower sirve el mismo propósito:

sudo fileslower-bpfcc 10

Los patrones de escritura son críticos para entender cómo las aplicaciones interactúan con el almacenamiento. El equivalente bpftrace de filetop muestra qué archivos están siendo leídos y escritos con más frecuencia:

sudo bpftrace -e '
tracepoint:syscalls:sys_enter_write {
    @writes[comm, pid] = count();
}
interval:s:5 { print(@writes); clear(@writes); }
'

Para cargas de trabajo pesadas en fsync como bases de datos, rastrear operaciones de sincronización puede revelar patrones de flush inesperados:

sudo bpftrace -e '
kprobe:vfs_fsync_range {
    @fsync_latency[comm] = count();
}
kretprobe:vfs_fsync_range {
    printf("fsync completed: comm=%s\n", comm);
}
'

Construcción de One-Liners Personalizados con bpftrace

El verdadero poder de eBPF viene de la capacidad de escribir programas de rastreo personalizados adaptados a tu stack específico. El lenguaje de scripting de bpftrace hace esto accesible para cualquiera que pueda escribir un script de shell.

La estructura básica de un one-liner de bpftrace es una especificación de sondeo seguida de un bloque de acción. Los sondeos pueden adjuntarse a kprobes (funciones del kernel), uprobes (funciones de espacio de usuario), tracepoints (eventos estables del kernel) y eventos de software.

Contar syscalls por proceso:

sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

Histograma de tamaños de lectura por proceso:

sudo bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
    @read_bytes[comm] = hist(args->ret);
}'

Rastrear la creación de nuevos procesos con líneas de comando completas:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
    printf("exec: pid=%d ppid=%d %s\n", pid, curtask->real_parent->pid, str(args->filename));
}'

Rastrear aperturas de archivos con rutas completas:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
    printf("open: pid=%d comm=%s file=%s\n", pid, comm, str(args->filename));
}'

Medir el tiempo que tu aplicación pasa en una función específica, agregado por traza de pila de llamadas:

sudo bpftrace -e '
uprobe:/opt/myapp/bin/server:DatabaseQuery { @start[tid] = nsecs; }
uretprobe:/opt/myapp/bin/server:DatabaseQuery /@start[tid]/ {
    @query_ns[ustack(5)] = hist(nsecs - @start[tid]);
    delete(@start[tid]);
}
'

Para eventos de alta frecuencia, usa maps para agregar dentro del kernel y evitar saturar el buffer de salida:

sudo bpftrace -e '
tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
    @total_bytes[comm] = sum(args->ret);
    @total_calls[comm] = count();
}
interval:s:10 {
    printf("\n--- Top readers (10s window) ---\n");
    print(@total_bytes, 10);
    print(@total_calls, 10);
    clear(@total_bytes);
    clear(@total_calls);
}
'

Integración de Perfilado Continuo

Mientras que el perfilado ad-hoc con bpftrace y bcc-tools es esencial para la respuesta a incidentes, el perfilado continuo proporciona visibilidad de rendimiento siempre activa. Dos proyectos de código abierto lideran este espacio: Parca y Pyroscope.

Parca usa eBPF para muestrear continuamente trazas de pila a través de todos los procesos en un host con sobrecarga mínima. El agente de Parca se ejecuta como un DaemonSet en Kubernetes o un servicio systemd en bare metal:

sudo parca-agent --remote-store-address=parca-server:7070 \
  --node=production-host-01 \
  --sampling-ratio=1.0 \
  --http-address=:7071

El agente descubre automáticamente todos los procesos en ejecución, resuelve símbolos desde información de depuración y datos BTF, y transmite datos de perfilado al servidor Parca. El servidor almacena perfiles en un formato columnar optimizado para consultas de series temporales y proporciona una UI para explorar gráficos de llamas, comparar perfiles entre ventanas de tiempo e identificar regresiones.

Pyroscope toma un enfoque similar pero soporta modos de perfilado adicionales más allá de CPU, incluyendo perfilado de asignación de memoria, perfilado de contención de locks y perfilado de goroutines para aplicaciones Go:

# pyroscope-agent.yaml
server-address: http://pyroscope-server:4040
log-level: info
targets:
  - service-name: api-server
    spy-name: ebpfspy
    application-name: api-server
  - service-name: worker
    spy-name: ebpfspy
    application-name: worker-pool

Ambas herramientas se integran con Grafana para la creación de dashboards y alertas. Una configuración típica de perfilado continuo incluye:

# Grafana data source configuration for Parca
curl -X POST http://grafana:3000/api/datasources \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Parca",
    "type": "parca",
    "url": "http://parca-server:7070",
    "access": "proxy"
  }'

El verdadero valor del perfilado continuo emerge con el tiempo. Cuando se despliega una regresión de rendimiento, puedes comparar el perfil actual contra una línea base de antes del despliegue e inmediatamente ver qué funciones están consumiendo más CPU. Esto transforma la depuración de rendimiento de una investigación reactiva en un sistema de detección proactiva.

Mejores Prácticas y Seguridad en Producción

Las garantías de seguridad de eBPF no significan que puedas ejecutar cualquier programa eBPF en cualquier sistema de producción sin pensarlo. Aunque el verificador previene crasheos del kernel, los programas eBPF mal escritos aún pueden consumir CPU excesiva, generar salida abrumadora o interferir con el rendimiento del sistema.

Siempre establece límites de tiempo en bpftrace y bcc-tools. Cada sesión de perfilado debería tener una duración explícita:

# Bueno: duración explícita de 30 segundos
sudo biolatency-bpfcc 30 1

# Peligroso: se ejecuta hasta ser detenido manualmente
sudo biolatency-bpfcc

Ten precaución con sondeos de alta frecuencia. Adjuntar un kprobe a una función que se dispara millones de veces por segundo agregará sobrecarga medible incluso si el programa eBPF en sí es trivial. Antes de adjuntar un sondeo en producción, estima la frecuencia:

sudo bpftrace -e 'kprobe:vfs_read { @count = count(); } interval:s:1 { print(@count); clear(@count); }'

Si la función se dispara más de unos cientos de miles de veces por segundo, considera si puedes usar un tracepoint en lugar de un kprobe, agregar un filtro para reducir el número de eventos procesados, o muestrear en lugar de rastrear cada evento.

Usa la bandera --dry-run de bpftrace para validar scripts antes de la ejecución:

sudo bpftrace --dry-run -e 'kprobe:vfs_read { @[comm] = count(); }'

Limita los tamaños de map para prevenir el crecimiento ilimitado de memoria:

sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' --map-max-entries=10000

Para sistemas de producción, establece un runbook de herramientas y scripts eBPF preaprobados. La colección bcc-tools está bien probada y es segura para uso en producción. Los scripts personalizados de bpftrace deben ser revisados por el equipo y probados en staging antes del uso en producción.

Considera ejecutar herramientas eBPF dentro de contenedores con las capacidades apropiadas en lugar de con acceso root completo:

docker run --rm -it --privileged \
  -v /sys/kernel/debug:/sys/kernel/debug:ro \
  -v /sys/kernel/btf:/sys/kernel/btf:ro \
  -v /proc:/proc:ro \
  quay.io/iovisor/bpftrace:latest \
  bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

Finalmente, documenta tus flujos de trabajo de perfilado. Cuando un incidente ocurre a las 3 AM, no quieres estar escribiendo scripts de bpftrace desde cero. Mantén un repositorio de scripts de perfilado probados organizados por síntoma: alta CPU, alta latencia, crecimiento de memoria, errores de red y saturación de E/S. Cada script debe incluir comentarios explicando qué mide, la sobrecarga esperada y cómo interpretar la salida. eBPF te da superpoderes en producción, pero solo si has practicado usarlos antes de que llegue la emergencia.