Capítulo 1. Introducción Introducción
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
En las últimas décadas, la complejidad de los sistemas informáticos no ha hecho más que aumentar. Razonar sobre cómo se comporta el software ha creado múltiples categorías empresariales, todas ellas tratando de resolver los retos de obtener visibilidad de los sistemas complejos. Un enfoque para conseguir esta visibilidad es analizar los registros de datos generados por todas las aplicaciones que se ejecutan en un sistema informático. Los logs son una gran fuente de información. Pueden darte datos precisos sobre cómo se comporta una aplicación. Sin embargo, te limitan porque sólo obtienes la información que los ingenieros que crearon la aplicación expusieron en esos logs. Recopilar cualquier información adicional en formato de registro de cualquier sistema puede ser tan difícil como descompilar el programa y examinar el flujo de ejecución. Otro enfoque popular es utilizar métricas para razonar por qué un programa se comporta como lo hace. Las métricas difieren de los registros en el formato de los datos; mientras que los registros te dan datos explícitos, las métricas agregan datos para medir cómo se comporta un programa en un momento concreto.
La observabilidad es una práctica emergente que aborda este problema desde un ángulo diferente. La gente define la observabilidad como la capacidad que tenemos de hacer preguntas arbitrarias y recibir respuestas complejas de cualquier sistema dado. Una diferencia clave entre la observabilidad, los registros y la agregación de métricas son los datos que recopilas. Dado que al practicar la observabilidad necesitas responder a cualquier pregunta arbitraria en cualquier momento, la única forma de razonar sobre los datos es recopilando todos los datos que pueda generar tu sistema y agregándolos sólo cuando sea necesario para responder a tus preguntas.
Nassim Nicholas Taleb, autor de libros superventas como Antifrágil: Cosas que ganan con el desorden (Penguin Random House), popularizó el término Cisne Negro para referirse a sucesos inesperados, con consecuencias importantes, que podrían haberse esperado si se hubieran observado antes de que ocurrieran. En su libro El Cisne Negro (Penguin Random House), razona cómo disponer de datos relevantes podría ayudar a mitigar el riesgo de estos sucesos poco frecuentes. Los sucesos de Cisne Negro son más comunes de lo que pensamos en la ingeniería de software, y son inevitables. Dado que podemos asumir que no podemos evitar este tipo de sucesos, nuestra única opción es tener toda la información posible sobre ellos para abordarlos sin que afecten a los sistemas empresariales de forma crítica. La observabilidad nos ayuda a construir sistemas robustos y a mitigar futuros sucesos Cisne Negro, porque se basa en la premisa de que estás recopilando cualquier dato que pueda responder a cualquier pregunta futura. El estudio de los sucesos Cisne Negro y la práctica de la observabilidad convergen en un punto central, que está en los datos que recoges de tus sistemas.
Los contenedores Linux son una abstracción sobre un conjunto de características del núcleo Linux para aislar y gestionar procesos informáticos. El núcleo, tradicionalmente encargado de la gestión de recursos, también proporciona aislamiento de tareas y seguridad. En Linux, las principales características en las que se basan los contenedores son los espacios de nombres y los cgroups. Los espacios de nombres son los componentes que aíslan las tareas entre sí. En cierto sentido, cuando estás dentro de un espacio de nombres, experimentas el sistema operativo como si no hubiera otras tareas ejecutándose en el ordenador. Los Cgroups son los componentes que proporcionan gestión de recursos. Desde un punto de vista operativo, te proporcionan un control detallado sobre cualquier uso de recursos, como CPU, E/S de disco, red, etc. En la última década, con el aumento de la popularidad de los contenedores Linux, se ha producido un cambio en la forma en que los ingenieros de software diseñan grandes sistemas distribuidos y plataformas informáticas. La informática multiusuario se ha vuelto completamente dependiente de estas características del núcleo.
Al confiar tanto en las capacidades de bajo nivel del núcleo de Linux, hemos accedido a una nueva fuente de complejidad e información que debemos tener en cuenta al diseñar sistemas observables. El núcleo es un sistema basado en eventos, lo que significa que todo el trabajo se describe y ejecuta en función de eventos. Abrir archivos es un tipo de evento, ejecutar una instrucción arbitraria de la CPU es un evento, recibir un paquete de red es un evento, etc. El Filtro de Paquetes de Berkeley (BPF) es un subsistema del núcleo que puede inspeccionar esas nuevas fuentes de información. BPF te permite escribir programas que se ejecutan de forma segura cuando el núcleo activa cualquier evento. BPF te ofrece fuertes garantías de seguridad para evitar que inyectes fallos del sistema y comportamientos maliciosos en esos programas. BPF está habilitando una nueva oleada de herramientas para ayudar a los desarrolladores de sistemas a observar y trabajar con estas nuevas plataformas.
En este libro, te mostramos el poder que te ofrece BPF para hacer más observable cualquier sistema informático. También te mostramos cómo escribir programas BPF con la ayuda de múltiples lenguajes de programación. Hemos puesto el código de tus programas en GitHub, para que no tengas que copiarlo y pegarlo. Puedes encontrarlo en un repositorio Git que acompaña a este libro.
Pero antes de empezar a centrarnos en los aspectos técnicos de BPF, veamos cómo empezó todo.
Historia de BPF
En 1992, Steven McCanne y Van Jacobson escribieron el artículo "El Filtro de Paquetes BSD: Una nueva arquitectura para la captura de paquetes a nivel de usuario". En este documento, los autores describían cómo implementaron un filtro de paquetes de red para el núcleo Unix que era 20 veces más rápido que el estado del arte en filtrado de paquetes de la época. Los filtros de paquetes tienen una finalidad específica: proporcionar a las aplicaciones que monitorizan la red del sistema información directa del núcleo. Con esta información, las aplicaciones podían decidir qué hacer con esos paquetes. BPF introdujo dos grandes innovaciones en el filtrado de paquetes:
-
Una nueva máquina virtual (VM) diseñada para trabajar eficazmente con CPUs basadas en registros.
-
El uso de búferes por aplicación que podían filtrar paquetes sin copiar toda la información del paquete. Esto minimizó la cantidad de datos que BPF necesitaba para tomar decisiones.
Estas drásticas mejoras hicieron que todos los sistemas Unix adoptaran BPF como tecnología de elección para el filtrado de paquetes de red, abandonando las antiguas implementaciones que consumían más memoria y tenían menos rendimiento. Esta implementación sigue presente en muchos derivados de ese núcleo Unix, incluido el núcleo Linux.
A principios de 2014, Alexei Starovoitov presentó la implementación ampliada de BPF. Este nuevo diseño se optimizó para el hardware moderno, haciendo que su conjunto de instrucciones resultante fuera más rápido que el código máquina generado por el antiguo intérprete BPF. Esta versión ampliada también aumentó el número de registros en el BPF VM de dos registros de 32 bits a diez registros de 64 bits. El aumento del número de registros, y de su anchura, abrió la posibilidad de escribir programas más complejos, porque los desarrolladores tenían libertad para intercambiar más información utilizando parámetros de función. Estos cambios, entre otras mejoras, hicieron que la versión ampliada de BPF fuera hasta cuatro veces más rápida que la implementación original de BPF.
El objetivo inicial de esta nueva implementación era optimizar el conjunto de instrucciones BPF interno que procesaba los filtros de red. En ese momento, BPF aún estaba restringido al espacio del núcleo, y sólo unos pocos programas en el espacio del usuario podían escribir filtros BPF para que el núcleo los procesara, como Tcpdump y Seccomp, de los que hablaremos en capítulos posteriores. Hoy en día, estos programas siguen generando bytecode para el antiguo intérprete de BPF, pero el núcleo traduce esas instrucciones a la representación interna, muy mejorada.
En junio de 2014, la versión ampliada de BPF se expuso al espacio de usuario. Esto supuso un punto de inflexión para el futuro de BPF. Como escribió Alexei en el parche que introdujo estos cambios: "Este conjunto de parches demuestra el potencial de eBPF".
BPF se convirtió en un subsistema de nivel superior del núcleo, y dejó de limitarse a la pila de redes. Los programas BPF empezaron a parecerse más a los módulos del núcleo, con un gran énfasis en la seguridad y la estabilidad. A diferencia de los módulos del núcleo, los programas BPF no requieren que recompiles el núcleo y se garantiza que se completan sin bloquearse.
El verificador BPF, del que hablaremos en el próximo capítulo, añade estas garantías de seguridad necesarias. Asegura que cualquier programa BPF se completará sin bloquearse, y garantiza que los programas no intenten acceder a memoria fuera de rango. Sin embargo, estas ventajas conllevan ciertas restricciones: los programas tienen un tamaño máximo permitido, y los bucles deben estar acotados para garantizar que la memoria del sistema nunca se agote por un programa BPF defectuoso.
Con los cambios para que BPF sea accesible desde el espacio de usuario, los desarrolladores del núcleo también añadieron una nueva llamada al sistema (syscall), bpf
. Esta nueva llamada al sistema será la pieza central de comunicación entre el espacio de usuario y el núcleo. En los Capítulos 2 y 3 de este libro veremos cómo utilizar esta llamada al sistema para trabajar con programas y mapas BPF.
Los mapas BPF se convertirán en el principal mecanismo de intercambio de datos entre el núcleo y el espacio de usuario. El Capítulo 2 muestra cómo utilizar estas estructuras especializadas para recoger información del núcleo y enviar información a los programas BPF que ya se están ejecutando en el núcleo.
La versión ampliada de BPF es el punto de partida de este libro. En los últimos cinco años, BPF ha evolucionado significativamente desde la introducción de esta versión ampliada, y cubrimos en detalle la evolución de los programas BPF, los mapas BPF y los subsistemas del núcleo que se han visto afectados por esta evolución.
Arquitectura
La arquitectura de BPF dentro del núcleo es fascinante. Nos sumergimos en sus detalles específicos a lo largo de todo el libro, pero en este capítulo queremos darte una rápida visión general de cómo funciona.
Como hemos mencionado antes, BPF es una máquina virtual muy avanzada, que ejecuta instrucciones de código en un entorno aislado. En cierto sentido, puedes pensar en BPF como en la Máquina Virtual Java (JVM), un programa especializado que ejecuta código máquina compilado a partir de un lenguaje de programación de alto nivel. Los compiladores como LLVM, y GNU Compiler Collection (GCC) en un futuro próximo, proporcionan soporte para BPF, permitiéndote compilar código C en instrucciones BPF. Una vez compilado tu código, BPF utiliza un verificador para garantizar que el programa es seguro para ser ejecutado por el núcleo. Evita que ejecutes código que pueda comprometer tu sistema bloqueando el núcleo. Si tu código es seguro, el programa BPF se cargará en el núcleo. El núcleo Linux también incorpora un compilador justo a tiempo (JIT) para las instrucciones BPF. El JIT transformará el bytecode BPF en código máquina directamente después de verificar el programa, evitando esta sobrecarga en el tiempo de ejecución. Un aspecto interesante de esta arquitectura es que no necesitas reiniciar tu sistema para cargar programas BPF; puedes cargarlos bajo demanda, y también puedes escribir tus propios scripts init que carguen programas BPF cuando se inicie tu sistema.
Antes de que el núcleo ejecute cualquier programa BPF, necesita saber a qué punto de ejecución está unido el programa. Hay múltiples puntos de ejecución en el núcleo, y la lista sigue creciendo. Los puntos de ejecución están definidos por los tipos de programa BPF; hablaremos de ellos en el próximo capítulo. Cuando eliges un punto de ejecución, el núcleo también pone a tu disposición ayudantes de función específicos que puedes utilizar para trabajar con los datos que recibe tu programa, lo que hace que los puntos de ejecución y los programas BPF estén estrechamente acoplados.
El último componente de la arquitectura de BPF se encarga de compartir datos entre el núcleo y el espacio de usuario. Este componente se llama mapa BPF, y hablamos de los mapas en el Capítulo 3. Los mapas BPF son estructuras bidireccionales para compartir datos. Esto significa que puedes escribirlos y leerlos desde ambos lados, el núcleo y el espacio de usuario. Hay varios tipos de estructuras, desde simples matrices y mapas hash hasta mapas especializados, que te permiten guardar en ellos programas BPF enteros.
Cubrimos cada componente de la arquitectura de BPF con más detalle a medida que avanza el libro. También aprenderás a aprovechar la extensibilidad y el uso compartido de datos de BPF, con ejemplos concretos que abarcan temas que van desde el análisis de rastros de pila hasta el filtrado de redes y el aislamiento en tiempo de ejecución.
Conclusión
Escribimos este libro para ayudarte a familiarizarte con los conceptos básicos de BPF que vas a necesitar en tu trabajo diario con este subsistema Linux. BPF sigue siendo una tecnología en desarrollo, y mientras escribimos este libro van surgiendo nuevos conceptos y paradigmas. Lo ideal es que este libro te ayude a ampliar tus conocimientos fácilmente, proporcionándote una base sólida de los componentes fundamentales de BPF.
El siguiente capítulo se sumerge directamente en la estructura de los programas BPF y en cómo los ejecuta el núcleo. También cubre los puntos del núcleo donde puedes adjuntar esos programas. Esto te ayudará a familiarizarte con todos los datos que pueden consumir tus programas y cómo utilizarlos.
Get Observabilidad de Linux con BPF now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.