Nube e infraestructura

Cómo maneja el CRI de Kubernetes exec, attach y reenvío de puertos

Un análisis en profundidad de 2024 explica la arquitectura única de streaming basada en URL detrás de los comandos de la Interfaz de Tiempo de Ejecución de Contenedores (CRI) de Kubernetes.

Diagrama que ilustra las rutas separadas de control y datos en el streaming CRI de Kubernetes
Imagen: Kubernetes Blog, con licencia CC BY 4.0

Traducido automáticamente del original en inglés.

En una publicación del Blog de Kubernetes en mayo de 2024, Sascha Grunert detalló los mecanismos internos de tres llamadas a procedimientos remotos (RPC) específicas de la Interfaz de Tiempo de Ejecución de Contenedores (CRI). El artículo explica cómo Exec, Attach y PortForward difieren de las interacciones gRPC estándar al utilizar un modelo distinto de streaming basado en URL que se ha mantenido consistente desde su diseño en 2016.

Qué ocurrió

El CRI de Kubernetes actúa como el puente principal entre el kubelet y los tiempos de ejecución de contenedores, exigiendo que estos últimos expongan un servidor gRPC que cumpla con una interfaz definida mediante Protocol Buffers. Mientras que la mayoría de las operaciones del CRI dependen de llamadas simples unarias o de streaming en el lado del servidor, Grunert destacó que Exec, Attach y PortForward funcionan de manera diferente. Estas tres funciones son críticas para los desarrolladores que necesitan ejecutar comandos dentro de los contenedores, ver la salida en vivo o reenviar puertos de red para depuración.

Grunert rastreó el historial de estas características hasta un documento de diseño de 2016 que precedió a las modernas Propuestas de Mejora de Kubernetes (KEP). Antes de la iniciativa CRI, estas capacidades estaban estrechamente ligadas a tiempos de ejecución específicos como Docker o rkt. La comunidad consideró implementar streaming RPC nativo pero lo rechazó porque crearía cuellos de botella de red en el kubelet y restringiría la flexibilidad del tiempo de ejecución. En su lugar, adoptaron un modelo donde el tiempo de ejecución proporciona un servidor de streaming, permitiendo que cada implementación gestione las conexiones de forma independiente.

Esta decisión arquitectónica significa que, aunque la solicitud inicial pasa por la interfaz gRPC estándar, la transferencia real de datos ocurre a través de una conexión HTTP separada. Esta separación permite a los tiempos de ejecución evolucionar sus implementaciones de streaming sin modificar la definición central del CRI. Aunque se han fusionado mejoras menores a lo largo de los años, el patrón fundamental de solicitar una URL y luego conectarse directamente a ella no ha cambiado.

Cómo funciona

El proceso comienza cuando un cliente, como kubectl o crictl, envía una solicitud gRPC al tiempo de ejecución para una sesión de Exec, Attach o PortForward. A diferencia de las llamadas API típicas que devuelven datos directamente, el tiempo de ejecución valida la solicitud y la almacena en una caché de seguimiento de conexiones. Luego devuelve una respuesta que contiene únicamente una URL completamente calificada. El cliente debe entonces conectarse a esta URL, actualizando la conexión para usar el protocolo SPDY o, cada vez más, WebSockets, para comenzar el streaming de datos.

Figure from the original article: Cómo maneja el CRI de Kubernetes exec, attach y reenvío de puertos
Figura del artículo original · Kubernetes Blog · CC BY 4.0

Para Exec y Attach, Kubernetes define un protocolo específico con cinco versiones, actualmente hasta v5.channel.k8s.io. Este protocolo utiliza el primer byte de cada paquete para identificar el tipo de flujo, como entrada estándar, salida estándar, error estándar o señales de control como el cambio de tamaño de terminal y cierre. El código fuente del kubelet proporciona una biblioteca reutilizable que maneja la interpretación de este protocolo, requiriendo que los tiempos de ejecución solo implementen la lógica para ejecutar comandos o adjuntarse a procesos. PortForward opera de manera diferente, ya que carece de una definición estricta de protocolo. En su lugar, el tiempo de ejecución entra en el espacio de nombres de red del contenedor y transmite tramas SPDY sin procesar, dependiendo de bibliotecas como moby/spdystream para gestionar el flujo de datos.

Detalles clave

  • Las RPCs Exec, Attach y PortForward del CRI devuelven solo una cadena de URL en su respuesta, no el flujo de datos real.
  • Los clientes deben actualizar la conexión HTTP a SPDY o WebSockets para establecer la sesión de streaming después de recibir la URL.
  • Los protocolos Exec y Attach utilizan el primer byte de cada paquete para distinguir entre stdin, stdout, stderr, errores, eventos de cambio de tamaño y señales de cierre.
  • Existen cinco versiones del protocolo de comando remoto, siendo v5 la que añade soporte para una señal CLOSE para WebSockets.
  • El kubelet proporciona una biblioteca reutilizable con una interfaz Runtime que los tiempos de ejecución deben implementar para manejar la ejecución subyacente de comandos y la entrada al espacio de nombres de red.
  • Los esfuerzos futuros se centran en reemplazar SPDY con WebSockets, con herramientas como crictl v1.30 que ya soportan una bandera --transport para elegir entre ellos.

Por qué es importante

Para los ingenieros de software que construyen o mantienen tiempos de ejecución de contenedores, comprender esta arquitectura es esencial para una implementación correcta. El desacoplamiento del plano de control (gRPC) del plano de datos (streaming HTTP) significa que los desarrolladores de tiempos de ejecución no pueden simplemente tratar estas llamadas como solicitudes API estándar. Deben gestionar sesiones de streaming concurrentes, manejar actualizaciones de protocolo y asegurar la compatibilidad con múltiples versiones de protocolo. Interpretar incorrectamente el primer byte de un paquete de datos o no soportar eventos de cambio de tamaño de terminal puede llevar a experiencias de usuario rotas en flujos de trabajo comunes de desarrollo.

Este diseño también impacta cómo las herramientas de depuración interactúan con los clústeres. Dado que los datos evitan el kubelet después de la obtención inicial de la URL, las políticas de red y los proxies deben permitir la comunicación directa entre el cliente y el nodo que aloja el contenedor. A medida que el ecosistema avanza hacia WebSockets, los ingenieros deben asegurarse de que sus herramientas soporten la nueva capa de transporte. El trabajo continuo en proyectos como CRI-O, que mueve la lógica de streaming a conmon-rs, demuestra cómo esta flexibilidad permite a los tiempos de ejecución mantener vivas las sesiones incluso si el proceso principal del tiempo de ejecución se reinicia, mejorando la fiabilidad para sesiones de depuración de larga duración.

Qué puedes hacer

  • Verifica que tu tiempo de ejecución de contenedores soporte el último protocolo v5.channel.k8s.io para garantizar el manejo adecuado de las señales de cierre de flujo.
  • Actualiza las herramientas cliente como crictl a la versión 1.30 o posterior para probar el soporte de transporte WebSocket utilizando la bandera --transport.
  • Revisa tus políticas de red para asegurar que los clientes puedan alcanzar las IPs y puertos del nodo utilizados para las conexiones de streaming, no solo el servidor API.
  • Si estás desarrollando un tiempo de ejecución personalizado, usa la biblioteca de streaming reutilizable del kubelet para manejar el análisis del protocolo en lugar de implementarlo desde cero.
  • Monitorea la adopción de WebSockets en tu entorno, ya que SPDY está siendo eliminado progresivamente en favor de estándares web más modernos.
  • Comprueba si tu tiempo de ejecución puede descargar las sesiones de streaming a monitores externos como conmon-rs para mejorar la resiliencia durante las actualizaciones del tiempo de ejecución.

Herramientas de la Tienda de Bytechap

Seguir leyendo

Todos los artículos