Imágenes listas para producción para k8s

Esta historia trata sobre cómo usamos contenedores en el entorno de la tienda de comestibles, especialmente bajo Kubernetes. El artículo está dedicado a la recopilación de métricas y registros de contenedores, así como a una compilación de imágenes.

imagen

Somos de la empresa fintech Exness, que desarrolla servicios para el comercio en línea y productos fintech para B2B y B2C. Hay muchos equipos diferentes en nuestra I + D, en el departamento de desarrollo de 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 y proporcionar métricas, registros y eventos de las aplicaciones. Actualmente, operamos alrededor de tres mil contenedores Docker en el entorno del producto, admitimos nuestro almacenamiento de datos grandes de 50 TB y brindamos soluciones arquitectónicas que se construyen alrededor de nuestra infraestructura: Kubernetes, Rancher y varios proveedores de nube pública. 

Nuestra motivación


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



¿Por qué algunos contenedores permanecen en pie mientras que otros se caen? ¿Qué contenedor tenía la culpa? De hecho, fuera de los contenedores son los mismos, pero dentro de cada uno tiene su propio Neo.



Nuestros desarrolladores son chicos alfabetizados. Hacen buenos servicios que hacen que la empresa se beneficie. Pero hay fakapy cuando los contenedores con aplicaciones van al azar. Un contenedor consume demasiada CPU, el otro consume red, el tercero consume operaciones de E / S y el cuarto generalmente no está claro qué hace con los sockets. Todo esto cae y el barco se hunde. 

Agentes


Para entender lo que está sucediendo dentro, decidimos poner agentes directamente en contenedores.



Estos agentes son programas de contención que mantienen los contenedores en un estado tal que no se rompen entre sí. Los agentes están estandarizados, y esto permite un enfoque estandarizado para el manejo de contenedores. 

En nuestro caso, los agentes deben proporcionar registros en un formato estándar, etiquetados y con trote. También deberían proporcionarnos métricas estandarizadas que sean extensibles en términos de aplicaciones comerciales.

Los agentes también se refieren a utilidades para operación y mantenimiento, capaces de trabajar en diferentes sistemas de orquestación, soportando diferentes imágenes (Debian, Alpine, Centos, etc.).

Finalmente, los agentes deben admitir un CI / CD simple que incluya archivos Docker. De lo contrario, el barco se vendrá abajo, porque los contenedores comenzarán a entregarse en rieles "curvos".

Proceso de ensamblaje e imagen del dispositivo de destino


Para que todo sea estandarizado y manejable, debe cumplir con algún proceso de ensamblaje estándar. Por lo tanto, decidimos recolectar contenedores por contenedores, tal recursión.



Aquí los contenedores están representados por contornos sólidos. Al mismo tiempo, decidieron poner distribuciones en ellos para que "la vida no parezca frambuesa". Por qué se hizo esto, lo describiremos a continuación.
 
El resultado es una herramienta de compilación: un contenedor de una determinada versión, que se refiere a ciertas versiones de distribuciones y ciertas versiones de scripts.

¿Cómo lo usamos? Tenemos un Docker Hub en el que se encuentra el contenedor. Lo duplicamos dentro de nuestro sistema para deshacernos de las dependencias externas. El contenedor resultante está marcado en amarillo. Creamos una plantilla para instalar en el contenedor todas las distribuciones y scripts que necesitamos. Después de eso, recopilamos una imagen que está lista para funcionar: los desarrolladores ponen el código y algunas dependencias especiales en él. 

¿Por qué es bueno este enfoque? 

  • En primer lugar, el control completo de la versión de las herramientas de compilación: versiones de contenedores de compilación, scripts y distribuciones. 
  • En segundo lugar, hemos logrado la estandarización: de la misma manera creamos plantillas, imágenes intermedias y listas para operar. 
  • En tercer lugar, los contenedores nos proporcionan portabilidad. Hoy usamos Gitlab, y mañana cambiaremos a TeamCity o Jenkins y de la misma manera podremos lanzar nuestros contenedores. 
  • Cuarto, minimizar las dependencias. No es casualidad que coloquemos distribuciones en el contenedor, ya que esto nos permite no descargarlas cada vez desde Internet. 
  • En quinto lugar, la velocidad de montaje ha aumentado: la disponibilidad de copias locales de imágenes le permite no perder el tiempo descargando, ya que hay una imagen local. 

En otras palabras, hemos logrado un proceso de ensamblaje controlado y flexible. Utilizamos las mismas herramientas para construir cualquier contenedor con versiones completas. 

Cómo funciona nuestro procedimiento de compilación




El ensamblaje se inicia con un comando, el proceso se realiza en la imagen (resaltada en rojo). El desarrollador tiene un archivo Docker (resaltado en amarillo), lo procesamos reemplazando las 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. 



Pensamos durante mucho tiempo si establecer un supervisor. Al final, decidieron que lo necesitábamos. Elige S6. El supervisor proporciona administración de contenedores: le permite conectarse a él en caso de una caída en el proceso principal y proporciona administración manual de contenedores sin volver a crearlo. Los registros y las métricas son procesos que se ejecutan dentro de un contenedor. También necesitan ser controlados de alguna manera, y lo hacemos con la ayuda de un supervisor. Finalmente, el S6 se encarga de la limpieza, el procesamiento de la señal y otras tareas.

Como utilizamos diferentes sistemas de orquestación, después del ensamblaje y el lanzamiento, el contenedor debe comprender en qué entorno se encuentra y actuar en función de la situación. Por ejemplo:
Esto nos permite recopilar una imagen y lanzarla en diferentes sistemas de orquestación, y se lanzará teniendo en cuenta las características específicas de este sistema de orquestación.

 

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



la carga útil se ejecuta bajo el supervisor 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 los tiene. ¿Por qué? 

Si observa la especificación del "hogar" (en adelante, pod Kubernetes), veremos que el contenedor de eventos se ejecuta en el hogar, en el que hay un contenedor de recopilador separado que realiza la función de recopilar métricas y registros. Podemos usar las capacidades de Kubernetes: ejecutar contenedores en un hogar, en un solo proceso y / o espacio de red. Presente realmente a sus agentes y realice algunas funciones. Y si se lanza el mismo contenedor en Docker, recibirá las mismas características en la salida, es decir, podrá entregar registros y métricas, ya que los agentes se lanzarán dentro. 

Métricas y Registros


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

El segundo aspecto es la limitación del volumen de registros. Si en varios contenedores existe una situación de aumento repentino en el volumen de registros (la aplicación muestra el seguimiento de la pila en un bucle), la carga en la CPU, los canales de comunicación, el sistema de procesamiento de registros aumenta, y esto afecta la operación del host como un todo y otros contenedores en el host, a veces esto conduce a "Caída" del anfitrión. 

El tercer aspecto: debe admitir tantos métodos de recopilación de métricas como sea posible. Desde la lectura de archivos y el sondeo de Prometheus-endpoint hasta el uso de protocolos de aplicación específicos.

Y el último aspecto: debe 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 salida (complementos de salida). Lo finalizamos y ahora diremos cómo se usa con Kubernetes como ejemplo. 



Supongamos que un desarrollador despliega una carga y Kubernetes recibe una solicitud para crear un hogar. En este punto, se crea automáticamente un contenedor llamado Collector para cada pod (usamos 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, usa las anotaciones del hogar y, según su contenido, crea, por ejemplo, el punto final del Prometeo; 
  • Según la especificación del hogar y la configuración específica de los contenedores, decide cómo entregar los registros.

Recopilamos registros a través de la API de Docker: es suficiente para que los desarrolladores los pongan en stdout o stderr, y luego Collector lo resolverá. Los registros se recopilan por fragmentos con cierto retraso para evitar la posible congestión del host. 

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

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

Del mismo modo, todo funciona en AWS, solo que estamos reemplazando Graylog de Kafka con Cloudwatch. Enviamos registros allí, y todo resulta muy conveniente: queda claro de inmediato a quién pertenecen el clúster y el contenedor. Lo mismo es cierto para Google Stackdriver. Es decir, nuestro esquema funciona tanto en las instalaciones con Kafka como en la nube. 

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



Los mismos procesos se realizan dentro del contenedor, se orquestan usando S6. Todos los mismos procesos se ejecutan dentro del mismo contenedor.

Finalmente


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

  • Desarrolló un enfoque estandarizado para el ensamblaje de imágenes, basado en el desarrollo de plantillas CI;
  • Los agentes de recopilación de datos son nuestras extensiones de Telegraf. Los ejecutamos bien en producción;
  • Utilizamos webhook de mutación para implementar contenedores con agentes en las cápsulas; 
  • 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 totalmente dinámica. 

Coautor: Ilya Prudnikov

All Articles