{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes","title":"eBPF en Producción: Observabilidad a Nivel de Kernel en Kubernetes Sin el Overhead","description":"Cómo usar eBPF para observabilidad de grado productivo en clusters de Kubernetes. Cilium, Tetragon, kprobes custom, y por qué el monitoreo tradicional está dejando performance sobre la mesa.","language":"es","datePublished":"2025-10-25","dateModified":null,"contentFormat":"text/markdown","content":"Infrastructure\n\n# eBPF en Producción: Observabilidad a Nivel de Kernel en Kubernetes Sin el Overhead\n\n2025-10-259 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/ebpf-observability-production-kubernetes>)\n\n## Qué es eBPF: Visión Técnica\n\nEliminar el hype y eBPF es bastante directo: es una máquina virtual dentro del kernel de Linux que te permite correr programas sandboxeados en puntos de enganche específicos sin modificar el código fuente del kernel ni cargar módulos de kernel. El \"extended\" en eBPF (originalmente Berkeley Packet Filter) se refiere a que fue expandido mucho más allá del filtrado de paquetes hacia un framework de instrumentación de kernel de propósito general.\n\nLas restricciones clave que lo hacen seguro:\n\n```\n1. Los programas deben pasar el verificador BPF (prueba terminación, seguridad de memoria)\n2. Sin loops no acotados (el verificador debe probar que todos los loops terminan)\n3. Tamaño de stack limitado (512 bytes)\n4. Sin sleep ni blocking en la mayoría de tipos de programa\n5. Las funciones helper son la única forma de interactuar con estado del kernel\n```\n\nEstas restricciones suenan limitantes, pero garantizan algo crucial: **un programa eBPF no puede crashear tu kernel**. Punto. El verificador rechaza cualquier cosa que pueda causar comportamiento indefinido. Esto es lo que hace viable a eBPF para producción, a diferencia de kprobes o scripts de SystemTap que pueden tirar abajo una máquina.\n\n## El Stack de Observabilidad en 2026\n\nA continuación se muestra cómo se ve un stack de observabilidad moderno basado en eBPF en Kubernetes de producción:\n\n```\n┌─────────────────────────────────────┐\n│ Grafana / UI │\n├─────────────────────────────────────┤\n│ Prometheus / OpenTelemetry │\n├──────────┬──────────┬───────────────┤\n│ Hubble │ Tetragon │ Probes Custom │\n├──────────┴──────────┴───────────────┤\n│ Cilium (CNI + eBPF) │\n├─────────────────────────────────────┤\n│ Linux Kernel (5.10+) │\n└─────────────────────────────────────┘\n```\n\n### Cilium: La Capa de Red\n\n[Cilium](<https://cilium.io/>) reemplaza kube-proxy e iptables con programas eBPF enganchados a hooks TC (traffic control). En vez de recorrer una cadena de reglas iptables por cada paquete, Cilium hace un solo lookup en un mapa BPF. La diferencia de performance es dramática:\n\n```\n# kube-proxy basado en iptables (5,000 servicios):\n# ~20ms por setup de conexión (recorrido de reglas)\n# ~15% overhead de CPU en nodo proxy\n\n# Cilium eBPF (5,000 servicios):\n# ~0.2ms por setup de conexión (lookup en hash map)\n# ~2% overhead de CPU en nodo proxy\n```\n\nPero la verdadera ganancia no es el throughput crudo, es la observabilidad. El componente Hubble de Cilium te da visibilidad L3/L4/L7 en cada paquete fluyendo por el cluster, con cero instrumentación en tu código de aplicación.\n\n```bash\n# Ver todas las requests HTTP entre servicios en tiempo real\nhubble observe --namespace production \\\n --protocol http \\\n --verdict FORWARDED \\\n -o json | jq '{src: .source.labels, dst: .destination.labels, http: .l7.http}'\n```\n\nEsto está observando el flujo de paquetes real a nivel de kernel, sin sampling. Cada paquete individual. Y porque es eBPF, el overhead es medible en microsegundos por paquete.\n\n### Tetragon: Observabilidad de Seguridad\n\n[Tetragon](<https://tetragon.io/>) es donde las cosas se ponen realmente interesantes desde una perspectiva de monitoreo de seguridad. Engancha programas eBPF a funciones del kernel (kprobes, tracepoints, hooks LSM) y te da una interfaz declarativa YAML para definir qué se desea observar.\n\nA continuación se presenta una TracingPolicy que monitorea accese es sospechose es a archivos:\n\n```yaml\napiVersion: cilium.io/v1alpha1\nkind: TracingPolicy\nmetadata:\n name: sensitive-file-access\nspec:\n kprobes:\n - call: \"security_file_open\"\n syscall: false\n args:\n - index: 0\n type: \"file\"\n selectors:\n - matchArgs:\n - index: 0\n operator: \"Prefix\"\n values:\n - \"/etc/shadow\"\n - \"/etc/passwd\"\n - \"/root/.ssh\"\n - \"/var/run/secrets/kubernetes.io\"\n matchActions:\n - action: Sigkill # Matar el proceso inmediatamente\n - action: Post # También mandar evento a userspace\n```\n\nEsta policy corre enteramente en kernel space. Cuando un proceso abre ` /etc/shadow `, el programa eBPF se dispara, checkea el path, y puede **matar el proceso antes de que la syscall ` open() ` retorne**. Sin roundtrip a userspace. Sin condición de carrera TOCTOU. El enforcement pasa a nivel de kernel.\n\nEn un escenario de deployment, un cluster corriendo un container de un vendor tercero sospechoso de exfiltrar secretos fue monitoreado con esta policy. En una hora, Tetragon lo enganchó intentando leer tokens de service account de Kubernetes del volumen de secrets montado. El proceso fue matado antes de que pudiera leer el primer byte.\n\n## Escribiendo Probes Custom\n\nA veces las herramientas out-of-the-box no alcanzan. Así se construyen probes eBPF custom para necesidades específicas de observabilidad.\n\n### Ejemplo: Histograma de Latencia de Conexión TCP\n\nEsta probe mide el tiempo entre que se llama a ` connect() ` y el handshake TCP se completa:\n\n```c\n// tcp_connect_latency.bpf.c\n#include \"vmlinux.h\"\n#include <bpf/bpf_helpers.h>\n#include <bpf/bpf_tracing.h>\n#include <bpf/bpf_core_read.h>\n\nstruct {\n __uint(type, BPF_MAP_TYPE_HASH);\n __uint(max_entries, 65536);\n __type(key, u64); // pid_tgid\n __type(value, u64); // timestamp de inicio\n} connect_start SEC(\".maps\");\n\nstruct {\n __uint(type, BPF_MAP_TYPE_RINGBUF);\n __uint(max_entries, 256 * 1024);\n} events SEC(\".maps\");\n\nstruct event {\n u32 pid;\n u32 dport;\n u64 latency_ns;\n u32 daddr;\n};\n\nSEC(\"kprobe/tcp_v4_connect\")\nint BPF_KPROBE(tcp_v4_connect_entry, struct sock *sk)\n{\n u64 pid_tgid = bpf_get_current_pid_tgid();\n u64 ts = bpf_ktime_get_ns();\n bpf_map_update_elem(&connect_start, &pid_tgid, &ts, BPF_ANY);\n return 0;\n}\n\nSEC(\"kprobe/tcp_finish_connect\")\nint BPF_KPROBE(tcp_finish_connect_entry, struct sock *sk)\n{\n u64 pid_tgid = bpf_get_current_pid_tgid();\n u64 *start_ts = bpf_map_lookup_elem(&connect_start, &pid_tgid);\n if (!start_ts)\n return 0;\n\n u64 latency = bpf_ktime_get_ns() - *start_ts;\n bpf_map_delete_elem(&connect_start, &pid_tgid);\n\n struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);\n if (!e)\n return 0;\n\n e->pid = pid_tgid >> 32;\n e->latency_ns = latency;\n e->dport = BPF_CORE_READ(sk, __sk_common.skc_dport);\n e->daddr = BPF_CORE_READ(sk, __sk_common.skc_daddr);\n\n bpf_ringbuf_submit(e, 0);\n return 0;\n}\n\nchar LICENSE[] SEC(\"license\") = \"GPL\";\n```\n\nEl componente Go de userspace que consume estos eventos:\n\n```go\npackage main\n\nimport (\n \"bytes\"\n \"encoding/binary\"\n \"fmt\"\n \"log\"\n \"net\"\n\n \"github.com/cilium/ebpf/link\"\n \"github.com/cilium/ebpf/ringbuf\"\n \"github.com/cilium/ebpf/rlimit\"\n)\n\n//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang tcpLatency tcp_connect_latency.bpf.c\n\ntype Event struct {\n Pid uint32\n Dport uint32\n LatencyNs uint64\n Daddr uint32\n}\n\nfunc main() {\n if err := rlimit.RemoveMemlock(); err != nil {\n log.Fatal(err)\n }\n\n objs := tcpLatencyObjects{}\n if err := loadTcpLatencyObjects(&objs, nil); err != nil {\n log.Fatalf(\"loading objects: %v\", err)\n }\n defer objs.Close()\n\n kp1, err := link.Kprobe(\"tcp_v4_connect\", objs.TcpV4ConnectEntry, nil)\n if err != nil {\n log.Fatalf(\"opening kprobe: %v\", err)\n }\n defer kp1.Close()\n\n kp2, err := link.Kprobe(\"tcp_finish_connect\", objs.TcpFinishConnectEntry, nil)\n if err != nil {\n log.Fatalf(\"opening kprobe: %v\", err)\n }\n defer kp2.Close()\n\n rd, err := ringbuf.NewReader(objs.Events)\n if err != nil {\n log.Fatalf(\"opening ringbuf reader: %v\", err)\n }\n defer rd.Close()\n\n fmt.Println(\"Tracing TCP connect latency... Ctrl+C to stop\")\n\n for {\n record, err := rd.Read()\n if err != nil {\n log.Printf(\"reading ringbuf: %v\", err)\n continue\n }\n\n var event Event\n if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {\n continue\n }\n\n addr := make(net.IP, 4)\n binary.LittleEndian.PutUint32(addr, event.Daddr)\n fmt.Printf(\"pid=%d dst=%s:%d latency=%.2fms\\n\",\n event.Pid, addr, ntohs(uint16(event.Dport)),\n float64(event.LatencyNs)/1e6)\n }\n}\n```\n\n### Deployando Probes Custom en Kubernetes\n\nLa parte complicada no es escribir la probe, es desplegarla de forma segura en toda la flota. Este es un patrón de deployment probado:\n\n```yaml\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n name: tcp-latency-probe\n namespace: observability\nspec:\n selector:\n matchLabels:\n app: tcp-latency-probe\n template:\n metadata:\n labels:\n app: tcp-latency-probe\n spec:\n hostPID: true\n hostNetwork: true\n containers:\n - name: probe\n image: ghcr.io/myorg/tcp-latency-probe:v1.2.0\n securityContext:\n privileged: false\n capabilities:\n add:\n - BPF\n - PERFMON\n - SYS_RESOURCE\n volumeMounts:\n - name: bpf-fs\n mountPath: /sys/fs/bpf\n - name: debugfs\n mountPath: /sys/kernel/debug\n readOnly: true\n volumes:\n - name: bpf-fs\n hostPath:\n path: /sys/fs/bpf\n - name: debugfs\n hostPath:\n path: /sys/kernel/debug\n```\n\nObservar que no estamos usando ` privileged: true `. Las capabilities ` BPF `, ` PERFMON ` y ` SYS_RESOURCE ` son suficientes. Correr probes eBPF como containers privilegiados es un anti-pattern común que le da a la probe acceso innecesario.\n\n## Características de Performance\n\nNúmeros reales de un cluster de producción (150 nodos, ~3000 pods):\n\n```\nBaseline (sin observabilidad eBPF):\n Utilización CPU de nodo: 62% promedio\n Latencia p99 de requests: 45ms\n\nCon Cilium + Hubble (visibilidad L7):\n Utilización CPU de nodo: 63.8% promedio (+1.8%)\n Latencia p99 de requests: 45.2ms (+0.4%)\n\nCon Tetragon (monitoreo de seguridad, 12 TracingPolicies):\n Utilización CPU de nodo: 64.1% promedio (+0.3% adicional)\n Latencia p99 de requests: 45.2ms (sin cambio)\n\nCon probe TCP latency custom (todas las conexiones):\n Utilización CPU de nodo: 64.3% promedio (+0.2% adicional)\n Latencia p99 de requests: 45.3ms (+0.1%)\n\nOverhead total: ~2.3% CPU, ~0.3ms latencia p99\n```\n\nCompará esto con un service mesh tradicional basado en sidecars (Istio con Envoy):\n\n```\nCon sidecar Istio (visibilidad L7):\n Utilización CPU de nodo: 71% promedio (+9%)\n Latencia p99 de requests: 52ms (+7ms)\n Overhead de memoria: ~50MB por pod (sidecar)\n```\n\nLa diferencia es abismal. eBPF opera a nivel de kernel, evitando los context switches y copias de memoria que los proxies sidecar requieren. Obteners mejor observabilidad con un orden de magnitud menos de overhead.\n\n## Errores Comunes\n\nDespués de desplegar eBPF en múltiples ambientes de producción, estos son los errores que se repiten:\n\n**1. Copiar demasiados datos a userspace.** Cada ` bpf_ringbuf_submit() ` o ` bpf_perf_event_output() ` involucra una copia de memoria. Si se está traceando cada syscall y copiando buffers de argumentos completos, se va a quemar CPU. Filtrá y agregar in-kernel siempre que sea posible.\n\n**2. No usar CO-RE.** Si se está compilando programas eBPF contra headers de kernel específicos y mandando binarios diferentes por versión de kernel, lo se está haciendo mal. BPF CO-RE (Compile Once, Run Everywhere) con BTF elimina esto completamente.\n\n**3. Ignorar el dimensionamiento de maps.** Los mapas BPF se alocan al momento de carga. Un ` BPF_MAP_TYPE_HASH ` con ` max_entries=1000000 ` aloca memoria de entrada en cada CPU. Dimensioná tus mapas basándote en cardinalidad real, no en el peor caso.\n\n**4. Correr como privileged.** Como se mencionó arriba, no se necesitan containers privilegiados. ` CAP_BPF ` + ` CAP_PERFMON ` es suficiente para probes de observabilidad. ` CAP_NET_ADMIN ` si se está haciendo cosas de red. Eso es todo.\n\n## Hacia Dónde Va Esto\n\nEl ecosistema eBPF está madurando rápido. Los grandes cambios a observar:\n\n- **eBPF en Windows.** Microsoft viene construyendo soporte de eBPF para Windows, y está llegando a production readiness. Las probes eBPF cross-platform se están volviendo viables.\n- **BPF Arena.** Nuevo tipo de mapa que le da a los programas eBPF un arena de memoria compartida, habilitando estructuras de datos más complejas (árboles, grafos) en kernel space.\n- **Programas de clase de scheduling.** Programas BPF que pueden influenciar el scheduler de CPU del kernel. sched\\_ext de Google ya está usando esto en producción.\n\nPara equipos de plataforma, el mensaje es claro: si todavía se está corriendo service meshes basados en sidecars principalmente para observabilidad, se está pagando un impuesto de overhead de 5-10x. eBPF te da visibilidad más profunda a una fracción del costo. El tooling maduró al punto donde no se necesita desarrolladores de kernel en el equipo para desplegarlo. Cilium y Tetragon cubren el 90% de los case es de uso out of the box.\n\nEl 10% restante, probes custom para tu dominio específico, es donde está la verdadera ventaja competitiva. Y escribir eBPF no es tan difícil una vez que se internalizan las restricciones. El verificador es tu amigo, no tu enemigo. Es la razón por la que es posible desplegar estas cosas en producción sin miedo.\n\n## Preguntas frecuentes\n\n### ¿eBPF agrega overhead significativo a workloads de producción?\n\nCuando se implementa correctamente, eBPF agrega overhead despreciable, típicamente menos del 1% de CPU. La clave es filtrar eventos in-kernel en vez de mandar todo a userspace. Una probe mal escrita que copia buffers grandes en cada syscall puede agregar 5-10% de overhead.\n\n### ¿Puedo usar eBPF sin escribir código C?\n\nSí. Herramientas como Cilium, Tetragon y bpftrace proveen abstracciones de alto nivel. Tetragon usa TracingPolicies en YAML, y bpftrace tiene un lenguaje de scripting estilo awk. Para necesidades custom, libbpf-rs (Rust) y cilium/ebpf (Go) proveen wrappers seguros.\n\n### ¿Qué versión de kernel necesito para observabilidad con eBPF?\n\nMínimo kernel 5.4 para soporte BTF (portabilidad CO-RE). Kernel 5.8+ para ring buffers. Kernel 5.11+ para bpf\\_snprintf y soporte de timers. La mayoría de las ofertas de Kubernetes managed (EKS, GKE, AKS) corren 5.10+ desde 2025.\n\n## Tips\n\n1. **BPF ring buffer vs perf buffer:** Utilizar siempre ` BPF_MAP_TYPE_RINGBUF ` en vez de ` BPF_MAP_TYPE_PERF_EVENT_ARRAY ` para programas nuevos. Los ring buffers comparten memoria entre CPUs, reducen el overhead de memoria ~50%, y soportan registros de largo variable. La única excepción es cuando se necesita ordenamiento estricto por CPU.\n2. **Tail call chaining para probes complejas:** Los programas eBPF individuales tienen un límite de 1M instrucciones (antes era 4096). Para lógica de observabilidad compleja, utilizar ` bpf_tail_call() ` para encadenar hasta 33 programas via ` BPF_MAP_TYPE_PROG_ARRAY `. Cada tail call resetea el contador de instrucciones, proporcionando tamaño de programa efectivamente ilimitado.\n3. **Portabilidad CO-RE con BTF:** Compilar las probes eBPF con ` -g -target bpf ` y utilizar wrappers ` __builtin_preserve_access_index() ` en accese es a structs. Esto embebe relocaciones BTF para que el mismo .o compilado corra en kernels 5.4+ sin recompilación. Verificar con ` bpftool btf dump file /sys/kernel/btf/vmlinux `.\n4. **Selectores de TracingPolicy en Tetragon:** No engancharse a cada syscall. Utilizar los selectores ` matchArgs ` y ` matchReturnArgs ` de Tetragon para filtrar in-kernel: ` matchArgs: [{index: 0, operator: Equal, values: [\"/etc/shadow\"]}] `. Esto descarta eventos irrelevantes antes de que lleguen a userspace, reduciendo overhead de CPU de ~5% a \\<0.1%.\n\n- ebpf\n- kubernetes\n- cilium\n- tetragon\n- observability\n- linux-kernel\n- security-monitoring\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"eBPF en Producción: Observabilidad a Nivel de Kernel en Kubernetes Sin el Overhead","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#title"},{"level":2,"text":"Qué es eBPF: Visión Técnica","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#qu-es-ebpf-visin-tcnica"},{"level":2,"text":"El Stack de Observabilidad en 2026","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#el-stack-de-observabilidad-en-2026"},{"level":3,"text":"Cilium: La Capa de Red","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#cilium-la-capa-de-red"},{"level":3,"text":"Tetragon: Observabilidad de Seguridad","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#tetragon-observabilidad-de-seguridad"},{"level":2,"text":"Escribiendo Probes Custom","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#escribiendo-probes-custom"},{"level":3,"text":"Ejemplo: Histograma de Latencia de Conexión TCP","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#ejemplo-histograma-de-latencia-de-conexin-tcp"},{"level":3,"text":"Deployando Probes Custom en Kubernetes","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#deployando-probes-custom-en-kubernetes"},{"level":2,"text":"Características de Performance","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#caractersticas-de-performance"},{"level":2,"text":"Errores Comunes","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#errores-comunes"},{"level":2,"text":"Hacia Dónde Va Esto","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#hacia-dnde-va-esto"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#questions"},{"level":3,"text":"¿eBPF agrega overhead significativo a workloads de producción?","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#question-1"},{"level":3,"text":"¿Puedo usar eBPF sin escribir código C?","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#question-2"},{"level":3,"text":"¿Qué versión de kernel necesito para observabilidad con eBPF?","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#article-tips"}],"sections":[{"heading":"eBPF en Producción: Observabilidad a Nivel de Kernel en Kubernetes Sin el Overhead","level":1,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes","content":"Infrastructure"},{"heading":"eBPF en Producción: Observabilidad a Nivel de Kernel en Kubernetes Sin el Overhead","level":1,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#title","content":"2025-10-259 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/ebpf-observability-production-kubernetes>)"},{"heading":"Qué es eBPF: Visión Técnica","level":2,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#qu-es-ebpf-visin-tcnica","content":"Eliminar el hype y eBPF es bastante directo: es una máquina virtual dentro del kernel de Linux que te permite correr programas sandboxeados en puntos de enganche específicos sin modificar el código fuente del kernel ni cargar módulos de kernel. El \"extended\" en eBPF (originalmente Berkeley Packet Filter) se refiere a que fue expandido mucho más allá del filtrado de paquetes hacia un framework de instrumentación de kernel de propósito general.\n\nLas restricciones clave que lo hacen seguro:\n\n```\n1. Los programas deben pasar el verificador BPF (prueba terminación, seguridad de memoria)\n2. Sin loops no acotados (el verificador debe probar que todos los loops terminan)\n3. Tamaño de stack limitado (512 bytes)\n4. Sin sleep ni blocking en la mayoría de tipos de programa\n5. Las funciones helper son la única forma de interactuar con estado del kernel\n```\n\nEstas restricciones suenan limitantes, pero garantizan algo crucial: **un programa eBPF no puede crashear tu kernel**. Punto. El verificador rechaza cualquier cosa que pueda causar comportamiento indefinido. Esto es lo que hace viable a eBPF para producción, a diferencia de kprobes o scripts de SystemTap que pueden tirar abajo una máquina."},{"heading":"El Stack de Observabilidad en 2026","level":2,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#el-stack-de-observabilidad-en-2026","content":"A continuación se muestra cómo se ve un stack de observabilidad moderno basado en eBPF en Kubernetes de producción:\n\n```\n┌─────────────────────────────────────┐\n│ Grafana / UI │\n├─────────────────────────────────────┤\n│ Prometheus / OpenTelemetry │\n├──────────┬──────────┬───────────────┤\n│ Hubble │ Tetragon │ Probes Custom │\n├──────────┴──────────┴───────────────┤\n│ Cilium (CNI + eBPF) │\n├─────────────────────────────────────┤\n│ Linux Kernel (5.10+) │\n└─────────────────────────────────────┘\n```"},{"heading":"Cilium: La Capa de Red","level":3,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#cilium-la-capa-de-red","content":"[Cilium](<https://cilium.io/>) reemplaza kube-proxy e iptables con programas eBPF enganchados a hooks TC (traffic control). En vez de recorrer una cadena de reglas iptables por cada paquete, Cilium hace un solo lookup en un mapa BPF. La diferencia de performance es dramática:\n\n```\n# kube-proxy basado en iptables (5,000 servicios):\n# ~20ms por setup de conexión (recorrido de reglas)\n# ~15% overhead de CPU en nodo proxy\n\n# Cilium eBPF (5,000 servicios):\n# ~0.2ms por setup de conexión (lookup en hash map)\n# ~2% overhead de CPU en nodo proxy\n```\n\nPero la verdadera ganancia no es el throughput crudo, es la observabilidad. El componente Hubble de Cilium te da visibilidad L3/L4/L7 en cada paquete fluyendo por el cluster, con cero instrumentación en tu código de aplicación.\n\n```bash\n# Ver todas las requests HTTP entre servicios en tiempo real\nhubble observe --namespace production \\\n --protocol http \\\n --verdict FORWARDED \\\n -o json | jq '{src: .source.labels, dst: .destination.labels, http: .l7.http}'\n```\n\nEsto está observando el flujo de paquetes real a nivel de kernel, sin sampling. Cada paquete individual. Y porque es eBPF, el overhead es medible en microsegundos por paquete."},{"heading":"Tetragon: Observabilidad de Seguridad","level":3,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#tetragon-observabilidad-de-seguridad","content":"[Tetragon](<https://tetragon.io/>) es donde las cosas se ponen realmente interesantes desde una perspectiva de monitoreo de seguridad. Engancha programas eBPF a funciones del kernel (kprobes, tracepoints, hooks LSM) y te da una interfaz declarativa YAML para definir qué se desea observar.\n\nA continuación se presenta una TracingPolicy que monitorea accese es sospechose es a archivos:\n\n```yaml\napiVersion: cilium.io/v1alpha1\nkind: TracingPolicy\nmetadata:\n name: sensitive-file-access\nspec:\n kprobes:\n - call: \"security_file_open\"\n syscall: false\n args:\n - index: 0\n type: \"file\"\n selectors:\n - matchArgs:\n - index: 0\n operator: \"Prefix\"\n values:\n - \"/etc/shadow\"\n - \"/etc/passwd\"\n - \"/root/.ssh\"\n - \"/var/run/secrets/kubernetes.io\"\n matchActions:\n - action: Sigkill # Matar el proceso inmediatamente\n - action: Post # También mandar evento a userspace\n```\n\nEsta policy corre enteramente en kernel space. Cuando un proceso abre ` /etc/shadow `, el programa eBPF se dispara, checkea el path, y puede **matar el proceso antes de que la syscall ` open() ` retorne**. Sin roundtrip a userspace. Sin condición de carrera TOCTOU. El enforcement pasa a nivel de kernel.\n\nEn un escenario de deployment, un cluster corriendo un container de un vendor tercero sospechoso de exfiltrar secretos fue monitoreado con esta policy. En una hora, Tetragon lo enganchó intentando leer tokens de service account de Kubernetes del volumen de secrets montado. El proceso fue matado antes de que pudiera leer el primer byte."},{"heading":"Escribiendo Probes Custom","level":2,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#escribiendo-probes-custom","content":"A veces las herramientas out-of-the-box no alcanzan. Así se construyen probes eBPF custom para necesidades específicas de observabilidad."},{"heading":"Ejemplo: Histograma de Latencia de Conexión TCP","level":3,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#ejemplo-histograma-de-latencia-de-conexin-tcp","content":"Esta probe mide el tiempo entre que se llama a ` connect() ` y el handshake TCP se completa:\n\n```c\n// tcp_connect_latency.bpf.c\n#include \"vmlinux.h\"\n#include <bpf/bpf_helpers.h>\n#include <bpf/bpf_tracing.h>\n#include <bpf/bpf_core_read.h>\n\nstruct {\n __uint(type, BPF_MAP_TYPE_HASH);\n __uint(max_entries, 65536);\n __type(key, u64); // pid_tgid\n __type(value, u64); // timestamp de inicio\n} connect_start SEC(\".maps\");\n\nstruct {\n __uint(type, BPF_MAP_TYPE_RINGBUF);\n __uint(max_entries, 256 * 1024);\n} events SEC(\".maps\");\n\nstruct event {\n u32 pid;\n u32 dport;\n u64 latency_ns;\n u32 daddr;\n};\n\nSEC(\"kprobe/tcp_v4_connect\")\nint BPF_KPROBE(tcp_v4_connect_entry, struct sock *sk)\n{\n u64 pid_tgid = bpf_get_current_pid_tgid();\n u64 ts = bpf_ktime_get_ns();\n bpf_map_update_elem(&connect_start, &pid_tgid, &ts, BPF_ANY);\n return 0;\n}\n\nSEC(\"kprobe/tcp_finish_connect\")\nint BPF_KPROBE(tcp_finish_connect_entry, struct sock *sk)\n{\n u64 pid_tgid = bpf_get_current_pid_tgid();\n u64 *start_ts = bpf_map_lookup_elem(&connect_start, &pid_tgid);\n if (!start_ts)\n return 0;\n\n u64 latency = bpf_ktime_get_ns() - *start_ts;\n bpf_map_delete_elem(&connect_start, &pid_tgid);\n\n struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);\n if (!e)\n return 0;\n\n e->pid = pid_tgid >> 32;\n e->latency_ns = latency;\n e->dport = BPF_CORE_READ(sk, __sk_common.skc_dport);\n e->daddr = BPF_CORE_READ(sk, __sk_common.skc_daddr);\n\n bpf_ringbuf_submit(e, 0);\n return 0;\n}\n\nchar LICENSE[] SEC(\"license\") = \"GPL\";\n```\n\nEl componente Go de userspace que consume estos eventos:\n\n```go\npackage main\n\nimport (\n \"bytes\"\n \"encoding/binary\"\n \"fmt\"\n \"log\"\n \"net\"\n\n \"github.com/cilium/ebpf/link\"\n \"github.com/cilium/ebpf/ringbuf\"\n \"github.com/cilium/ebpf/rlimit\"\n)\n\n//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang tcpLatency tcp_connect_latency.bpf.c\n\ntype Event struct {\n Pid uint32\n Dport uint32\n LatencyNs uint64\n Daddr uint32\n}\n\nfunc main() {\n if err := rlimit.RemoveMemlock(); err != nil {\n log.Fatal(err)\n }\n\n objs := tcpLatencyObjects{}\n if err := loadTcpLatencyObjects(&objs, nil); err != nil {\n log.Fatalf(\"loading objects: %v\", err)\n }\n defer objs.Close()\n\n kp1, err := link.Kprobe(\"tcp_v4_connect\", objs.TcpV4ConnectEntry, nil)\n if err != nil {\n log.Fatalf(\"opening kprobe: %v\", err)\n }\n defer kp1.Close()\n\n kp2, err := link.Kprobe(\"tcp_finish_connect\", objs.TcpFinishConnectEntry, nil)\n if err != nil {\n log.Fatalf(\"opening kprobe: %v\", err)\n }\n defer kp2.Close()\n\n rd, err := ringbuf.NewReader(objs.Events)\n if err != nil {\n log.Fatalf(\"opening ringbuf reader: %v\", err)\n }\n defer rd.Close()\n\n fmt.Println(\"Tracing TCP connect latency... Ctrl+C to stop\")\n\n for {\n record, err := rd.Read()\n if err != nil {\n log.Printf(\"reading ringbuf: %v\", err)\n continue\n }\n\n var event Event\n if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {\n continue\n }\n\n addr := make(net.IP, 4)\n binary.LittleEndian.PutUint32(addr, event.Daddr)\n fmt.Printf(\"pid=%d dst=%s:%d latency=%.2fms\\n\",\n event.Pid, addr, ntohs(uint16(event.Dport)),\n float64(event.LatencyNs)/1e6)\n }\n}\n```"},{"heading":"Deployando Probes Custom en Kubernetes","level":3,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#deployando-probes-custom-en-kubernetes","content":"La parte complicada no es escribir la probe, es desplegarla de forma segura en toda la flota. Este es un patrón de deployment probado:\n\n```yaml\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n name: tcp-latency-probe\n namespace: observability\nspec:\n selector:\n matchLabels:\n app: tcp-latency-probe\n template:\n metadata:\n labels:\n app: tcp-latency-probe\n spec:\n hostPID: true\n hostNetwork: true\n containers:\n - name: probe\n image: ghcr.io/myorg/tcp-latency-probe:v1.2.0\n securityContext:\n privileged: false\n capabilities:\n add:\n - BPF\n - PERFMON\n - SYS_RESOURCE\n volumeMounts:\n - name: bpf-fs\n mountPath: /sys/fs/bpf\n - name: debugfs\n mountPath: /sys/kernel/debug\n readOnly: true\n volumes:\n - name: bpf-fs\n hostPath:\n path: /sys/fs/bpf\n - name: debugfs\n hostPath:\n path: /sys/kernel/debug\n```\n\nObservar que no estamos usando ` privileged: true `. Las capabilities ` BPF `, ` PERFMON ` y ` SYS_RESOURCE ` son suficientes. Correr probes eBPF como containers privilegiados es un anti-pattern común que le da a la probe acceso innecesario."},{"heading":"Características de Performance","level":2,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#caractersticas-de-performance","content":"Números reales de un cluster de producción (150 nodos, ~3000 pods):\n\n```\nBaseline (sin observabilidad eBPF):\n Utilización CPU de nodo: 62% promedio\n Latencia p99 de requests: 45ms\n\nCon Cilium + Hubble (visibilidad L7):\n Utilización CPU de nodo: 63.8% promedio (+1.8%)\n Latencia p99 de requests: 45.2ms (+0.4%)\n\nCon Tetragon (monitoreo de seguridad, 12 TracingPolicies):\n Utilización CPU de nodo: 64.1% promedio (+0.3% adicional)\n Latencia p99 de requests: 45.2ms (sin cambio)\n\nCon probe TCP latency custom (todas las conexiones):\n Utilización CPU de nodo: 64.3% promedio (+0.2% adicional)\n Latencia p99 de requests: 45.3ms (+0.1%)\n\nOverhead total: ~2.3% CPU, ~0.3ms latencia p99\n```\n\nCompará esto con un service mesh tradicional basado en sidecars (Istio con Envoy):\n\n```\nCon sidecar Istio (visibilidad L7):\n Utilización CPU de nodo: 71% promedio (+9%)\n Latencia p99 de requests: 52ms (+7ms)\n Overhead de memoria: ~50MB por pod (sidecar)\n```\n\nLa diferencia es abismal. eBPF opera a nivel de kernel, evitando los context switches y copias de memoria que los proxies sidecar requieren. Obteners mejor observabilidad con un orden de magnitud menos de overhead."},{"heading":"Errores Comunes","level":2,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#errores-comunes","content":"Después de desplegar eBPF en múltiples ambientes de producción, estos son los errores que se repiten:\n\n**1. Copiar demasiados datos a userspace.** Cada ` bpf_ringbuf_submit() ` o ` bpf_perf_event_output() ` involucra una copia de memoria. Si se está traceando cada syscall y copiando buffers de argumentos completos, se va a quemar CPU. Filtrá y agregar in-kernel siempre que sea posible.\n\n**2. No usar CO-RE.** Si se está compilando programas eBPF contra headers de kernel específicos y mandando binarios diferentes por versión de kernel, lo se está haciendo mal. BPF CO-RE (Compile Once, Run Everywhere) con BTF elimina esto completamente.\n\n**3. Ignorar el dimensionamiento de maps.** Los mapas BPF se alocan al momento de carga. Un ` BPF_MAP_TYPE_HASH ` con ` max_entries=1000000 ` aloca memoria de entrada en cada CPU. Dimensioná tus mapas basándote en cardinalidad real, no en el peor caso.\n\n**4. Correr como privileged.** Como se mencionó arriba, no se necesitan containers privilegiados. ` CAP_BPF ` + ` CAP_PERFMON ` es suficiente para probes de observabilidad. ` CAP_NET_ADMIN ` si se está haciendo cosas de red. Eso es todo."},{"heading":"Hacia Dónde Va Esto","level":2,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#hacia-dnde-va-esto","content":"El ecosistema eBPF está madurando rápido. Los grandes cambios a observar:\n\n- **eBPF en Windows.** Microsoft viene construyendo soporte de eBPF para Windows, y está llegando a production readiness. Las probes eBPF cross-platform se están volviendo viables.\n- **BPF Arena.** Nuevo tipo de mapa que le da a los programas eBPF un arena de memoria compartida, habilitando estructuras de datos más complejas (árboles, grafos) en kernel space.\n- **Programas de clase de scheduling.** Programas BPF que pueden influenciar el scheduler de CPU del kernel. sched\\_ext de Google ya está usando esto en producción.\n\nPara equipos de plataforma, el mensaje es claro: si todavía se está corriendo service meshes basados en sidecars principalmente para observabilidad, se está pagando un impuesto de overhead de 5-10x. eBPF te da visibilidad más profunda a una fracción del costo. El tooling maduró al punto donde no se necesita desarrolladores de kernel en el equipo para desplegarlo. Cilium y Tetragon cubren el 90% de los case es de uso out of the box.\n\nEl 10% restante, probes custom para tu dominio específico, es donde está la verdadera ventaja competitiva. Y escribir eBPF no es tan difícil una vez que se internalizan las restricciones. El verificador es tu amigo, no tu enemigo. Es la razón por la que es posible desplegar estas cosas en producción sin miedo."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#questions","content":""},{"heading":"¿eBPF agrega overhead significativo a workloads de producción?","level":3,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#question-1","content":"Cuando se implementa correctamente, eBPF agrega overhead despreciable, típicamente menos del 1% de CPU. La clave es filtrar eventos in-kernel en vez de mandar todo a userspace. Una probe mal escrita que copia buffers grandes en cada syscall puede agregar 5-10% de overhead."},{"heading":"¿Puedo usar eBPF sin escribir código C?","level":3,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#question-2","content":"Sí. Herramientas como Cilium, Tetragon y bpftrace proveen abstracciones de alto nivel. Tetragon usa TracingPolicies en YAML, y bpftrace tiene un lenguaje de scripting estilo awk. Para necesidades custom, libbpf-rs (Rust) y cilium/ebpf (Go) proveen wrappers seguros."},{"heading":"¿Qué versión de kernel necesito para observabilidad con eBPF?","level":3,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#question-3","content":"Mínimo kernel 5.4 para soporte BTF (portabilidad CO-RE). Kernel 5.8+ para ring buffers. Kernel 5.11+ para bpf\\_snprintf y soporte de timers. La mayoría de las ofertas de Kubernetes managed (EKS, GKE, AKS) corren 5.10+ desde 2025."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#article-tips","content":"1. **BPF ring buffer vs perf buffer:** Utilizar siempre ` BPF_MAP_TYPE_RINGBUF ` en vez de ` BPF_MAP_TYPE_PERF_EVENT_ARRAY ` para programas nuevos. Los ring buffers comparten memoria entre CPUs, reducen el overhead de memoria ~50%, y soportan registros de largo variable. La única excepción es cuando se necesita ordenamiento estricto por CPU.\n2. **Tail call chaining para probes complejas:** Los programas eBPF individuales tienen un límite de 1M instrucciones (antes era 4096). Para lógica de observabilidad compleja, utilizar ` bpf_tail_call() ` para encadenar hasta 33 programas via ` BPF_MAP_TYPE_PROG_ARRAY `. Cada tail call resetea el contador de instrucciones, proporcionando tamaño de programa efectivamente ilimitado.\n3. **Portabilidad CO-RE con BTF:** Compilar las probes eBPF con ` -g -target bpf ` y utilizar wrappers ` __builtin_preserve_access_index() ` en accese es a structs. Esto embebe relocaciones BTF para que el mismo .o compilado corra en kernels 5.4+ sin recompilación. Verificar con ` bpftool btf dump file /sys/kernel/btf/vmlinux `.\n4. **Selectores de TracingPolicy en Tetragon:** No engancharse a cada syscall. Utilizar los selectores ` matchArgs ` y ` matchReturnArgs ` de Tetragon para filtrar in-kernel: ` matchArgs: [{index: 0, operator: Equal, values: [\"/etc/shadow\"]}] `. Esto descarta eventos irrelevantes antes de que lleguen a userspace, reduciendo overhead de CPU de ~5% a \\<0.1%.\n\n- ebpf\n- kubernetes\n- cilium\n- tetragon\n- observability\n- linux-kernel\n- security-monitoring\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/ebpf-observability-production-kubernetes"},{"text":"Cilium","url":"https://cilium.io/"},{"text":"Tetragon","url":"https://tetragon.io/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes"},"headline":"eBPF en Producción: Observabilidad a Nivel de Kernel en Kubernetes Sin el Overhead","description":"Cómo usar eBPF para observabilidad de grado productivo en clusters de Kubernetes. Cilium, Tetragon, kprobes custom, y por qué el monitoreo tradicional está dejando performance sobre la mesa.","datePublished":"2025-10-25","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://statics.lucioduran.com/images/lucioduran-social-card.jpg","url":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Infrastructure","keywords":"ebpf, kubernetes, cilium, tetragon, observability, linux-kernel, security-monitoring","wordCount":1715,"timeRequired":"PT9M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/ebpf-observability-production-kubernetes"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"eBPF en Producción: Observabilidad a Nivel de Kernel en Kubernetes Sin el Overhead","item":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿eBPF agrega overhead significativo a workloads de producción?","acceptedAnswer":{"@type":"Answer","text":"Cuando se implementa correctamente, eBPF agrega overhead despreciable, típicamente menos del 1% de CPU. La clave es filtrar eventos in-kernel en vez de mandar todo a userspace. Una probe mal escrita que copia buffers grandes en cada syscall puede agregar 5-10% de overhead."}},{"@type":"Question","name":"¿Puedo usar eBPF sin escribir código C?","acceptedAnswer":{"@type":"Answer","text":"Sí. Herramientas como Cilium, Tetragon y bpftrace proveen abstracciones de alto nivel. Tetragon usa TracingPolicies en YAML, y bpftrace tiene un lenguaje de scripting estilo awk. Para necesidades custom, libbpf-rs (Rust) y cilium/ebpf (Go) proveen wrappers seguros."}},{"@type":"Question","name":"¿Qué versión de kernel necesito para observabilidad con eBPF?","acceptedAnswer":{"@type":"Answer","text":"Mínimo kernel 5.4 para soporte BTF (portabilidad CO-RE). Kernel 5.8+ para ring buffers. Kernel 5.11+ para bpf_snprintf y soporte de timers. La mayoría de las ofertas de Kubernetes managed (EKS, GKE, AKS) corren 5.10+ desde 2025."}}]}]}],"contentHash":"9eada725f2cc0061e71a8b175ef82560778e2c74b9f9693270bd5f61e44665b5","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/ebpf-observability-production-kubernetes","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":73185,"contentBytes":16086}}
