paint-brush
Cómo optimizar Kubernetes para imágenes Docker de gran tamañopor@kksudo
190 lecturas

Cómo optimizar Kubernetes para imágenes Docker de gran tamaño

por Kazakov Kirill10m2024/09/30
Read on Terminal Reader

Demasiado Largo; Para Leer

🚀 Magos de Kubernetes, ¿están cansados de esperar horas para que sus nodos se calienten? ¡Imagínense reducir ese tiempo a unos pocos segundos! Este artículo innovador revela cómo potenciar su proceso de implementación de Kubernetes, incluso con imágenes masivas de 3 GB y 1000 pods. Descubra la fórmula secreta que transforma el rendimiento de su clúster de lento a supersónico. No permita que los calentamientos lentos lo detengan: ¡aprenda a revolucionar su flujo de trabajo de Kubernetes hoy mismo!
featured image - Cómo optimizar Kubernetes para imágenes Docker de gran tamaño
Kazakov Kirill HackerNoon profile picture
0-item

Una breve descripción del problema

Un día, durante una actualización planificada del clúster k8s, descubrimos que casi todos nuestros POD (aproximadamente 500 de 1000) en los nuevos nodos no podían iniciarse y los minutos se convirtieron rápidamente en horas. Buscamos activamente la causa raíz, pero después de tres horas, los POD todavía estaban en estado ContainerCreating .


Kubernetes estancado en ContainerCreating

Afortunadamente, no se trataba del entorno de producción y el período de mantenimiento estaba programado para el fin de semana. Tuvimos tiempo para investigar el problema sin ninguna presión.

¿Por dónde debería empezar a buscar la causa raíz? ¿Le gustaría saber más sobre la solución que encontramos? ¡Abróchese el cinturón y disfrute!

Más detalles sobre el problema

El problema era que teníamos una gran cantidad de imágenes de Docker que debían extraerse e iniciarse en cada nodo del clúster al mismo tiempo. Esto se debía a que varias extracciones simultáneas de imágenes de Docker en un solo nodo pueden generar una alta utilización del disco y tiempos de inicio en frío prolongados.


De vez en cuando, el proceso de CD tarda hasta 3 horas en extraer las imágenes. Sin embargo, esta vez se quedó completamente bloqueado, porque la cantidad de PODS durante la actualización de EKS (en línea, cuando reemplazamos todos los nodos del clúster) era demasiado alta.


  • Todas nuestras aplicaciones se ejecutan en K8S (basadas en EKS ). Para ahorrar en costos en el entorno DEV, utilizamos instancias puntuales.

  • Utilizamos la imagen AmazonLinux2 para los nodos.

  • Contamos con una gran cantidad de ramas de funciones (FB) en el entorno de desarrollo que se implementan continuamente en nuestro clúster de Kubernetes. Cada FB tiene su propio conjunto de aplicaciones y cada aplicación tiene su propio conjunto de dependencias (dentro de una imagen).

  • En nuestro proyecto, hay casi 200 aplicaciones y esta cifra va en aumento. Cada aplicación utiliza una de las 7 imágenes base de Docker con un tamaño de aproximadamente 2 GB. El tamaño total máximo de la imagen archivada (en el ECR ) es de aproximadamente 3 GB.

  • Todas las imágenes se almacenan en Amazon Elastic Container Registry (ECR).

  • Utilizamos el tipo de volumen EBS gp3 predeterminado para los nodos.


Problemas a los que nos enfrentamos

  • Tiempo de inicio en frío extendido: iniciar un nuevo pod con una nueva imagen puede llevar más de una hora, en particular cuando se extraen varias imágenes simultáneamente en un solo nodo.

  • Errores de ErrImagePull: ErrImagePull frecuente o atascado en los estados ContainerCreating , lo que indica problemas con la extracción de imágenes.

  • Alta utilización del disco: la utilización del disco se mantiene cerca del 100 % durante el proceso de extracción de imágenes, principalmente debido a la intensa E/S del disco necesaria para la descompresión (por ejemplo, “unpigz”).

  • Problemas con DaemonSet del sistema: algunos DaemonSets del sistema (como aws-node o ebs-csi-node ) pasaron al estado "no listo" debido a la presión del disco, lo que afectó la preparación del nodo.

  • No hay caché de imágenes en los nodos: debido a que utilizamos instancias puntuales, no podemos usar el disco local para almacenar imágenes en caché.


Esto da como resultado muchas implementaciones estancadas en las ramas de características, en particular porque los diferentes FB tienen diferentes conjuntos de imágenes base.

Después de una rápida investigación, descubrimos que el problema principal era la presión del disco sobre los nodos por parte del proceso unpigz . Este proceso es responsable de descomprimir las imágenes de Docker. No cambiamos la configuración predeterminada para el tipo de volumen EBS gp3, porque no es adecuado para nuestro caso.


Revisión rápida para recuperar el clúster

Como primer paso, decidimos reducir la cantidad de POD en los nodos.

  1. Movemos los nuevos nodos al estado “Cordón”
  2. Retire todos los PODS atascados para reducir la presión del disco.
  3. Ejecute uno por uno los POD para calentar los nodos
  4. Después de eso, movemos los nodos calentados al estado normal (“unCordon”)
  5. Se eliminaron todos los nodos en estado bloqueado
  6. Todos los PODS se iniciaron correctamente al usar el caché de imágenes de Docker


Un diseño CI/CD original

La idea principal de la solución es calentar los nodos antes de que comience el proceso de CD mediante la mayor parte de la imagen de Docker (capa de dependencias de JS), que se utiliza como imagen raíz para todas nuestras aplicaciones. Tenemos al menos 7 tipos de imágenes raíz con las dependencias de JS, que están relacionadas con el tipo de aplicación. Por lo tanto, analicemos el diseño CI/CD original.


En nuestro pipeline de CI/CD, tenemos 3 pilares: Una canalización CI/CD original

Una canalización CI/CD original:

  1. En el paso Init it: preparamos el entorno/variables, definimos el conjunto de imágenes a reconstruir, etc...

  2. En el paso Build : construimos las imágenes y las enviamos al ECR

  3. En el paso Deploy : implementamos las imágenes en los k8s (implementaciones de actualizaciones, etc.)


Más detalles sobre el diseño original del CICD:

  • Nuestras ramas de características (FB) se bifurcan de la rama main . En el proceso de integración continua, siempre analizamos el conjunto de imágenes que se modificaron en la FB y las reconstruimos. La rama main siempre es estable, ya que, por definición, siempre debe existir la última versión de las imágenes base.
  • Creamos por separado las imágenes de Docker de las dependencias de JS (para cada entorno) y las enviamos al ECR para reutilizarlas como imagen raíz (base) en el Dockerfile. Tenemos entre 5 y 10 tipos de imágenes de Docker de las dependencias de JS.
  • El FB se implementa en el clúster k8s en un espacio de nombres independiente, pero en los nodos comunes para el FB. El FB puede tener aproximadamente 200 aplicaciones, con un tamaño de imagen de hasta 3 GB.
  • Contamos con el sistema de escalamiento automático de cluster, que escala los nodos del cluster en función de la carga o PODS pendientes con el nodeSelector y la tolerancia correspondientes.
  • Utilizamos las instancias puntuales para los nodos.

Implementación del proceso de calentamiento

Existen requisitos para el proceso de calentamiento.

Obligatorio:

  1. Resolución de problemas : aborda y resuelve problemas ContainerCreating .
  2. Rendimiento mejorado : reduce significativamente el tiempo de inicio al utilizar imágenes base precalentadas (dependencias JS).

Es bueno tener mejoras:

  1. Flexibilidad : permite realizar cambios sencillos en el tipo de nodo y su vida útil (por ejemplo, SLA alto o tiempo de vida útil extendido).
  2. Transparencia : proporciona métricas claras sobre el uso y el rendimiento.
  3. Eficiencia de costos : ahorra costos al eliminar el VNG inmediatamente después de eliminar la rama de funciones asociada.
  4. Aislamiento : este enfoque garantiza que otros entornos no se vean afectados.

Solución

Después de analizar los requisitos y las limitaciones, decidimos implementar un proceso de calentamiento que precalentaría los nodos con las imágenes de caché de JS base. Este proceso se activaría antes de que comience el proceso de CD, lo que garantizaría que los nodos estén listos para la implementación del FB y que tengamos la máxima posibilidad de acceder a la caché.


Esta mejora la dividimos en tres grandes pasos:

  1. Crea el conjunto de nodos (Grupo de nodos virtuales) por cada FB

  2. Agregue imágenes base al script cloud-init para los nuevos nodos

  3. Agregue un paso previo a la implementación para ejecutar DaemonSet con la sección initContainers para descargar las imágenes de Docker necesarias a los nodos antes de que comience el proceso de CD.


Una secuencia de CI/CD actualizada se vería así: Una canalización de CI/CD actualizada


Una secuencia de CI/CD actualizada:

  1. Paso inicial
    1.1.(nuevo paso) Implementación inicial : si se trata de un primer inicio del FB, se crea un nuevo conjunto personal de instancias de nodo (en nuestros términos, es Virtual Node Group o VNG) y se descargan todas las imágenes base de JS (5 a 10 imágenes) de la rama principal. Es justo hacerlo, porque bifurcamos el FB de la rama principal. Un punto importante: no es una operación de bloqueo.
  2. Construir paso
  3. Paso de pre-implementación Descargue imágenes base JS recién horneadas con la etiqueta FB específica del ECR.
    3.1.(nuevo paso) Puntos importantes : Es una operación de bloqueo, ya que debemos reducir la presión del disco. Una a una, descargamos las imágenes base para cada nodo relacionado.
    Por cierto, gracias por el paso “ init deployment” , ya tenemos las imágenes base de Docker de la rama principal, lo que nos da una gran posibilidad de acceder al caché en el primer inicio.
  4. **Desplegar
    **No hay cambios en este paso. Pero gracias al paso anterior, ya tenemos todas las capas de imágenes pesadas de Docker en los nodos necesarios.

Paso de implementación inicial

Cree un nuevo conjunto de nodos para cada FB a través de una llamada API (al sistema de escalado automático de terceros) desde nuestra canalización CI.


Problemas resueltos:

  1. Aislamiento : cada FB tiene su propio conjunto de nodos, lo que garantiza que el entorno no se vea afectado por otros FB.

  2. Flexibilidad : Podemos cambiar fácilmente el tipo de nodo y su vida útil.

  3. Rentabilidad : Podemos eliminar los nodos inmediatamente después de eliminar el FB.

  4. Transparencia : Podemos rastrear fácilmente el uso y el rendimiento de los nodos (cada nodo tiene una etiqueta relacionada con el FB).

  5. Uso efectivo de las instancias puntuales : la instancia puntual comienza con imágenes base ya predefinidas, lo que significa que, después de que se inicia el nodo puntual, ya existen imágenes base en el nodo (desde la rama principal).


Descargue todas las imágenes base de JS de la rama principal a los nuevos nodos a través del script cloud-init .


Mientras se descargan las imágenes en segundo plano, el proceso de CD puede continuar creando nuevas imágenes sin problemas. Además, los siguientes nodos (que se crearán mediante el sistema de escalado automático) de este grupo se crearán con los datos actualizados cloud-init , que ya tienen instrucciones para descargar imágenes antes de comenzar.


Problemas resueltos:

  1. Resolución del problema : la presión del disco desapareció porque actualizamos el script cloud-init agregando la descarga de las imágenes base desde la rama principal. Esto nos permite acceder a la memoria caché en el primer inicio del FB.

  2. Uso eficaz de las instancias de Spot : la instancia de Spot se inicia con datos cloud-init actualizados. Esto significa que, después de que se inicia el nodo de Spot, ya existen las imágenes base en el nodo (de la rama principal).

  3. Rendimiento mejorado : el proceso de CD puede continuar creando nuevas imágenes sin problemas.


Esta acción agregó ~17 segundos (llamada API) a nuestro flujo de trabajo de CI/CD.

Esta acción solo tiene sentido la primera vez que iniciamos el FB. La próxima vez, implementamos nuestras aplicaciones en nodos ya existentes, que ya tienen las imágenes base que entregamos en la implementación anterior.

Paso previo a la implementación

Necesitamos este paso porque las imágenes de FB son diferentes de las imágenes de la rama principal. Necesitamos descargar las imágenes base de FB a los nodos antes de que comience el proceso de CD. Esto ayudará a mitigar los tiempos de inicio en frío prolongados y la alta utilización del disco que pueden ocurrir cuando se extraen varias imágenes pesadas simultáneamente.


Objetivos del paso previo a la implementación

  1. Prevenir la presión del disco : descargar secuencialmente las imágenes más pesadas de Docker. Después del paso de implementación inicial, ya tenemos las imágenes base en los nodos, lo que significa que tenemos una gran posibilidad de acceder a la caché.

  2. Mejore la eficiencia de la implementación : asegúrese de que los nodos estén precalentados con imágenes de Docker esenciales, lo que genera tiempos de inicio de POD más rápidos (casi de inmediato).

  3. Mejorar la estabilidad : minimice las posibilidades de encontrar errores ErrImagePull / ContainerCreating y asegúrese de que los conjuntos de demonios del sistema permanezcan en un estado "listo".


En este paso añadimos entre 10 y 15 minutos al proceso de CD.

Detalles del paso previo a la implementación:

  • En el CD creamos un DaemonSet con la sección initContainers .
  • La sección initContainers se ejecuta antes de que se inicie el contenedor principal, lo que garantiza que las imágenes necesarias se descarguen antes de que se inicie el contenedor principal.
  • En el CD, verificamos continuamente el estado del daemonSet. Si el daemonSet está en estado “listo”, procedemos con la implementación. De lo contrario, esperamos a que el daemonSet esté listo.

Comparación

Comparación de los pasos originales y actualizados con el proceso de precalentamiento.

Paso

Paso de implementación inicial

Paso previo a la implementación

Desplegar

Tiempo total

Diferencia

Sin precalentamiento

0

0

11 minutos 21 segundos

11 minutos 21 segundos

0

Con precalentamiento

8 segundos

58 segundos

25 segundos

1 minuto 31 segundos

-9m 50s


Lo más importante es que el tiempo de “Implementación” cambió (desde el primer comando de aplicación hasta el estado de ejecución de los pods) de 11 min 21 s a 25 segundos. El tiempo total cambió de 11 min 21 s a 1 min 31 s.

Un punto importante, si no hay imágenes base de la rama principal, entonces el tiempo de “Implementación” será el mismo que el tiempo original o un poco más. Pero de todos modos, resolvimos un problema con la presión del disco y el tiempo de arranque en frío.


Conclusión El tiempo de tracción

El problema principal ContainerCreating se resolvió con el proceso de calentamiento. Como beneficio, redujimos significativamente el tiempo de arranque en frío de los POD.
La presión del disco desapareció porque ya tenemos las imágenes base en los nodos. Los conjuntos de demonios del sistema están en un estado "listo" y "en buen estado" (porque no hay presión del disco) y no hemos encontrado ningún error ErrImagePull relacionado con este problema.


Posibles soluciones y enlaces


PD: Me gustaría agradecer al gran equipo técnico de Justt ( https://www.linkedin.com/company/justt-ai ) por su incansable trabajo y su enfoque realmente creativo ante cualquier problema al que se enfrentan. En particular, quiero agradecer a Ronny Sharaby, el excelente líder que es responsable del excelente trabajo que está haciendo el equipo. Espero ver cada vez más ejemplos excelentes de cómo su creatividad impacta en el producto Justt.