Ir al contenido

Blog

Presentamos: el nuevo Cluster Agent basado en Rust


tl;dr Migramos nuestro cluster agent de Go a Rust y ahora es más pequeño y usa menos memoria. Para usar el nuevo cluster agent simplemente actualiza a la última release (cli/v0.8.2, helm/v0.15.2). También puedes probarlo en vivo aquí.


Hace poco decidimos migrar nuestro cluster agent de Go a Rust. Ahora me alegra decir que la reescritura está completa y el resultado es una imagen del cluster agent un 57% más pequeña (10MB) que usa un 70% menos de memoria (~3MB) mientras sigue usando una cantidad mínima de CPU (~0.1%).

La primera versión de Kubetail fue diseñada para ejecutarse dentro del clúster y exponer los logs a los usuarios a través de un navegador web. En esa versión, la responsabilidad principal del backend era hacer solicitudes a la API de Kubernetes y retransmitir las respuestas al frontend en tiempo real. Después de evaluar varias opciones, incluyendo Python y JavaScript, decidí escribirlo en Go porque cuenta con buen soporte por parte de la API de Kubernetes, tiene un excelente soporte para multithreading y produce ejecutables rápidos e imágenes Docker pequeñas.

La siguiente versión de Kubetail añadió la herramienta CLI kubetail, capaz de ejecutar el dashboard web localmente. Para implementar la CLI elegí Go nuevamente porque el lenguaje tiene buenas bibliotecas para la interacción en línea de comandos (gracias spf13!), un excelente soporte multiplataforma y, lo más importante, porque me permitía reutilizar la aplicación web en Go usada por el dashboard dentro del clúster.

Hasta ese momento, Kubetail solo obtenía logs a través de la API de Kubernetes. Pero cuando quise añadir nuevas funcionalidades como el tamaño de los archivos de log y las marcas de tiempo del último evento —datos no expuestos por la API de Kubernetes— me di cuenta de que necesitábamos un agente con acceso directo a los archivos de log en bruto de cada nodo. Aunque podría haber usado un lenguaje diferente, elegí Go de nuevo porque era el lenguaje que mejor conocía y nos había servido bien hasta entonces. Por suerte, también tenía un excelente soporte para gRPC, que era una elección natural para la interfaz del agente.

Dado el conjunto de funcionalidades de la aplicación en ese momento, estaba muy satisfecho con mi elección original de Go, ya que nos había servido bien tanto en el escritorio como dentro del clúster. Luego empecé a pensar en cómo implementar la funcionalidad más solicitada: la búsqueda en logs.

Cuando empecé a pensar en la búsqueda en logs, sabía que quería usar grep en lugar de un índice de texto completo porque es suficiente para la mayoría de los casos de uso y no quería que nuestros usuarios tuvieran que asumir el coste en recursos de mantener un índice de texto completo. Al mismo tiempo, llevaba un tiempo usando rg personalmente para hacer grep de logs y me había impresionado su velocidad, así que cuando empecé a buscar una solución de grep me pregunté si podría usarlo de alguna manera. Fue entonces cuando me di cuenta de que estaba disponible como biblioteca, pero con una condición: estaba escrito en Rust.

Antes de escribir código personalizado, exploré la idea de usar rg como ejecutable externo mediante exec.Command para interactuar con él a través de stdin/stdout. Esto funcionó para casos de uso básicos, pero se volvió difícil de manejar cuando empecé a añadir funcionalidades personalizadas como filtros de tiempo, manejo de secuencias de escape ANSI y soporte para líneas en formato JSON. Así que decidí sumergirme y escribir un grepper de archivos de log personalizado. Exploré brevemente la opción de usar Go, pero finalmente decidí que por razones de rendimiento y robustez quería usar la biblioteca detrás de rg, ripgrep, lo que significaba que el código tenía que escribirse en Rust.

En ese momento, no quería reescribir todo el cluster agent en Rust, así que exploré formas de llamar a Rust desde Go (p. ej. rustgo) y opté por mantener el código Rust personalizado como un ejecutable separado y llamarlo desde Go usando exec.Command. Para que el código fuera lo más simple posible, usé un esquema de protocol buffers compartido con serialización/deserialización implementada en la interfaz stdin/stdout.

Tras lanzar la funcionalidad de búsqueda, nuestra comunidad empezó a crecer y conocí a un par de hackers con mucha más experiencia en Rust que yo: Christopher Valerio (freexploit) y Giannis Karagiannis (gikaragia). Inicialmente empezaron a hacer mejoras al código Rust y, a medida que fueron familiarizándose con la base de código, comenzamos a hablar sobre cómo eliminar la fricción entre Go y Rust en el cluster agent. Independientemente de la funcionalidad de búsqueda, el cluster agent se ejecuta en cada nodo del clúster, por lo que es importante que sea lo más eficiente y ligero posible, que es exactamente el caso de uso para el que Rust fue diseñado. Con estas ideas flotando en el ambiente, tuvimos una reunión de comunidad donde discutimos la idea de migrar todo el agente a Rust. Dijeron que estaban entusiasmados con trabajar en ello, así que dije: ¡adelante!

Una vez tomada la decisión, Christopher y Giannis se pusieron manos a la obra. Christopher definió la arquitectura de alto nivel inicial del proyecto y creó algunos issues iniciales en GitHub. Luego Giannis tomó el relevo y empezó a implementar el conjunto de funcionalidades, escribiendo pruebas y creando más issues para poder recibir ayuda de otros contribuidores. Giannis logró alcanzar la paridad de funcionalidades con el cluster agent en Go en apenas unas pocas semanas y, tras aproximadamente otra semana de pruebas, decidimos que el código estaba listo para fusionarse con main.

Yo empecé a aprender Rust recientemente, así que Claude Code y Codex CLI fueron invaluables para ayudarme a revisar los pull requests de Giannis. Él también estaba usando los chatbots en su lado, así que fue una verdadera colaboración humano-bot mediada por los pull requests de GitHub. Una de las ventajas clave que tuvimos fue que, como el agente usa una interfaz gRPC bien definida, pudimos reutilizar el esquema de protocol buffers y simplemente hacer el cambio cuando el agente en Rust alcanzó la paridad de funcionalidades con la versión en Go. Para construir el servidor gRPC en Rust usamos tonic, que fue sencillo y solo tuvo pequeñas diferencias respecto al servidor gRPC en Go.

El resultado final es una imagen del cluster agent un 57% más pequeña (10MB) que usa un 70% menos de memoria (~3MB) mientras sigue usando una cantidad mínima de CPU (~0.1%). Además, el código es mucho más fácil de trabajar ahora porque todo está en el mismo lenguaje.

Nuestra misión es dar a los usuarios acceso a potentes herramientas de logging en un paquete simple y ligero, pero la API de Kubernetes tiene capacidades de logging limitadas, por lo que desbloquear funcionalidades más avanzadas requiere acceso directo a los archivos de log en bruto de cada nodo. Ahí es donde entra el cluster agent: es la base de todo lo que queremos construir a continuación.

Por supuesto, los usuarios son comprensiblemente cautelosos a la hora de instalar agentes en sus clústeres. Además de ser útiles, los agentes deben ser pequeños, rápidos y seguros. La migración a Rust es nuestra respuesta a esos requisitos. Al reducir el tamaño de la imagen en más de la mitad y el uso de memoria en un 70%, hemos hecho que el agente de Kubetail sea lo suficientemente pequeño como para desplegarse incluso en los entornos más limitados en recursos.

Pero esto es solo el comienzo. Rust nos permitirá llevar al límite lo que se puede hacer dentro del clúster en tiempo real, directamente con los archivos en disco, usando la menor cantidad posible de CPU y memoria. Ahora mismo nuestro enfoque está en los logs, pero el mismo enfoque se aplica a métricas, notificaciones y otros tipos de datos de observabilidad.

Estamos entusiasmados con lo que viene y nos encantaría que formaras parte de ello. Si te gusta lo que estamos haciendo y quieres contribuir con código o compartir tu opinión como usuario, únete a nosotros en Discord.

Gracias, OCV

Si llegamos a cumplir nuestra misión de construir una nueva capa de logging para Kubernetes que funcione en cada clúster, será en gran parte gracias a Open Core Ventures (OCV) y su programa Catalyst liderado por Alex Smith.

OCV es una firma de capital riesgo fundada por Sid Sijbandij (cofundador de GitLab) que financia empresas de código abierto en etapa temprana basadas en principios de Open Core. Como parte de sus esfuerzos de divulgación en código abierto, pusieron en marcha un programa llamado Catalyst que ofrece un pequeño estipendio y mucha mentoría a los mantenedores de proyectos de código abierto interesados en hacer crecer sus proyectos. A lo largo de 12 semanas te enseñan a construir una comunidad de código abierto y a promocionar tu producto de manera efectiva para que pueda crecer y encontrar tracción. Kubetail participó recientemente en el programa y, para nosotros, fue un cambio de juego.

Antes de trabajar en Kubetail, cofundé una startup llamada Octopart que formó parte del batch W07 de Y Combinator. No nos fue mal como startup, así que cuando empecé a trabajar en Kubetail seguí un enfoque similar: me centré en construir un MVP y en cuanto estuvo listo lo publiqué en Hacker News (HN). Afortunadamente, la publicación llegó a la portada durante unas horas y terminamos con un par de cientos de estrellas en GitHub y un pequeño número de usuarios reales (~10).

Después, Kubetail entró en el Valle de la Tristeza. Esta es la parte de la curva de una startup después de tu lanzamiento inicial, cuando el entusiasmo se apaga y te quedas con un puñado de usuarios, sin validación externa, y solo con tu propio optimismo interno para seguir adelante. No era la primera vez que atravesaba el valle, así que hice lo que había hecho anteriormente: agaché la cabeza y seguí programando.

Durante ese período, me centré en hacer que nuestro MVP (el Kubetail Dashboard) fuera lo más fácil de usar posible. En respuesta a los comentarios de algunos usuarios tempranos, cambié la arquitectura para que pudiera ejecutarse en el escritorio del usuario además de dentro del clúster. También me centré en facilitar que los usuarios encontraran y descargaran la aplicación a través de Homebrew y otros repositorios de paquetes. Y en paralelo, me centré en implementar nuestra funcionalidad más solicitada: la búsqueda.

Durante más de un año trabajé solo mientras el crecimiento del proyecto se estancaba. Entonces recibí un correo electrónico inesperado de OCV que nos llevó a ser aceptados en el programa de patrocinio Catalyst, que lo cambió todo.

Como parte de Catalyst, recibí mentoría práctica de Alex y el equipo de OCV que resultó invaluable para alguien como yo, con habilidades técnicas pero sin experiencia en la construcción de comunidades ni en la gestión de proyectos de código abierto. Con la ayuda de Catalyst, cambié mi rutina de programación pura a equilibrar el desarrollo con la participación en la comunidad y el apoyo a los contribuidores.

Antes de Catalyst, Kubetail no tenía ninguna comunidad. Teníamos un servidor de Discord pero yo era el único allí, trabajando solo todos los días. Luego Alex me fue guiando semana a semana, sugiriendo en qué enfocarme y nuevas cosas que probar. Con su ayuda, Kubetail creció de unas 300 estrellas a más de 1.300 en el transcurso de 12 semanas. Y aún más significativo, la comunidad despegó. Antes de Catalyst teníamos 3 contribuidores y ningún usuario en Discord. Ahora tenemos 35 contribuidores y una vibrante comunidad en Discord con 61 miembros.

Durante Catalyst, todo encajó y finalmente estuvimos listos para lanzar nuestra funcionalidad de búsqueda en logs, pero esta vez con una comunidad detrás y la mentoría de OCV para ayudarnos a promocionar la funcionalidad entre nuevos usuarios. Esta vez, cuando anunciamos la funcionalidad, Kubetail llegó a la portada de HN durante más de un día y fue visto por decenas de miles de usuarios en Reddit y Twitter. Esto se tradujo en un aumento de las descargas mensuales de menos de 100 a más de 400 y transformó Kubetail de un pequeño proyecto personal en un ambicioso proyecto impulsado por la comunidad. El momento culminante de Catalyst para mí ocurrió en torno a esta época, cuando pude celebrar el hito de las 1.000 estrellas en GitHub con un nuevo mantenedor de Kubetail (rxinui) y el resto de nuestra comunidad.

Celebración en Discord

No me hago ilusiones sobre lo difícil que es el camino por delante. Estamos trabajando en un problema técnico complejo y operamos en un espacio con muchas empresas bien financiadas como Datadog, Grafana, New Relic y ClickHouse que ya tienen la atención de la mayoría de nuestros potenciales usuarios. Además, los usuarios ya esperan muchas funcionalidades de las herramientas de observabilidad, por lo que necesitaremos muchos ingenieros con talento para lograrlo y, para ello, necesitamos recursos que aún no hemos encontrado la manera de conseguir.

Sin embargo, nunca he sido más optimista sobre nuestras posibilidades de éxito. Cada vez que aprendo algo nuevo de uno de nuestros contribuidores experimentados o veo lo entusiasmados que se ponen nuestros contribuidores más jóvenes cuando uno de sus pull requests se fusiona, me llena de energía. Cada vez que reviso un pull request de un usuario que resuelve su propio problema o tengo una conversación con alguien sobre una nueva funcionalidad, me hace sentir aún más convencido de que elegí la mejor manera de construir un producto: juntos, como parte de una comunidad de código abierto.

Para mí, un proyecto de código abierto es como una olla que puede producir productos de alta calidad que los usuarios adoran usar y que además son saludables para ellos. Pero por supuesto el ingrediente mágico detrás de cada producto es la comunidad y, cuando se trata de la comunidad de Kubetail, tengo que dar un gran agradecimiento a Alex y al resto del equipo de OCV.

Presentamos: búsqueda de logs en tiempo real para Kubernetes

Desde que Kubetail se lanzó el año pasado, la funcionalidad más solicitada ha sido la búsqueda en logs. Ahora me alegra decir que por fin tenemos búsqueda de logs disponible en nuestra última release oficial (cli/v0.4.3, helm/v0.10.1). Puedes verla en acción aquí:

https://www.kubetail.com/demo

Implementar la búsqueda llevó tiempo porque la API de Kubernetes no la soporta de forma nativa, así que tuvimos que construir la funcionalidad desde cero. Consideramos implementarla rápidamente usando grep en el lado del cliente, pero eso no habría sido una buena experiencia de usuario, ya que cada búsqueda podría haber desencadenado una descarga completa de muchos archivos de log, lo que habría sido lento y consumiría mucho ancho de banda. Hay formas de evitarlo, pero habría requerido más intervención del usuario, lo que tampoco es una buena experiencia.

En cambio, implementamos la búsqueda creando un ejecutable personalizado basado en Rust que envuelve ripgrep. ¿Por qué Rust? Porque es increíblemente rápido. La mayor parte del backend de Kubetail está escrita en Go, pero para este componente de bajo nivel que lee archivos de log en disco queríamos que fuera lo más rápido posible. El resultado: un escaneo completo de un archivo de 1GB tarda ~250ms. En cada consulta, el ejecutable escanea únicamente los archivos de log del contenedor relevantes en cada nodo y transmite de vuelta solo las líneas que coinciden a tu navegador. La mayoría de las consultas pueden detenerse antes de tiempo, lo que significa que pueden incluso devolver resultados antes de completar el escaneo. Puedes pensar en la búsqueda de Kubetail como un “grep remoto” para tus logs de Kubernetes. Ya no necesitas descargar un archivo de log entero solo para hacer grep localmente.

Para habilitar la búsqueda, necesitas instalar los “Cluster Resources” de Kubetail en tu clúster. Esto se puede hacer fácilmente haciendo clic en “Install” desde la interfaz gráfica o ejecutando kubetail cluster install desde la CLI. Esta acción desplegará un Kubetail Cluster Agent en cada nodo, así como una instancia de la Kubetail Cluster API. Cuando la Cluster API esté disponible, el dashboard la usará para acceder a las funcionalidades personalizadas de Kubetail, como la búsqueda en los nodos. En caso contrario, el dashboard deshabilitará esas funcionalidades en la interfaz gráfica y usará la API de Kubernetes como alternativa.

Apenas estamos comenzando con la búsqueda en logs y hay mucho margen para mejorarla. Si eres un hacker de Rust, Go o React, o un diseñador de UI al que le apasionan los logs, ven a ayudarnos a construir la plataforma de logging de código abierto más amigable para Kubernetes. ¡Únete a nuestra comunidad en Discord!

Andres

Presentamos el comando "logs" de la CLI de Kubetail

Para facilitar la monitorización y depuración de workloads multicontenedor en Kubernetes, hemos añadido un nuevo comando logs a la herramienta CLI de Kubetail. Con el nuevo comando logs ahora puedes hacer grep de los logs de tus workloads de Kubernetes en tiempo real desde tu terminal. También puedes filtrar por tiempo y otras propiedades de la fuente, como el nodo y la zona.

Para instalar la herramienta CLI de Kubetail puedes descargarla desde la página de releases o usar Homebrew:

Ventana de terminal
brew install kubetail

Aquí tienes algunos ejemplos de lo que puedes hacer con el nuevo comando logs:

Ventana de terminal
# Tail 'web' deployment in the 'default' namespace
kubetail logs deployments/web
# Tail 'web' deployment in the 'frontend' namespace
kubetail logs frontend:deployments/web
# Return last 100 records
kubetail logs deployments/web --tail=100
# Return first 100 records
kubetail logs deployments/web --head=100
# Stream new records
kubetail logs deployments/web --follow
# Return all records
kubetail logs deployments/web --all
# Return first 10 records starting from 30 minutes ago
kubetail logs deployments/web --since PT30M
# Return last 10 records leading up to 30 minutes ago
kubetail logs deployments/web --until PT30M
# Return first 10 records between two exact timestamps
kubetail logs deployments/web --since 2006-01-02T15:04:05Z07:00 --until 2007-01-02T15:04:05Z07:00
# Return last 10 records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --force
# Return first 10 records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --head --force
# Return last 10 records that match "GET /about" or "GET /contact"
kubetail logs deployments/web --grep "GET /(about|contact)" --force
# Stream new records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --follow --force

El comando logs usa tu archivo de configuración local de kube para autenticarse con tu clúster, así que para cambiar de clúster simplemente cambia el contexto de tu kube config. También puedes usar el flag --kube-context:

Ventana de terminal
kubetail logs --kube-context minikube deployments/web

Algo que notarás es que para usar --grep también tienes que usar --force. Esto se debe a que el filtrado se realiza en el lado del cliente, lo que significa que la herramienta descargará logs de tu clúster de forma continua hasta que se encuentre el número deseado de coincidencias. Esto podría resultar en descargas inesperadamente grandes, por lo que añadimos una verificación de flag secundaria. Estamos trabajando en una nueva funcionalidad para solucionar este problema.

¡Prueba el nuevo comando logs y cuéntanos qué te parece!