Pruebas automatizadas de microservicios en Docker para una integración continua

En proyectos relacionados con el desarrollo de arquitectura de microservicios, CI/CD pasa de la categoría de una oportunidad agradable a la categoría de una necesidad urgente. Las pruebas automatizadas son una parte integral de la integración continua, un enfoque competente que puede brindar al equipo muchas veladas agradables con familiares y amigos. De lo contrario, el proyecto corre el riesgo de no completarse nunca.

Es posible cubrir todo el código del microservicio con pruebas unitarias con objetos simulados, pero esto solo resuelve parcialmente el problema y deja muchas preguntas y dificultades, especialmente cuando se prueba el trabajo con datos. Como siempre, los más urgentes son probar la coherencia de los datos en una base de datos relacional, probar el trabajo con servicios en la nube y hacer suposiciones incorrectas al escribir objetos simulados.

Todo esto y un poco más se puede solucionar probando todo el microservicio en un contenedor Docker. Una ventaja indudable para garantizar la validez de las pruebas es que se prueban las mismas imágenes de Docker que entran en producción.

La automatización de este enfoque presenta una serie de problemas cuya solución se describirá a continuación:

  • conflictos de tareas paralelas en el mismo host acoplable;
  • conflictos de identificadores en la base de datos durante las iteraciones de prueba;
  • esperando que los microservicios estén listos;
  • fusionar y enviar registros a sistemas externos;
  • probar solicitudes HTTP salientes;
  • prueba de socket web (usando SignalR);
  • probar la autenticación y autorización OAuth.

Este artículo está basado en mi discurso en SECR 2019. Entonces, para aquellos que son demasiado vagos para leer, Aquí hay una grabación del discurso..

Pruebas automatizadas de microservicios en Docker para una integración continua

En este artículo, le diré cómo usar un script para ejecutar el servicio bajo prueba, una base de datos y los servicios de Amazon AWS en Docker, luego realizar pruebas en Postman y, una vez completadas, detener y eliminar los contenedores creados. Las pruebas se ejecutan cada vez que cambia el código. De esta manera nos aseguramos de que cada versión funcione correctamente con la base de datos y los servicios de AWS.

Los propios desarrolladores ejecutan el mismo script en sus escritorios de Windows y el servidor Gitlab CI en Linux.

Para estar justificado, introducir nuevas pruebas no debería requerir la instalación de herramientas adicionales ni en la computadora del desarrollador ni en el servidor donde se ejecutan las pruebas en una confirmación. Docker resuelve este problema.

La prueba debe ejecutarse en un servidor local por los siguientes motivos:

  • La red nunca es completamente confiable. De mil solicitudes, una puede fallar;
    En este caso, la prueba automática no funcionará, el trabajo se detendrá y tendrás que buscar el motivo en los registros;
  • Algunos servicios de terceros no permiten solicitudes demasiado frecuentes.

Además, no es deseable utilizar el soporte porque:

  • Un soporte puede dañarse no sólo por un código incorrecto que se ejecuta en él, sino también por datos que el código correcto no puede procesar;
  • No importa cuánto intentemos revertir todos los cambios realizados por la prueba durante la prueba en sí, algo puede salir mal (de lo contrario, ¿por qué realizar la prueba?).

Sobre la organización del proyecto y proceso.

Nuestra empresa desarrolló una aplicación web de microservicio que se ejecuta en Docker en la nube de Amazon AWS. Las pruebas unitarias ya se utilizaban en el proyecto, pero a menudo se producían errores que las pruebas unitarias no detectaban. Fue necesario probar un microservicio completo junto con la base de datos y los servicios de Amazon.

El proyecto utiliza un proceso de integración continua estándar, que incluye probar el microservicio con cada confirmación. Después de asignar una tarea, el desarrollador realiza cambios en el microservicio, lo prueba manualmente y ejecuta todas las pruebas automatizadas disponibles. Si es necesario, el desarrollador cambia las pruebas. Si no se encuentran problemas, se realiza una confirmación en la rama de este problema. Después de cada confirmación, las pruebas se ejecutan automáticamente en el servidor. La fusión en una rama común y el lanzamiento de pruebas automáticas se produce después de una revisión exitosa. Si las pruebas en la sucursal compartida pasan, el servicio se actualiza automáticamente en el entorno de prueba en Amazon Elastic Container Service (banco). El soporte es necesario para todos los desarrolladores y evaluadores, y no es recomendable romperlo. Los evaluadores en este entorno verifican una solución o una nueva característica realizando pruebas manuales.

Arquitectura del proyecto

Pruebas automatizadas de microservicios en Docker para una integración continua

La aplicación consta de más de diez servicios. Algunos de ellos están escritos en .NET Core y otros en NodeJs. Cada servicio se ejecuta en un contenedor Docker en Amazon Elastic Container Service. Cada uno tiene su propia base de datos Postgres y algunos también tienen Redis. No existen bases de datos comunes. Si varios servicios necesitan los mismos datos, estos datos, cuando cambian, se transmiten a cada uno de estos servicios a través de SNS (Simple Notification Service) y SQS (Amazon Simple Queue Service), y los servicios los guardan en sus propias bases de datos separadas.

SQS y SNS

SQS le permite colocar mensajes en una cola y leer mensajes de la cola utilizando el protocolo HTTPS.

Si varios servicios leen una cola, cada mensaje llega solo a uno de ellos. Esto resulta útil cuando se ejecutan varias instancias del mismo servicio para distribuir la carga entre ellas.

Si desea que cada mensaje se entregue a varios servicios, cada destinatario debe tener su propia cola y se necesita SNS para duplicar mensajes en varias colas.

En SNS creas un tema y te suscribes a él, por ejemplo, una cola SQS. Puedes enviar mensajes al tema. En este caso, el mensaje se envía a cada cola suscrita a este tema. SNS no tiene un método para leer mensajes. Si durante la depuración o las pruebas necesita averiguar qué se envía a SNS, puede crear una cola SQS, suscribirse al tema deseado y leer la cola.

Pruebas automatizadas de microservicios en Docker para una integración continua

API Gateway

No se puede acceder directamente a la mayoría de los servicios desde Internet. El acceso se realiza a través de API Gateway, que verifica los derechos de acceso. Este también es nuestro servicio y también existen pruebas para ello.

Notificaciones en tiempo real

La aplicación utiliza SeñalRpara mostrar notificaciones en tiempo real al usuario. Esto se implementa en el servicio de notificación. Es accesible directamente desde Internet y funciona con OAuth, porque resultó poco práctico crear soporte para sockets web en Gateway, en comparación con la integración de OAuth y el servicio de notificación.

Enfoque de prueba conocido

Las pruebas unitarias reemplazan cosas como la base de datos con objetos simulados. Si un microservicio, por ejemplo, intenta crear un registro en una tabla con una clave externa y el registro al que hace referencia esa clave no existe, entonces la solicitud no se puede ejecutar. Las pruebas unitarias no pueden detectar esto.

В artículo de Microsoft Se propone utilizar una base de datos en memoria e implementar objetos simulados.

La base de datos en memoria es uno de los DBMS admitidos por Entity Framework. Fue creado específicamente para pruebas. Los datos en dicha base de datos se almacenan solo hasta que finaliza el proceso que los utiliza. No requiere crear tablas y no verifica la integridad de los datos.

Los objetos simulados modelan la clase que están reemplazando solo en la medida en que el desarrollador de la prueba comprenda cómo funciona.

En el artículo de Microsoft no se especifica cómo hacer que Postgres inicie y realice migraciones automáticamente cuando ejecuta una prueba. Mi solución hace esto y, además, no agrega ningún código específicamente para pruebas al microservicio en sí.

Pasemos a la solución.

Durante el proceso de desarrollo, quedó claro que las pruebas unitarias no eran suficientes para encontrar todos los problemas de manera oportuna, por lo que se decidió abordar este problema desde un ángulo diferente.

Configuración de un entorno de prueba

La primera tarea es implementar un entorno de prueba. Pasos necesarios para ejecutar un microservicio:

  • Configure el servicio bajo prueba para el entorno local, especifique los detalles para conectarse a la base de datos y AWS en las variables de entorno;
  • Inicie Postgres y realice la migración ejecutando Liquibase.
    En los DBMS relacionales, antes de escribir datos en la base de datos, es necesario crear un esquema de datos, en otras palabras, tablas. Al actualizar una aplicación, las tablas deben llevarse al formato utilizado por la nueva versión y, preferiblemente, sin perder datos. A esto se le llama migración. La creación de tablas en una base de datos inicialmente vacía es un caso especial de migración. La migración se puede integrar en la propia aplicación. Tanto .NET como NodeJS tienen marcos de migración. En nuestro caso, por razones de seguridad, los microservicios se ven privados del derecho a cambiar el esquema de datos y la migración se realiza mediante Liquibase.
  • Inicie Amazon LocalStack. Esta es una implementación de los servicios de AWS para ejecutar en casa. Hay una imagen lista para usar para LocalStack en Docker Hub.
  • Ejecute el script para crear las entidades necesarias en LocalStack. Los scripts de Shell utilizan la CLI de AWS.

Utilizado para pruebas en el proyecto. Cartero. Ya existía antes, pero se lanzó manualmente y se probó una aplicación ya desplegada en el stand. Esta herramienta le permite realizar solicitudes HTTP(S) arbitrarias y comprobar si las respuestas coinciden con las expectativas. Las consultas se combinan en una colección y se puede ejecutar toda la colección.

Pruebas automatizadas de microservicios en Docker para una integración continua

¿Cómo funciona la prueba automática?

Durante la prueba, todo funciona en Docker: el servicio bajo prueba, Postgres, la herramienta de migración y Postman, o más bien su versión de consola, Newman.

Docker resuelve una serie de problemas:

  • Independencia de la configuración del host;
  • Instalación de dependencias: Docker descarga imágenes desde Docker Hub;
  • Devolver el sistema a su estado original: simplemente retirando los contenedores.

Docker-componer une contenedores en una red virtual, aislada de Internet, en la que los contenedores se encuentran entre sí por nombres de dominio.

La prueba está controlada por un script de shell. Para ejecutar la prueba en Windows usamos git-bash. Por tanto, un script es suficiente tanto para Windows como para Linux. Todos los desarrolladores del proyecto instalan Git y Docker. Al instalar Git en Windows, se instala git-bash, por lo que todos también lo tienen.

El script realiza los siguientes pasos:

  • Imágenes de la ventana acoplable del edificio
    docker-compose build
  • Lanzando la base de datos y LocalStack
    docker-compose up -d <контейнер>
  • Migración de base de datos y preparación de LocalStack.
    docker-compose run <контейнер>
  • Lanzamiento del servicio bajo prueba
    docker-compose up -d <сервис>
  • Ejecutando la prueba (Newman)
  • Detener todos los contenedores
    docker-compose down
  • Publicar resultados en Slack
    Tenemos un chat donde van los mensajes con un check verde o una cruz roja y un enlace al log.

Las siguientes imágenes de Docker participan en estos pasos:

  • El servicio que se está probando es la misma imagen que para producción. La configuración para la prueba es mediante variables de entorno.
  • Para Postgres, Redis y LocalStack, se utilizan imágenes listas para usar de Docker Hub. También hay imágenes listas para usar para Liquibase y Newman. Construimos el nuestro sobre su esqueleto y agregamos nuestros archivos allí.
  • Para preparar LocalStack, utiliza una imagen de AWS CLI ya preparada y crea una imagen que contiene un script basado en ella.

Uso volúmenes, no es necesario crear una imagen de Docker solo para agregar archivos al contenedor. Sin embargo, los volúmenes no son adecuados para nuestro entorno porque las tareas de Gitlab CI se ejecutan en contenedores. Puede controlar Docker desde dicho contenedor, pero los volúmenes solo montan carpetas desde el sistema host y no desde otro contenedor.

Problemas a afrontar

Esperando la preparación

Cuando un contenedor con un servicio se está ejecutando, esto no significa que esté listo para aceptar conexiones. Debes esperar a que continúe la conexión.

Este problema a veces se resuelve usando un script. espéralo.sh, que espera una oportunidad para establecer una conexión TCP. Sin embargo, LocalStack puede generar un error 502 Bad Gateway. Además, consta de muchos servicios, y si uno de ellos está listo, esto no dice nada sobre los demás.

Solución: scripts de aprovisionamiento de LocalStack que esperan una respuesta 200 tanto de SQS como de SNS.

Conflictos de tareas paralelas

Se pueden ejecutar varias pruebas simultáneamente en el mismo host Docker, por lo que los nombres de los contenedores y de las redes deben ser únicos. Además, las pruebas de diferentes ramas del mismo servicio también se pueden ejecutar simultáneamente, por lo que no basta con escribir sus nombres en cada archivo de redacción.

Solución: El script establece la variable COMPOSE_PROJECT_NAME en un valor único.

Características de Windows

Hay una serie de cosas que quiero señalar cuando uso Docker en Windows, ya que estas experiencias son importantes para comprender por qué ocurren los errores.

  1. Los scripts de Shell en un contenedor deben tener finales de línea de Linux.
    El símbolo CR del shell es un error de sintaxis. Es difícil saber por el mensaje de error que este es el caso. Al editar dichos scripts en Windows, necesita un editor de texto adecuado. Además, el sistema de control de versiones debe estar configurado correctamente.

Así es como se configura git:

git config core.autocrlf input

  1. Git-bash emula carpetas estándar de Linux y, al llamar a un archivo exe (incluido docker.exe), reemplaza las rutas absolutas de Linux con rutas de Windows. Sin embargo, esto no tiene sentido para rutas que no están en la máquina local (o rutas en un contenedor). Este comportamiento no se puede desactivar.

Solución: agregue una barra diagonal adicional al comienzo de la ruta: //bin en lugar de /bin. Linux entiende estos caminos; para él, varias barras diagonales equivalen a una. Pero git-bash no reconoce tales rutas y no intenta convertirlas.

Salida de registro

Al ejecutar pruebas, me gustaría ver registros tanto de Newman como del servicio que se está probando. Dado que los eventos de estos registros están interconectados, combinarlos en una consola es mucho más conveniente que dos archivos separados. Newman se lanza vía ejecución de docker-compose, por lo que su salida termina en la consola. Todo lo que queda es asegurarse de que la salida del servicio también vaya allí.

La solución original era hacer docker-componer sin bandera -d, pero usando las capacidades del shell, envía este proceso a un segundo plano:

docker-compose up <service> &

Esto funcionó hasta que fue necesario enviar registros desde Docker a un servicio de terceros. docker-componer dejó de enviar registros a la consola. Sin embargo, el equipo trabajó acoplar acoplador.

Solución:

docker attach --no-stdin ${COMPOSE_PROJECT_NAME}_<сервис>_1 &

Conflicto de identificador durante iteraciones de prueba

Las pruebas se ejecutan en varias iteraciones. La base de datos no se borra. Los registros de la base de datos tienen ID únicos. Si anotamos ID específicos en las solicitudes, obtendremos un conflicto en la segunda iteración.

Para evitarlo, los ID deben ser únicos o se deben eliminar todos los objetos creados por la prueba. Algunos objetos no se pueden eliminar debido a requisitos.

Solución: genera GUID utilizando scripts Postman.

var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);

Luego use el símbolo en la consulta. {{myUUID}}, que será reemplazado con el valor de la variable.

Colaboración a través de LocalStack

Si el servicio que se está probando lee o escribe en una cola SQS, para verificar esto, la prueba en sí también debe funcionar con esta cola.

Solución: solicitudes de Postman a LocalStack.

La API de servicios de AWS está documentada, lo que permite realizar consultas sin un SDK.

Si un servicio escribe en una cola, lo leemos y verificamos el contenido del mensaje.

Si el servicio envía mensajes a SNS, en la etapa de preparación LocalStack también crea una cola y se suscribe a este tema de SNS. Entonces todo se reduce a lo descrito anteriormente.

Si el servicio necesita leer un mensaje de la cola, en el paso de prueba anterior escribimos este mensaje en la cola.

Prueba de solicitudes HTTP que se originan en el microservicio bajo prueba

Algunos servicios funcionan a través de HTTP con algo que no sea AWS y algunas características de AWS no están implementadas en LocalStack.

Solución: en estos casos puede ayudar Servidor simulado, que tiene una imagen ya preparada en Centro acoplable. Las solicitudes esperadas y las respuestas a ellas se configuran mediante una solicitud HTTP. La API está documentada, por lo que realizamos solicitudes a Postman.

Prueba de autenticación y autorización de OAuth

Usamos OAuth y Tokens web JSON (JWT). La prueba requiere un proveedor de OAuth que podamos ejecutar localmente.

Toda interacción entre el servicio y el proveedor OAuth se reduce a dos solicitudes: primero, se solicita la configuración /.bien conocido/configuración-openid, y luego se solicita la clave pública (JWKS) en la dirección de la configuración. Todo esto es contenido estático.

Solución: Nuestro proveedor de OAuth de prueba es un servidor de contenido estático y dos archivos en él. El token se genera una vez y se envía a Git.

Características de las pruebas de SignalR

Postman no funciona con websockets. Se creó una herramienta especial para probar SignalR.

Un cliente SignalR puede ser más que un simple navegador. Hay una biblioteca cliente para ello en .NET Core. El cliente, escrito en .NET Core, establece una conexión, se autentica y espera una secuencia específica de mensajes. Si se recibe un mensaje inesperado o se pierde la conexión, el cliente sale con un código de 1. Si se recibe el último mensaje esperado, el cliente sale con un código de 0.

Newman trabaja simultáneamente con el cliente. Se lanzan varios clientes para comprobar que los mensajes se entregan a todos los que los necesitan.

Pruebas automatizadas de microservicios en Docker para una integración continua

Para ejecutar varios clientes utilice la opción --escala en la línea de comando de Docker-Compose.

Antes de ejecutarse, el script Postman espera a que todos los clientes establezcan conexiones.
Ya nos hemos encontrado con el problema de esperar una conexión. Pero había servidores y aquí está el cliente. Se necesita un enfoque diferente.

Solución: el cliente en el contenedor usa el mecanismo Chequeo de saludpara informar al script en el host sobre su estado. El cliente crea un archivo en una ruta específica, por ejemplo /healthcheck, tan pronto como se establece la conexión. El script HealthCheck en el archivo acoplable tiene este aspecto:

HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fi

Equipo ventana acoplable inspeccionar Muestra el estado normal, el estado de salud y el código de salida del contenedor.

Una vez que Newman completa, el script verifica que todos los contenedores con el cliente hayan terminado, con el código 0.

la felicidad existe

Después de superar las dificultades descritas anteriormente, tuvimos una serie de pruebas de ejecución estable. En las pruebas, cada servicio funciona como una sola unidad, interactuando con la base de datos y Amazon LocalStack.

Estas pruebas protegen a un equipo de más de 30 desarrolladores de errores en una aplicación con interacción compleja de más de 10 microservicios con implementaciones frecuentes.

Fuente: habr.com

Añadir un comentario