El kernel del sistema operativo Linux es el programa principal que se ejecuta después del arranque del equipo y permite que el hardware y el software “conversen entre sí”. Comprender esto facilita entender por qué la tecnología eBPF aprovecha el sistema operativo para implementar la observabilidad, la seguridad y el funcionamiento en las redes, lo cual es fundamental para la conectividad que demanda la digitalización de los negocios de hoy.
Desafíos en el desarrollo en el kernel de Linux
Linux se ha caracterizado por la apertura y la colaboración a lo largo de su evolución desde sus orígenes en los años 90, abierto a modificaciones y redistribuciones a lo largo del tiempo. Este modelo de licencia de código abierto generó contribuciones de todo el mundo, sentando las bases del desarrollo colaborativo. Está claro que en un inicio había cierto escepticismo sobre la viabilidad y el potencial de Linux, ya que, en ese entonces, los sistemas propietarios eran los preferidos y la idea de un sistema operativo libre y desarrollado en colaboración suscitaba dudas. Con todo, la apertura y la inclusión de Linux atrajeron a desarrolladores por la posibilidad de usar, modificar y distribuir libremente un sistema, tendencia que aún sigue en nuestros días.
Solución: Extended Berkeley Packet Filter (eBPF)
Las capacidades del kernel continuaron expandiéndose, impulsadas por la colaboración y la inteligencia colectiva por las contribuciones de desarrolladores de todo el mundo. A esto se sumó la adopción de Internet: al inicio de los 90, Internet todavía estaba en sus primeras etapas y los ingenieros estaban interesados en encontrar formas de comprender mejor el tráfico que fluía a través de sus redes. BPF fue diseñado para proporcionar una manera de hacer esto sin tener que modificar el código de todos los servidores de una organización o bifurcar el código del kernel y actualizarlo con instrucciones de instrumentación.
¿Qué es eBPF?
BPF originalmente significaba Berkeley Packet Filter y se usaba para registrar todas las comunicaciones TCP entrantes y salientes en un servidor. Si bien este enfoque requiere menos trabajo que modificar el código de todos los servidores, aún así precisaba de un esfuerzo significativo para crear el código e implementarlo en todos los servidores de la organización, y sólo podía aceptar paquetes con un hash firmado en el encabezado. Cuando llegaba un paquete entrante, se iniciaba el programa BPF y se verificaba el encabezado para ver si el valor esperado estaba presente. Dependiendo de los resultados, el paquete era aceptado o descartado. Sin embargo, ya no solo se necesitaba el filtrado, por lo que se dio origen a una versión de BPF extendido o eBPF.
Diferencias entre eBPF y BPF
Como ya comentábamos, actualmente, BPF se utiliza para diversos fines más allá del simple análisis y filtrado del tráfico de red, ampliando sus funciones y compatibilidad con 64 bytes, lo que permite una mayor recopilación y almacenamiento de información. Esto es porque, desde el lanzamiento del kernel 3.18, se crearon filtros de paquetes extendidos de Berkeley (eBPF) para registrar casi todos los eventos del kernel. Los eBPF, pueden almacenar e intercambiar más datos y usarse para diversos fines, incluyendo la monitorización, el seguimiento y la optimización de las funciones del kernel.
En el código fuente de Linux, se observa que el término BPF persiste; y en las herramientas y la documentación, los términos BPF y eBPF generalmente se usan indistintamente. También se ha visto que el BPF original a veces se denomina cBPF (BPF clásico) para distinguirlo del eBPF.
Funcionamiento de eBPF
Para comprender cómo se usa eBPF, es importante entender los conceptos clave:
- Hooks predefinidos. Los programas eBPF se ejecutan según los eventos que los desencadenan: una aplicación (o el kernel) pasa un umbral conocido como punto de enlace. Los Hooks están predefinidos e incluyen eventos de red, llamadas al sistema (system calls o syscalls), entrada y salida de funciones y puntos de seguimiento (tracepoints) de kernel. Si no hay un enlace predefinido para un requisito determinado, se puede crear una sonda de usuario o kernel (uprobe o kprobe).
- Verificación del programa. Una vez que se identifica un enlace, la llamada al sistema BPF se puede usar para cargar el programa eBPF correspondiente en el kernel de Linux. Esto implica el uso de una biblioteca eBPF. Cuando se carga un programa en el kernel, se verifica para garantizar que sea seguro ejecutarlo, tomando en cuenta condiciones como que:
- El programa sólo puede cargarse mediante un proceso eBPF privilegiado (a menos que se especifique lo contrario).
- El programa no dañará ni bloqueará el sistema.
- El programa siempre se ejecutará hasta su finalización (no permanecerá en un bucle sin fin).
- Mapas eBPF. Un programa eBPF debe poder almacenar su estado y compartir los datos recopilados. Los mapas eBPF pueden ayudar a los programas a recuperar y almacenar información de acuerdo con una variedad de estructuras de datos. Los usuarios pueden acceder a mapas de eBPF a través de llamadas al sistema, tanto desde programas como desde aplicaciones de eBPF. Los tipos de mapas son tablas o matrices hash, búfer circular, seguimiento de stack o coincidencia de prefijo más larga, entre otros.
- Llamadas de ayuda (helper calls). Un programa eBPF no puede llamar arbitrariamente a una función del kernel, ya que se necesita mantener la compatibilidad; por lo que los eBPF utilizan funciones auxiliares para realizar las llamadas a funciones. Las funciones auxiliares son API (interfaces) proporcionadas por el kernel y se pueden ajustar fácilmente.
Las Helper Calls permiten a los programas generar números aleatorios, recibir la fecha y hora actuales, acceder a mapas eBPF, manipular la lógica de reenvío y los paquetes de red, entre otros. - Llamadas de función (function call) y de cola (tail call) Estas llamadas hacen que los programas eBPF puedan estar formados por varios elementos. Las function calls permiten definir y llamar funciones en un programa. Las llamadas de cola permiten la ejecución de otros programas eBPF. También pueden cambiar el contexto de ejecución.
Proceso de ejecución de programas eBPF
eBPF permite a los desarrolladores escribir pequeños programas que pueden adjuntarse a varios puntos del kernel para interceptar y procesar datos. Generalmente escritos en un subconjunto del lenguaje de programación C, los programas luego se compilan en el código de bytes eBPF, que luego se carga en el kernel y se ejecuta. A diferencia de los módulos del kernel tradicionales, los programas eBPF son fáciles de implementar y administrar, ya que no necesitan modificaciones ni recopilación del código fuente del kernel.
El programa eBPF sigue una serie de pasos cuando se ejecuta dentro del kernel de Linux:
- Una vez que se ha escrito el programa eBPF, se compila en el bytecode de eBPF. El resultante es una representación, independiente de la plataforma, del programa eBPF que se puede ejecutar dentro del kernel.
- A continuación, el bytecode de eBPF se carga en el kernel. El proceso de carga implica verificar la seguridad y compatibilidad del bytecode con el kernel en ejecución, y asignar los recursos necesarios para ejecutar el programa eBPF.
- Una vez que se ha cargado el programa eBPF, se puede adjuntar a un evento del kernel específico, como un paquete de red o una syscall.
- Cuando ocurre el evento del kernel adjunto, éste ejecuta automáticamente el programa eBPF. El programa eBPF puede acceder y modificar datos asociados con el evento, usando la programación C estándar, como bloops y condicionales.
- Después de que el programa eBPF haya procesado los datos, puede devolver un resultado al kernel. Por ejemplo, un programa eBPF adjunto a una interfaz de red podría descartar un paquete si coincide con ciertos criterios, o un programa eBPF adjunto a un syscall podría modificar los argumentos pasados a la llamada antes de permitirle continuar.
- Cuando el programa eBPF ya no es necesario, se puede descargar del kernel, liberando los recursos asignados al programa eBPF y asegurando que ya no se ejecutará cuando ocurra el evento del kernel adjunto.
Cabe notar que, una de las características clave de eBPF son sus garantías de seguridad. Los programas eBPF se ejecutan en una máquina virtual restringida dentro del kernel y no pueden acceder ni modificar las estructuras de datos del kernel directamente ni causar fallas o vulnerabilidades de seguridad. También, los programas eBPF están sujetos a varias comprobaciones de tiempo de ejecución, como la verificación de límites y la verificación de tipo, para garantizar que no violen la seguridad de la memoria u otras propiedades de seguridad.
Ventajas de eBPF
La implementación de eBPF tiene beneficios como:
- Velocidad y rendimiento: eBPF es una solución universal y eficiente que los desarrolladores usan para asegurar el rendimiento y la estabilidad a los sistemas.
- Bajo intrusismo: Debido a que los programas eBPF se ejecutan dentro del kernel de Linux, operan con una sobrecarga mínima en comparación con los programas tradicionales del espacio del usuario.
- Seguridad: eBPF visualiza y explica las llamadas al sistema, lo que permite tener un control en los procesos de red.
- Conveniencia: La tecnología requiere un mínimo de configuración en comparación con otras soluciones para que los desarrolladores puedan implementar rápidamente sus aplicaciones en entornos de producción con el menor esfuerzo.
- Rastreabilidad unificada: Con eBPF, se puede recopilar datos sobre rendimiento de aplicaciones y servicios basados en Linux a través de programas que se ejecutan en el espacio del kernel. Con esos datos se pueden solucionar problemas de rendimiento.
- Programabilidad: Los desarrolladores que utilizan eBPF pueden escribir nuevas funciones que antes no se podían haber realizado debido a limitaciones técnicas o problemas de seguridad. Con eBPF, pueden escribir aplicaciones que aprovechen la aceleración de hardware en dispositivos integrados como Raspberry Pi (una microcomputadora de bajo costo que se puede insertar en un monitor o televisor) u otros sistemas basados en ARM, que es una arquitectura de procesador ampliamente utilizada en dispositivos móviles, servidores y sistemas incrustados.
- Expresividad: Con eBPF es posible extraer datos de observabilidad de seguridad que ayuda a los desarrolladores de aplicaciones a rastrear aplicaciones, proporcionar información para la resolución de problemas de rendimiento, aplicar la seguridad en tiempo de ejecución de las aplicaciones y contenedores, entre otros.
Mejores Prácticas de eBPF
- Compilación con LLVM y Clang: Clang se usa para compilar programas eBPF desde código C y se apoya en LLVM como backend para garantizar la corrección y la seguridad.
- Uso del kit de herramientas BCC: BCC es un conjunto de herramientas para crear programas eficientes de manipulación y seguimiento del kernel e incluye ejemplos útiles.
- Aprovechamiento de funciones auxiliares de eBPF: Uso de la conexión de programas pequeños y eficientes a varios hooks del kernel para obtener visibilidad y monitorización de las capacidades, brindar seguridad a la red y contenedores/microservicios de las aplicaciones, además de detectar/prevenir intrusiones y reforzar políticas de seguridad.
- Uso de mapas para compartir datos: Los mapas eBPF se pueden compartir entre ejecuciones consecutivas de programas, entre el kernel y el espacio de usuario o, según su caso de uso, entre diferentes programas eBPF, sin importar a qué interfaces estén conectados.
- Monitorización de límites de recursos: La monitorización de la capacidad de los recursos permite implementar acciones preventivas por parte de los empleados de TI.
- Validación de entradas de usuario: Se pueden crear programas eBPF personalizados para hacer cumplir los controles de acceso, regular el uso de recursos y proteger los componentes sensibles del sistema contra manipulaciones.
- Mantenimiento actualizado del kernel: Los fabricantes de hardware están haciendo avances constantes, por lo que la actualización permite integrar nuevas tecnologías, además de mejorar el rendimiento.
- Uso de Librerías y frameworks de eBPF: Los frameworks y las librerías de monitorización eBPF permiten un seguimiento eficiente de procesos y eventos de red, además del análisis de símbolos de depuración del kernel.
- Documentación de código: La documentación ayuda a los desarrolladores a rastrear aplicaciones y llevar un registro sobre resolución de problemas de rendimiento.
Desventajas de eBPF
- Limitaciones de portabilidad: El uso de eBPF para realizar un procesamiento intensivo de CPU o paquete por paquete no sería eficiente porque necesitaría construirse una estructura y realizar una búsqueda para cada paquete, lo cual puede llegar a ser costoso.
- Limitaciones de programas sandboxed: eBPF no fue diseñado para la monitorización de seguridad. Los programas sandboxed tienen sus limitaciones:
- Memoria: Operan dentro de un espacio de memoria restringido.
- Syscall: Solo pueden realizar syscalls específicas permitidas por el verificador eBPF.
- Acceso a las estructuras de datos del kernel: El acceso directo a las estructuras de datos del kernel está restringido.
- Acceso al sistema de archivos: No pueden leer ni escribir archivos directamente.
Casos de uso de eBPF
- Monitorización del sistema en tiempo real. A diferencia de indicadores y contadores estáticos, eBPF permite la generación de eventos de visibilidad y la recopilación y agregación de métricas personalizadas en tiempo real. Esto aumenta la profundidad de la visibilidad que se puede lograr y reduce drásticamente la sobrecarga general del sistema.
- Seguridad informática. eBPF permite detectar e investigar anomalías en operaciones, como un proceso inusual que se han iniciado en un sistema y rastrear su comportamiento para determinar a qué recursos están intentando acceder o qué datos se están transmitiendo a través de la red. De esta manera, eBPF complementa y ayuda a respaldar otros procesos como parte de los sistemas de seguridad que protegen a tu servidor.
- Redes. La capacidad de inspeccionar todos los eventos de la red y los flujos de tráfico de la red que entran y salen de un sistema y determinar qué procesos interactúan con ellos ayuda a los administradores a solucionar problemas de red. También proporciona un contexto que puede ser útil cuando se busca determinar si un problema de rendimiento con una aplicación es provocado por un evento de red o por la propia aplicación.
- Monitoreo del rendimiento de aplicaciones. Se pueden calcular métricas de rendimiento para aplicaciones, microservicios y procesos individuales de forma granular. Esto permite establecer perfiles de rendimiento y realizar un seguimiento continuo para detectar anomalías. Con eBPF no es necesario implementar y configurar agentes para rastrear cada aplicación o proceso.
- Versatilidad. A diferencia de las herramientas de monitorización tradicionales, centradas principalmente en el consumo de recursos del sistema, la creación de perfiles eBPF proporciona una comprensión granular de cuánto de CPU, memoria y recursos de red está consumiendo una aplicación o proceso en específico. Estos datos detallados permiten identificar aplicaciones que consumen muchos recursos, definir posibles optimizaciones y tomar decisiones informadas sobre la migración de cargas de trabajo.
Aplicaciones de eBPF en Observabilidad
Acceso a eventos de bajo nivel del sistema: eBPF permite la recopilación y agregación en el kernel de métricas personalizadas, mediante eventos de visibilidad y estructuras de datos de diversas fuentes sin tener que exportar muestras.
Aplicaciones en seguridad, rendimiento y networking: eBPF se usa para proporcionar formas más eficientes y seguras de administrar aplicaciones en contenedores y analizar el rendimiento de los sistemas. Por ejemplo:
- Proyecto Falco usa eBPF para dar seguridad en el momento de ejecución para aplicaciones en contenedores.
- Proyecto Cilium aprovecha eBPF para realizar el filtrado de red, el equilibrio de carga e incluso la seguridad en el espacio del kernel, lo que mejora la eficiencia de aplicaciones en contenedores.
- BCC (BPF Compiler Collection) es un conjunto de herramientas y bibliotecas que se usan eBPF para dar seguimiento y análisis de rendimiento para sistemas Linux.
Cómo empezar con eBPF
Ahora que conoces cómo aprovechar eBPF, sus beneficios y consideraciones, solo queda compartir contigo algunas recomendaciones para empezar a usar eBPF. El enriquecimiento de eBPF parte de las contribuciones de desarrolladores independientes, por lo que podemos facilitarte algunos enlaces a recursos y herramientas que puedes aprovechar:
- BCC Toolkit, con herramientas y ejemplos de uso de eBPF y recursos de programación en C y Python.
- Página de Infraestructura de eBPF, donde podrás encontrar los compiladores de la infraestructura de LLVM y GCC incluyendo códigos de programas, aportaciones sobre brechas por resolver y blogs de soporte, entre otros.
- Casos de éxito mundiales de uso de eBPF, como Google, Netflix, Android, Cloudflare, Android, Microsoft, entre otros.
- Linux Foundation, donde hallarás datos interesantes sobre la comunidad involucrada en la evolución de EBPF y su ecosistema. También encontrarás en informe State of eBPF, con un poco de su historia y perspectivas a futuro.
Más allá de los límites,
más allá de las expectativas