¿Qué sabemos sobre los microservicios?

¡Hola! Mi nombre es Vadim Madison, dirijo el desarrollo de la plataforma del sistema Avito. Se ha dicho más de una vez cómo en la empresa estamos pasando de una arquitectura monolítica a una de microservicios. Es hora de compartir cómo hemos transformado nuestra infraestructura para aprovechar al máximo los microservicios y evitar perdernos en ellos. Cómo nos ayuda PaaS aquí, cómo simplificamos la implementación y reducimos la creación de un microservicio a un solo clic: siga leyendo. No todo lo que escribo a continuación está completamente implementado en Avito, parte de ello es cómo desarrollamos nuestra plataforma.

(Y al final de este artículo, hablaré sobre la oportunidad de asistir a un seminario de tres días impartido por el experto en arquitectura de microservicios Chris Richardson).

¿Qué sabemos sobre los microservicios?

Cómo llegamos a los microservicios

Avito es uno de los sitios de clasificados más grandes del mundo; en él se publican más de 15 millones de anuncios nuevos por día. Nuestro backend acepta más de 20 mil solicitudes por segundo. Actualmente contamos con varios cientos de microservicios.

Llevamos varios años construyendo una arquitectura de microservicios. Cómo exactamente: nuestros colegas en detalle dicho en nuestra sección en RIT++ 2017. En CodeFest 2017 (ver. видео), Sergey Orlov y Mikhail Prokopchuk explicaron en detalle por qué necesitábamos la transición a microservicios y qué papel jugó Kubernetes aquí. Bueno, ahora estamos haciendo todo lo posible para minimizar los costos de escalamiento inherentes a dicha arquitectura.

Inicialmente, no creamos un ecosistema que nos ayudara de manera integral a desarrollar y lanzar microservicios. Simplemente recopilaron soluciones sensatas de código abierto, las lanzaron en casa e invitaron al desarrollador a ocuparse de ellas. Como resultado, fue a una docena de lugares (paneles de control, servicios internos), después de lo cual se fortaleció en su deseo de cortar el código a la antigua, en un monolito. El color verde en los diagramas a continuación indica lo que el desarrollador hace de una forma u otra con sus propias manos, y el color amarillo indica automatización.

¿Qué sabemos sobre los microservicios?

Ahora, en la utilidad CLI de PaaS, se crea un nuevo servicio con un comando y se agrega una nueva base de datos con dos más y se implementa en Stage.

¿Qué sabemos sobre los microservicios?

Cómo superar la era de la "fragmentación de microservicios"

Con una arquitectura monolítica, en aras de la coherencia de los cambios en el producto, los desarrolladores se vieron obligados a descubrir qué estaba pasando con sus vecinos. Cuando se trabaja en la nueva arquitectura, los contextos de servicio ya no dependen unos de otros.

Además, para que una arquitectura de microservicios sea eficaz, es necesario establecer muchos procesos, a saber:

• Inicio sesión;
• solicitar rastreo (Jaeger);
• agregación de errores (Sentry);
• estados, mensajes, eventos de Kubernetes (procesamiento de flujo de eventos);
• límite de carrera/disyuntor (puedes usar Hystrix);
• control de la conectividad del servicio (utilizamos Netramesh);
• seguimiento (Grafana);
• asamblea (TeamCity);
• comunicación y notificación (Slack, correo electrónico);
• seguimiento de tareas; (jira)
• preparación de documentación.

Para garantizar que el sistema no pierda su integridad y siga siendo eficaz a medida que escala, repensamos la organización de los microservicios en Avito.

Cómo gestionamos los microservicios

Lo siguiente ayuda a implementar una "política de partido" unificada entre muchos microservicios de Avito:

  • dividir la infraestructura en capas;
  • Concepto de plataforma como servicio (PaaS);
  • monitorizando todo lo que sucede con los microservicios.

Las capas de abstracción de infraestructura incluyen tres capas. Vayamos de arriba a abajo.

A. Top - malla de servicio. Al principio probamos Istio, pero resultó que utiliza demasiados recursos, lo que resulta demasiado caro para nuestros volúmenes. Por lo tanto, el ingeniero senior del equipo de arquitectura Alexander Lukyanchenko desarrolló su propia solución: netramesh (disponible en código abierto), que utilizamos actualmente en producción y que consume varias veces menos recursos que Istio (pero no hace todo lo que Istio puede presumir).
B. Medio: Kubernetes. Implementamos y operamos microservicios en él.
C. Abajo: metal desnudo. No utilizamos nubes ni cosas como OpenStack, sino que dependemos completamente del metal desnudo.

Todas las capas se combinan mediante PaaS. Y esta plataforma, a su vez, consta de tres partes.

I. Generadores, controlado a través de una utilidad CLI. Es ella quien ayuda al desarrollador a crear un microservicio de la forma correcta y con un mínimo de esfuerzo.

II. Colector consolidado con control de todas las herramientas a través de un panel común.

III. Almacenamiento. Se conecta con programadores que configuran automáticamente desencadenantes de acciones importantes. Gracias a este sistema, no se pierde ni una sola tarea simplemente porque alguien olvidó configurar una tarea en Jira. Para ello utilizamos una herramienta interna llamada Atlas.

¿Qué sabemos sobre los microservicios?

La implementación de microservicios en Avito también se realiza según un esquema único, lo que simplifica el control sobre los mismos en cada etapa de desarrollo y lanzamiento.

¿Cómo funciona un proceso de desarrollo de microservicios estándar?

En general, la cadena de creación de microservicios tiene este aspecto:

CLI-push → Integración continua → Hornear → Implementar → Pruebas artificiales → Pruebas canarias → Pruebas de compresión → Producción → Mantenimiento.

Repasémoslo exactamente en este orden.

Empuje CLI

• Crear un microservicio.
Luchamos durante mucho tiempo para enseñar a todos los desarrolladores cómo realizar microservicios. Esto incluyó escribir instrucciones detalladas en Confluence. Pero los esquemas cambiaron y se complementaron. El resultado es que apareció un cuello de botella al comienzo del viaje: se necesitaba mucho más tiempo para lanzar microservicios y, aun así, a menudo surgían problemas durante su creación.

Al final, creamos una utilidad CLI simple que automatiza los pasos básicos al crear un microservicio. De hecho, reemplaza el primer git push. Esto es exactamente lo que hace.

— Crea un servicio según una plantilla, paso a paso, en modo “asistente”. Disponemos de plantillas para los principales lenguajes de programación en el backend de Avito: PHP, Golang y Python.

- Un comando a la vez implementa un entorno para el desarrollo local en una máquina específica. - Se inicia Minikube, los gráficos de Helm se generan y lanzan automáticamente en kubernetes locales.

— Conecta la base de datos requerida. El desarrollador no necesita conocer la IP, el nombre de usuario y la contraseña para obtener acceso a la base de datos que necesita, ya sea localmente, en Stage o en producción. Además, la base de datos se implementa inmediatamente en una configuración tolerante a fallos y con equilibrio.

— Realiza el montaje en vivo por sí mismo. Digamos que un desarrollador corrigió algo en un microservicio a través de su IDE. La utilidad ve cambios en el sistema de archivos y, en función de ellos, reconstruye la aplicación (para Golang) y se reinicia. Para PHP, simplemente reenviamos el directorio dentro del cubo y allí se obtiene la recarga en vivo "automáticamente".

— Genera autotests. En forma de espacios en blanco, pero bastante adecuados para su uso.

• Implementación de microservicios.

Implementar un microservicio solía ser una tarea ardua para nosotros. Se requería lo siguiente:

I. Dockerfile.

II. Config.
III. Carta de timón, que en sí misma es engorrosa e incluye:

— los propios gráficos;
— plantillas;
— valores específicos teniendo en cuenta diferentes entornos.

Hemos eliminado la molestia de reelaborar los manifiestos de Kubernetes para que ahora se generen automáticamente. Pero lo más importante es que simplificaron el despliegue al límite. A partir de ahora tenemos un Dockerfile y el desarrollador escribe toda la configuración en un único archivo corto app.toml.

¿Qué sabemos sobre los microservicios?

Sí, y en app.toml no hay nada que hacer por un minuto. Especificamos dónde y cuántas copias del servicio generar (en el servidor de desarrollo, en preparación, en producción) e indicamos sus dependencias. Observe el tamaño de línea = "pequeño" en el bloque [motor]. Este es el límite que se asignará al servicio vía Kubernetes.

Luego, según la configuración, se generan automáticamente todos los gráficos de Helm necesarios y se crean conexiones a las bases de datos.

• Validación básica. Estos controles también están automatizados.
Necesidad de realizar un seguimiento:
- ¿Existe un Dockerfile?
- ¿Existe app.toml;
— ¿Hay documentación disponible?
— ¿Está en orden la dependencia?
— si se han establecido reglas de alerta.
Hasta el último punto: el propio propietario del servicio determina qué métricas del producto monitorear.

• Elaboración de documentación.
Sigue siendo un área problemática. Parece lo más obvio, pero al mismo tiempo es también un registro “a menudo olvidado” y, por tanto, un eslabón vulnerable de la cadena.
Es necesario que exista documentación para cada microservicio. Incluye los siguientes bloques.

I. Breve descripción del servicio. Literalmente unas pocas frases sobre lo que hace y por qué es necesario.

II. Enlace al diagrama de arquitectura. Es importante que con un vistazo rápido sea fácil entender, por ejemplo, si está utilizando Redis para el almacenamiento en caché o como almacén de datos principal en modo persistente. En Avito por ahora este es un enlace a Confluence.

III. Libro de ejecución. Una breve guía sobre cómo iniciar el servicio y las complejidades de su manejo.

IV. Preguntas más frecuentes, donde sería bueno anticiparse a los problemas que pueden encontrar sus compañeros al trabajar con el servicio.

V. Descripción de endpoints para la API. Si de repente no especificaste los destinos, es casi seguro que los colegas cuyos microservicios estén relacionados con el tuyo pagarán por ello. Ahora usamos Swagger y nuestra solución llamada breve para esto.

VI. Etiquetas. O marcadores que muestran a qué producto, funcionalidad o división estructural de la empresa pertenece el servicio. Le ayudan a comprender rápidamente, por ejemplo, si está eliminando la funcionalidad que sus colegas implementaron para la misma unidad de negocios hace una semana.

VII. Propietario o propietarios del servicio. En la mayoría de los casos, esto (o ellos) se puede determinar automáticamente usando PaaS, pero para estar seguros, requerimos que el desarrollador los especifique manualmente.

Por último, es una buena práctica revisar la documentación, de forma similar a la revisión del código.

Integración continua

  • Preparando repositorios.
  • Creando una canalización en TeamCity.
  • Establecimiento de derechos.
  • Busque propietarios de servicios. Aquí hay un esquema híbrido: marcado manual y automatización mínima de PaaS. Un esquema completamente automático falla cuando los servicios se transfieren para soporte a otro equipo de desarrollo o, por ejemplo, si el desarrollador del servicio renuncia.
  • Registrar un servicio en Atlas (véase más arriba). Con todos sus dueños y dependencias.
  • Comprobación de migraciones. Comprobamos si alguno de ellos es potencialmente peligroso. Por ejemplo, en uno de ellos aparece una tabla de modificación o algo más que puede romper la compatibilidad del esquema de datos entre diferentes versiones del servicio. Luego no se realiza la migración, sino que se coloca en una suscripción: la PaaS debe indicarle al propietario del servicio cuándo es seguro usarlo.

Hornear

La siguiente etapa es empaquetar los servicios antes de la implementación.

  • Construyendo la aplicación. Según los clásicos, en una imagen de Docker.
  • Generación de gráficos Helm para el propio servicio y recursos relacionados. Incluso para bases de datos y caché. Se crean automáticamente de acuerdo con la configuración de app.toml que se generó en la etapa CLI-push.
  • Crear tickets para que los administradores abran puertos (cuando sea necesario).
  • Ejecutar pruebas unitarias y calcular la cobertura del código.. Si la cobertura del código está por debajo del umbral especificado, lo más probable es que el servicio no avance más: hasta la implementación. Si está al borde de lo aceptable, entonces al servicio se le asignará un coeficiente "pesimizante": luego, si no hay mejora en el indicador a lo largo del tiempo, el desarrollador recibirá una notificación de que no hay progreso en términos de pruebas ( y es necesario hacer algo al respecto).
  • Contabilización de limitaciones de memoria y CPU. Principalmente escribimos microservicios en Golang y los ejecutamos en Kubernetes. De ahí una sutileza asociada con la peculiaridad del lenguaje Golang: de forma predeterminada, al iniciar, se utilizan todos los núcleos de la máquina, si no configura explícitamente la variable GOMAXPROCS, y cuando se inician varios de estos servicios en la misma máquina, comienzan competir por los recursos, interfiriendo entre sí. Los gráficos a continuación muestran cómo cambia el tiempo de ejecución si ejecuta la aplicación sin contención y en modo de carrera por recursos. (Las fuentes de los gráficos son aquí).

¿Qué sabemos sobre los microservicios?

Tiempo de ejecución, menos es mejor. Máximo: 643 ms, mínimo: 42 ms. Se puede hacer clic en la foto.

¿Qué sabemos sobre los microservicios?

Es hora de operarse, menos es mejor. Máximo: 14091 ns, mínimo: 151 ns. Se puede hacer clic en la foto.

En la etapa de preparación del ensamblaje, puede establecer esta variable explícitamente o puede usar la biblioteca automaxprocs de los chicos de Uber.

Desplegar

• Comprobación de convenciones. Antes de comenzar a entregar ensamblajes de servicios a los entornos previstos, debe verificar lo siguiente:
- Puntos finales API.
— Cumplimiento de las respuestas de los puntos finales de API con el esquema.
— Formato de registro.
— Configuración de encabezados para solicitudes al servicio (actualmente esto lo hace netramesh)
— Configuración del token de propietario al enviar mensajes al bus de eventos. Esto es necesario para rastrear la conectividad de los servicios en todo el autobús. Puede enviar al bus tanto datos idempotentes, que no aumentan la conectividad de los servicios (lo cual es bueno), como datos comerciales que fortalecen la conectividad de los servicios (¡lo cual es muy malo!). Y en el momento en que esta conectividad se convierte en un problema, comprender quién escribe y lee el bus ayuda a separar adecuadamente los servicios.

Todavía no hay muchas convenciones en Avito, pero su grupo se está ampliando. Cuantos más acuerdos de este tipo estén disponibles en una forma que el equipo pueda comprender y comprender, más fácil será mantener la coherencia entre los microservicios.

Pruebas sintéticas

• Pruebas en circuito cerrado. Para esto ahora estamos usando código abierto. Hoverfly.io. Primero, registra la carga real del servicio y luego, en un circuito cerrado, la emula.

• Pruebas de estrés. Intentamos llevar todos los servicios a un rendimiento óptimo. Y todas las versiones de cada servicio deben estar sujetas a pruebas de carga; de esta manera podemos comprender el rendimiento actual del servicio y la diferencia con versiones anteriores del mismo servicio. Si después de una actualización del servicio su rendimiento disminuyó una vez y media, esto es una señal clara para sus propietarios: es necesario profundizar en el código y corregir la situación.
Utilizamos los datos recopilados, por ejemplo, para implementar correctamente el auto scaling y, al final, comprender en general qué tan escalable es el servicio.

Durante las pruebas de carga, comprobamos si el consumo de recursos cumple con los límites establecidos. Y nos centramos principalmente en los extremos.

a) Nos fijamos en la carga total.
- Demasiado pequeño: lo más probable es que algo no funcione en absoluto si la carga cae repentinamente varias veces.
- Demasiado grande: se requiere optimización.

b) Nos fijamos en el corte según RPS.
Aquí nos fijamos en la diferencia entre la versión actual y la anterior y la cantidad total. Por ejemplo, si un servicio produce 100 rps, entonces está mal escrito o esta es su especificidad, pero en cualquier caso, esta es una razón para observar el servicio con mucha atención.
Si por el contrario hay demasiados RPS, entonces quizás haya algún tipo de error y alguno de los endpoints haya dejado de ejecutar la carga útil, y algún otro simplemente se active return true;

pruebas canarias

Después de pasar las pruebas sintéticas, probamos el microservicio en una pequeña cantidad de usuarios. Empezamos con cuidado, con una pequeña proporción de la audiencia prevista del servicio: menos del 0,1%. En esta etapa es muy importante que se incluyan en el seguimiento las métricas técnicas y de producto correctas para que muestren el problema en el servicio lo más rápido posible. El tiempo mínimo para una prueba de canario es de 5 minutos, el principal es de 2 horas. Para servicios complejos, configuramos la hora manualmente.
Analizamos:
— métricas específicas del idioma, en particular, trabajadores php-fpm;
— errores en Sentry;
— estados de respuesta;
— tiempo de respuesta, exacto y medio;
- latencia;
— excepciones, procesadas y no manejadas;
- métricas del producto.

Prueba de compresión

La prueba de compresión también se denomina prueba de “expresión”. El nombre de la técnica se introdujo en Netflix. Su esencia es que primero llenamos una instancia con tráfico real hasta el punto de falla y así establecemos su límite. Luego agregamos otra instancia y cargamos este par, nuevamente al máximo; Vemos su techo y delta con el primer "apretón". Y entonces conectamos una instancia a la vez y calculamos el patrón de cambios.
Los datos de prueba mediante "compresión" también fluyen hacia una base de datos de métricas común, donde enriquecemos con ellos los resultados de la carga artificial o incluso reemplazamos los "sintéticos" con ellos.

Producción

• Escalamiento. Cuando implementamos un servicio en producción, monitoreamos cómo escala. Según nuestra experiencia, monitorear únicamente los indicadores de la CPU no es efectivo. El escalado automático con evaluación comparativa de RPS en su forma pura funciona, pero solo para ciertos servicios, como la transmisión en línea. Por eso, primero analizamos las métricas de productos específicas de la aplicación.

Como resultado, al escalar analizamos:
- Indicadores de CPU y RAM,
— el número de solicitudes en la cola,
- tiempo de respuesta,
— pronóstico basado en datos históricos acumulados.

Al escalar un servicio, también es importante monitorear sus dependencias para que no escalemos el primer servicio de la cadena y aquellos a los que accede fallen bajo carga. Para establecer una carga aceptable para todo el conjunto de servicios, analizamos los datos históricos del servicio dependiente "más cercano" (basados ​​en una combinación de indicadores de CPU y RAM, junto con métricas específicas de la aplicación) y los comparamos con los datos históricos. del servicio de inicialización, y así sucesivamente a lo largo de la “cadena de dependencia”, de arriba a abajo.

Servicio

Una vez que el microservicio se pone en funcionamiento, podemos adjuntarle activadores.

A continuación se presentan situaciones típicas en las que se producen desencadenantes.
— Detectadas migraciones potencialmente peligrosas.
— Se han publicado actualizaciones de seguridad.
— El servicio en sí no se actualiza desde hace mucho tiempo.
— La carga del servicio ha disminuido notablemente o algunas de las métricas de sus productos están fuera del rango normal.
— El servicio ya no cumple con los requisitos de la nueva plataforma.

Algunos de los factores desencadenantes son responsables de la estabilidad del funcionamiento, otros, en función del mantenimiento del sistema, por ejemplo, algún servicio no se ha implementado durante mucho tiempo y su imagen base ha dejado de pasar los controles de seguridad.

Tablero de instrumentos

En resumen, el tablero es el panel de control de toda nuestra PaaS.

  • Un único punto de información sobre el servicio, con datos sobre su cobertura de pruebas, número de sus imágenes, número de copias de producción, versiones, etc.
  • Una herramienta para filtrar datos por servicios y etiquetas (marcadores de pertenencia a unidades de negocio, funcionalidad del producto, etc.)
  • Una herramienta para la integración con herramientas de infraestructura para seguimiento, registro y monitoreo.
  • Un único punto de documentación de servicio.
  • Un único punto de vista de todos los eventos en todos los servicios.

¿Qué sabemos sobre los microservicios?
¿Qué sabemos sobre los microservicios?
¿Qué sabemos sobre los microservicios?
¿Qué sabemos sobre los microservicios?

En total

Antes de introducir PaaS, un nuevo desarrollador podría dedicar varias semanas a comprender todas las herramientas necesarias para lanzar un microservicio en producción: Kubernetes, Helm, nuestras funciones internas de TeamCity, configurar conexiones a bases de datos y cachés de forma tolerante a fallos, etc. Se necesitan un par de horas para leer el inicio rápido y crear el servicio en sí.

Di un informe sobre este tema para HighLoad++ 2018, puedes verlo видео и presentación.

Bonus track para aquellos que lean hasta el final

En Avito estamos organizando una formación interna de tres días para desarrolladores de Chris Richardson, experto en arquitectura de microservicios. Nos gustaría darle la oportunidad de participar en él a uno de los lectores de este post. es El programa de formación ha sido publicado.

La formación tendrá lugar del 5 al 7 de agosto en Moscú. Son días laborables que estarán totalmente ocupados. El almuerzo y la capacitación serán en nuestra oficina, y el participante seleccionado pagará él mismo el viaje y el alojamiento.

Puedes solicitar participación en este formulario de google. De su parte: la respuesta a la pregunta de por qué necesita asistir a la capacitación e información sobre cómo contactarlo. Responda en inglés, porque Chris elegirá él mismo al participante que asistirá a la capacitación.
Anunciaremos el nombre del participante de la capacitación en una actualización de esta publicación y en las redes sociales Avito para desarrolladores (AvitoTech en Фейсбуке, VKontakte, Gorjeo) a más tardar el 19 de julio.

Fuente: habr.com

Añadir un comentario