Imágenes listas para producción para k8s

Esta historia trata sobre cómo usamos contenedores en un entorno de producción, específicamente Kubernetes. El artículo está dedicado a recopilar métricas y registros de contenedores, así como a crear imágenes.

Imágenes listas para producción para k8s

Somos de la empresa fintech Exness, que desarrolla servicios para el comercio online y productos fintech para B2B y B2C. Nuestro departamento de I+D cuenta con muchos equipos diferentes y el departamento de desarrollo cuenta con más de 100 empleados.

Representamos al equipo responsable de la plataforma para que nuestros desarrolladores recopilen y ejecuten código. En particular, somos responsables de recopilar, almacenar e informar métricas, registros y eventos de las aplicaciones. Actualmente operamos aproximadamente tres mil contenedores Docker en un entorno de producción, mantenemos nuestro almacenamiento de big data de 50 TB y brindamos soluciones arquitectónicas construidas alrededor de nuestra infraestructura: Kubernetes, Rancher y varios proveedores de nube pública. 

Nuestra motivación

¿Qué está ardiendo? Nadie puede responder. ¿Dónde está el hogar? Es difícil de entender. ¿Cuándo se incendió? Puedes averiguarlo, pero no de inmediato. 

Imágenes listas para producción para k8s

¿Por qué algunos contenedores están en pie y otros caídos? ¿Qué contenedor tuvo la culpa? Al fin y al cabo, el exterior de los contenedores es igual, pero por dentro cada uno tiene su propio Neo.

Imágenes listas para producción para k8s

Nuestros desarrolladores son chicos competentes. Ofrecen buenos servicios que aportan beneficios a la empresa. Pero también se producen fallos cuando los contenedores con aplicaciones se extravían. Un contenedor consume demasiada CPU, otro consume la red, un tercero consume operaciones de E/S y el cuarto no está del todo claro qué hace con los sockets. Todo se cae y el barco se hunde. 

Agentes

Para entender lo que sucede en el interior, decidimos colocar los agentes directamente en contenedores.

Imágenes listas para producción para k8s

Estos agentes son programas restrictivos que mantienen los contenedores en tal estado que no se rompan entre sí. Los agentes están estandarizados y esto permite un enfoque estandarizado para el servicio de contenedores. 

En nuestro caso, los agentes deben proporcionar registros en un formato estándar, etiquetados y limitados. También deberían proporcionarnos métricas estandarizadas que sean extensibles desde la perspectiva de las aplicaciones comerciales.

Los agentes también significan utilidades de operación y mantenimiento que pueden funcionar en diferentes sistemas de orquestación que admiten diferentes imágenes (Debian, Alpine, Centos, etc.).

Finalmente, los agentes deben admitir CI/CD simples que incluyan archivos Docker. De lo contrario, el barco se desmoronará, porque los contenedores comenzarán a entregarse por rieles "torcidos".

Proceso de construcción y dispositivo de imagen de destino

Para mantener todo estandarizado y manejable, se debe seguir algún tipo de proceso de compilación estándar. Por lo tanto, decidimos recolectar contenedores por contenedores; esto es recursividad.

Imágenes listas para producción para k8s

Aquí los contenedores están representados por contornos sólidos. Al mismo tiempo, decidieron ponerles kits de distribución para que “la vida no parezca frambuesas”. Por qué se hizo esto, lo explicaremos a continuación.
 
El resultado es una herramienta de compilación: un contenedor de versión específica que hace referencia a versiones de distribución específicas y versiones de script específicas.

¿Cómo lo usamos? Tenemos un Docker Hub que contiene un contenedor. Lo reflejamos dentro de nuestro sistema para deshacernos de las dependencias externas. El resultado es un contenedor marcado en amarillo. Creamos una plantilla para instalar todas las distribuciones y scripts que necesitemos en el contenedor. Después de eso, ensamblamos una imagen lista para usar: los desarrolladores le agregan código y algunas de sus propias dependencias especiales. 

¿Qué tiene de bueno este enfoque? 

  • En primer lugar, control total de versiones de las herramientas de compilación: versiones de contenedor, script y distribución de compilación. 
  • En segundo lugar, hemos logrado la estandarización: creamos plantillas, imágenes intermedias e imágenes listas para usar de la misma manera. 
  • En tercer lugar, los contenedores nos dan portabilidad. Hoy usamos Gitlab y mañana cambiaremos a TeamCity o Jenkins y podremos ejecutar nuestros contenedores de la misma manera. 
  • Cuarto, minimizar las dependencias. No fue casualidad que pusiéramos kits de distribución en el contenedor, porque esto nos permite evitar descargarlos de Internet cada vez. 
  • En quinto lugar, la velocidad de compilación ha aumentado: la presencia de copias locales de imágenes le permite evitar perder tiempo descargando, ya que hay una imagen local. 

Es decir, hemos conseguido un proceso de montaje controlado y flexible. Usamos las mismas herramientas para construir contenedores con versiones completas. 

Cómo funciona nuestro procedimiento de construcción

Imágenes listas para producción para k8s

El ensamblaje se inicia con un comando, el proceso se ejecuta en la imagen (resaltada en rojo). El desarrollador tiene un archivo Docker (resaltado en amarillo), lo renderizamos reemplazando variables con valores. Y a lo largo del camino agregamos encabezados y pies de página: estos son nuestros agentes. 

El encabezado agrega distribuciones de las imágenes correspondientes. Y el pie de página instala nuestros servicios en el interior, configura el lanzamiento de la carga de trabajo, el registro y otros agentes, reemplaza el punto de entrada, etc. 

Imágenes listas para producción para k8s

Pensamos durante mucho tiempo si instalar un supervisor. Al final decidimos que lo necesitábamos. Elegimos S6. El supervisor proporciona gestión de contenedores: le permite conectarse a él si el proceso principal falla y proporciona gestión manual del contenedor sin volver a crearlo. Los registros y métricas son procesos que se ejecutan dentro del contenedor. También es necesario controlarlos de alguna manera, y lo hacemos con la ayuda de un supervisor. Finalmente, el S6 se encarga de la limpieza, el procesamiento de señales y otras tareas.

Dado que utilizamos diferentes sistemas de orquestación, después de construir y ejecutar, el contenedor debe comprender en qué entorno se encuentra y actuar de acuerdo con la situación. Por ejemplo:
Esto nos permite crear una imagen y ejecutarla en diferentes sistemas de orquestación, y se lanzará teniendo en cuenta las características específicas de este sistema de orquestación.

 Imágenes listas para producción para k8s

Para el mismo contenedor obtenemos diferentes árboles de procesos en Docker y Kubernetes:

Imágenes listas para producción para k8s

La carga útil se ejecuta bajo la supervisión de S6. Preste atención al recopilador y a los eventos: estos son nuestros agentes responsables de los registros y las métricas. Kubernetes no los tiene, pero Docker sí. ¿Por qué? 

Si nos fijamos en la especificación del “pod” (en adelante, pod de Kubernetes), veremos que el contenedor de eventos se ejecuta en un pod, que tiene un contenedor recolector separado que realiza la función de recopilar métricas y registros. Podemos utilizar las capacidades de Kubernetes: ejecutar contenedores en un pod, en un único proceso y/o espacio de red. Presente realmente a sus agentes y realice algunas funciones. Y si el mismo contenedor se lanza en Docker, recibirá las mismas capacidades como salida, es decir, podrá entregar registros y métricas, ya que los agentes se lanzarán internamente. 

Métricas y registros

Entregar métricas y registros es una tarea compleja. Hay varios aspectos de su decisión.
La infraestructura se crea para la ejecución de la carga útil y no para la entrega masiva de registros. Es decir, este proceso debe realizarse con requisitos mínimos de recursos de contenedor. Nos esforzamos por ayudar a nuestros desarrolladores: "Obtenga un contenedor Docker Hub, ejecútelo y podremos entregar los registros". 

El segundo aspecto es limitar el volumen de registros. Si se produce un aumento en el volumen de registros en varios contenedores (la aplicación genera un seguimiento de la pila en un bucle), la carga en la CPU, los canales de comunicación y el sistema de procesamiento de registros aumenta, y esto afecta el funcionamiento del host como enteros y otros contenedores en el host, esto a veces conduce a la "caída" del host. 

El tercer aspecto es que es necesario admitir tantos métodos de recopilación de métricas como sea posible de forma inmediata. Desde leer archivos y sondear el punto final de Prometheus hasta usar protocolos específicos de la aplicación.

Y el último aspecto es minimizar el consumo de recursos.

Elegimos una solución Go de código abierto llamada Telegraf. Este es un conector universal que admite más de 140 tipos de canales de entrada (complementos de entrada) y 30 tipos de canales de salida (complementos de salida). Lo hemos finalizado y ahora os contamos cómo lo utilizamos usando Kubernetes como ejemplo. 

Imágenes listas para producción para k8s

Digamos que un desarrollador implementa una carga de trabajo y Kubernetes recibe una solicitud para crear un pod. En este punto, se crea automáticamente un contenedor llamado Collector para cada pod (usamos un webhook de mutación). El coleccionista es nuestro agente. Al principio, este contenedor se configura para funcionar con Prometheus y el sistema de recopilación de registros.

  • Para hacer esto, utiliza anotaciones de pod y, según su contenido, crea, por ejemplo, un punto final de Prometheus; 
  • Según la especificación del pod y la configuración específica del contenedor, decide cómo entregar los registros.

Recopilamos registros a través de la API de Docker: los desarrolladores solo necesitan colocarlos en stdout o stderr y Collector lo solucionará. Los registros se recopilan en fragmentos con cierto retraso para evitar una posible sobrecarga del host. 

Las métricas se recopilan entre instancias de carga de trabajo (procesos) en contenedores. Todo se etiqueta: espacio de nombres, debajo, etc., y luego se convierte al formato Prometheus y queda disponible para su recopilación (excepto los registros). También enviamos registros, métricas y eventos a Kafka y más:

  • Los registros están disponibles en Graylog (para análisis visual);
  • Los registros, métricas y eventos se envían a Clickhouse para su almacenamiento a largo plazo.

Todo funciona exactamente igual en AWS, solo que reemplazamos Graylog por Kafka por Cloudwatch. Enviamos los registros allí y todo resulta muy cómodo: inmediatamente queda claro a qué clúster y contenedor pertenecen. Lo mismo ocurre con Google Stackdriver. Es decir, nuestro esquema funciona tanto de forma local con Kafka como en la nube. 

Si no tenemos Kubernetes con pods, el esquema es un poco más complicado, pero funciona con los mismos principios.

Imágenes listas para producción para k8s

Los mismos procesos se ejecutan dentro del contenedor y se organizan mediante S6. Todos los mismos procesos se ejecutan dentro del mismo contenedor.

Como resultado,

Hemos creado una solución completa para crear y lanzar imágenes, con opciones para recopilar y entregar registros y métricas:

  • Desarrollamos un enfoque estandarizado para ensamblar imágenes y, en base a él, desarrollamos plantillas de CI;
  • Los agentes de recopilación de datos son nuestras extensiones de Telegraf. Los probamos bien en producción;
  • Usamos webhook de mutación para implementar contenedores con agentes en pods; 
  • Integrado en el ecosistema Kubernetes/Rancher;
  • Podemos ejecutar los mismos contenedores en diferentes sistemas de orquestación y obtener el resultado que esperamos;
  • Creé una configuración de gestión de contenedores completamente dinámica. 

Coautor: Iliá Prudnikov

Fuente: habr.com

Añadir un comentario