Probas automatizadas de microservizos en Docker para unha integración continua

Nos proxectos relacionados co desenvolvemento da arquitectura de microservizos, CI/CD pasa da categoría de oportunidade agradable á categoría de necesidade urxente. As probas automatizadas son parte integrante da integración continua, un enfoque competente para o que pode dar ao equipo moitas noites agradables coa familia e os amigos. En caso contrario, o proxecto corre o risco de non rematar nunca.

É posible cubrir todo o código do microservizo con probas unitarias con obxectos simulados, pero isto só resolve parcialmente o problema e deixa moitas preguntas e dificultades, especialmente cando se proba o traballo con datos. Como sempre, os máis urxentes son probar a coherencia dos datos nunha base de datos relacional, probar o traballo con servizos na nube e facer suposicións incorrectas ao escribir obxectos simulados.

Todo isto e un pouco máis pódese solucionar probando todo o microservizo nun contedor Docker. Unha vantaxe indubidable para garantir a validez das probas é que se proban as mesmas imaxes de Docker que entran en produción.

A automatización deste enfoque presenta unha serie de problemas, cuxa solución se describe a continuación:

  • conflitos de tarefas paralelas no mesmo host docker;
  • conflitos de identificadores na base de datos durante as iteracións de proba;
  • á espera de que os microservizos estean listos;
  • fusión e saída de rexistros a sistemas externos;
  • probar as solicitudes HTTP saíntes;
  • proba de socket web (usando SignalR);
  • probando a autenticación e autorización OAuth.

Este artigo baséase en o meu discurso no SECR 2019. Entón, para os que teñen preguiza para ler, aquí tedes unha gravación do discurso.

Probas automatizadas de microservizos en Docker para unha integración continua

Neste artigo explicarei como usar un script para executar o servizo en proba, unha base de datos e os servizos de Amazon AWS en Docker, despois probas en Postman e, unha vez rematadas, deter e eliminar os contedores creados. As probas execútanse cada vez que cambia o código. Deste xeito, asegurámonos de que cada versión funciona correctamente coa base de datos e os servizos de AWS.

O mesmo script é executado tanto polos propios desenvolvedores nos seus escritorios Windows como polo servidor Gitlab CI baixo Linux.

Para ser xustificado, a introdución de novas probas non debería requirir a instalación de ferramentas adicionais nin no ordenador do programador nin no servidor onde se executan as probas nun commit. Docker resolve este problema.

A proba debe executarse nun servidor local polos seguintes motivos:

  • A rede nunca é completamente fiable. Entre mil solicitudes, unha pode fallar;
    Neste caso, a proba automática non funcionará, o traballo pararase e terás que buscar o motivo nos rexistros;
  • Algúns servizos de terceiros non permiten solicitudes demasiado frecuentes.

Ademais, non é desexable utilizar o soporte porque:

  • Un soporte pódese romper non só polo código incorrecto que se executa nel, senón tamén por datos que o código correcto non pode procesar;
  • Por moito que tratemos de revertir todos os cambios realizados pola proba durante a propia proba, algo pode saír mal (se non, por que probar?).

Sobre o proxecto e organización do proceso

A nosa empresa desenvolveu unha aplicación web de microservizos que se executa en Docker na nube de Amazon AWS. As probas unitarias xa se utilizaron no proxecto, pero moitas veces ocorreron erros que as probas unitarias non detectaron. Foi necesario probar todo un microservizo xunto coa base de datos e os servizos de Amazon.

O proxecto utiliza un proceso estándar de integración continua, que inclúe probar o microservizo con cada commit. Despois de asignar unha tarefa, o programador fai cambios no microservizo, probao manualmente e executa todas as probas automatizadas dispoñibles. Se é necesario, o programador cambia as probas. Se non se atopan problemas, realízase un compromiso na rama deste problema. Despois de cada commit, as probas execútanse automaticamente no servidor. A fusión nunha rama común e o lanzamento de probas automáticas nela ocorre despois dunha revisión exitosa. Se as probas na sucursal compartida pasan, o servizo actualízase automaticamente no ambiente de proba en Amazon Elastic Container Service (banco). O soporte é necesario para todos os desenvolvedores e probadores, e non é recomendable rompelo. Os probadores deste ambiente verifican unha corrección ou unha función nova realizando probas manuais.

Arquitectura do proxecto

Probas automatizadas de microservizos en Docker para unha integración continua

A aplicación consta de máis de dez servizos. Algúns deles están escritos en .NET Core e outros en NodeJs. Cada servizo execútase nun contedor Docker no Amazon Elastic Container Service. Cada un ten a súa propia base de datos Postgres, e algúns tamén teñen Redis. Non hai bases de datos comúns. Se varios servizos necesitan os mesmos datos, estes datos, cando cambian, transmítense a cada un destes servizos a través de SNS (Simple Notification Service) e SQS (Amazon Simple Queue Service), e os servizos gárdanos nas súas propias bases de datos separadas.

SQS e SNS

SQS permítelle poñer mensaxes nunha cola e ler mensaxes da cola mediante o protocolo HTTPS.

Se varios servizos len unha cola, entón cada mensaxe chega só a un deles. Isto é útil cando se executan varias instancias do mesmo servizo para distribuír a carga entre elas.

Se queres que cada mensaxe se entregue a varios servizos, cada destinatario debe ter a súa propia cola e o SNS é necesario para duplicar as mensaxes en varias filas.

En SNS creas un tema e subscríbete a el, por exemplo, unha cola SQS. Podes enviar mensaxes ao tema. Neste caso, a mensaxe envíase a cada fila subscrita a este tema. SNS non ten un método para ler mensaxes. Se durante a depuración ou a proba necesitas descubrir o que se envía a SNS, podes crear unha cola SQS, subscribila ao tema desexado e ler a cola.

Probas automatizadas de microservizos en Docker para unha integración continua

Pasarela API

A maioría dos servizos non son accesibles directamente desde Internet. O acceso realízase mediante API Gateway, que comproba os dereitos de acceso. Este tamén é o noso servizo, e tamén hai probas para iso.

Notificacións en tempo real

A aplicación usa Sinal Rpara mostrar notificacións en tempo real ao usuario. Isto implícase no servizo de notificacións. É accesible directamente desde Internet e funciona con OAuth, porque resultou pouco práctico crear compatibilidade con sockets web en Gateway, en comparación coa integración de OAuth e o servizo de notificación.

Enfoque de proba coñecido

As probas unitarias substitúen cousas como a base de datos por obxectos simulados. Se un microservizo, por exemplo, tenta crear un rexistro nunha táboa cunha chave estranxeira e o rexistro ao que fai referencia esa chave non existe, non se pode executar a solicitude. As probas unitarias non poden detectar isto.

В artigo de Microsoft Proponse utilizar unha base de datos en memoria e implementar obxectos simulados.

A base de datos en memoria é un dos DBMS admitidos polo Entity Framework. Foi creado específicamente para probar. Os datos desta base de datos almacénanse só ata que remate o proceso que a utiliza. Non require a creación de táboas e non verifica a integridade dos datos.

Os obxectos simulados modelan a clase que están a substituír só na medida en que o desenvolvedor da proba entende como funciona.

Como facer que Postgres inicie e realice migracións automaticamente cando executa unha proba non se especifica no artigo de Microsoft. A miña solución fai isto e, ademais, non engade ningún código especificamente para probas ao propio microservizo.

Pasemos á solución

Durante o proceso de desenvolvemento, quedou claro que as probas unitarias non eran suficientes para atopar todos os problemas de forma oportuna, polo que se decidiu abordar esta cuestión desde un ángulo diferente.

Configurar un ambiente de proba

A primeira tarefa é implantar un ambiente de proba. Pasos necesarios para executar un microservizo:

  • Configure o servizo en proba para o ambiente local, especifique os detalles para conectarse á base de datos e AWS nas variables de ambiente;
  • Inicie Postgres e realice a migración executando Liquibase.
    Nos DBMS relacionais, antes de escribir datos na base de datos, cómpre crear un esquema de datos, é dicir, táboas. Cando se actualice unha aplicación, as táboas deben levarse ao formulario empregado pola nova versión e, preferentemente, sen perder datos. Isto chámase migración. A creación de táboas nunha base de datos inicialmente baleira é un caso especial de migración. A migración pódese integrar na propia aplicación. Tanto .NET como NodeJS teñen marcos de migración. No noso caso, por razóns de seguridade, os microservizos están privados do dereito a cambiar o esquema de datos e a migración realízase mediante Liquibase.
  • Inicie Amazon LocalStack. Esta é unha implementación dos servizos de AWS para executarse na casa. Hai unha imaxe preparada para LocalStack en Docker Hub.
  • Executa o script para crear as entidades necesarias en LocalStack. Os scripts de shell usan a AWS CLI.

Usado para probar o proxecto Carteiro. Existía antes, pero lanzouse manualmente e probou unha aplicación xa despregada no stand. Esta ferramenta permítelle facer solicitudes HTTP(S) arbitrarias e comprobar se as respostas coinciden coas expectativas. As consultas combínanse nunha colección e pódese executar toda a colección.

Probas automatizadas de microservizos en Docker para unha integración continua

Como funciona a proba automática?

Durante a proba, todo funciona en Docker: o servizo en proba, Postgres, a ferramenta de migración e Postman, ou máis ben a súa versión de consola - Newman.

Docker resolve unha serie de problemas:

  • Independencia da configuración do host;
  • Instalación de dependencias: Docker descarga imaxes desde Docker Hub;
  • Devolver o sistema ao seu estado orixinal: simplemente retirando os contedores.

Docker-compoñer une os contedores nunha rede virtual, illada de Internet, na que os contedores se atopan por nomes de dominio.

A proba está controlada por un script de shell. Para executar a proba en Windows usamos git-bash. Así, un script é suficiente para Windows e Linux. Git e Docker son instalados por todos os desenvolvedores do proxecto. Ao instalar Git en Windows, git-bash está instalado, polo que todos o teñen tamén.

O script realiza os seguintes pasos:

  • Creación de imaxes docker
    docker-compose build
  • Iniciando a base de datos e LocalStack
    docker-compose up -d <контейнер>
  • Migración de bases de datos e preparación de LocalStack
    docker-compose run <контейнер>
  • Lanzamento do servizo en proba
    docker-compose up -d <сервис>
  • Realizando a proba (Newman)
  • Parando todos os contedores
    docker-compose down
  • Publicación de resultados en Slack
    Temos un chat onde van as mensaxes cunha marca de verificación verde ou cruz vermella e unha ligazón ao rexistro.

Nestes pasos están implicadas as seguintes imaxes de Docker:

  • O servizo que se está a probar é a mesma imaxe que para a produción. A configuración para a proba realízase mediante variables de ambiente.
  • Para Postgres, Redis e LocalStack, utilízanse imaxes preparadas de Docker Hub. Tamén hai imaxes preparadas para Liquibase e Newman. Construímos os nosos sobre o seu esqueleto, engadindo alí os nosos arquivos.
  • Para preparar LocalStack, usa unha imaxe AWS CLI preparada e crea unha imaxe que conteña un script baseado nela.

Uso volumes, non tes que crear unha imaxe de Docker só para engadir ficheiros ao contedor. Non obstante, os volumes non son axeitados para o noso contorno porque as tarefas CI de Gitlab se executan en contedores. Podes controlar Docker desde un contedor deste tipo, pero os volumes só montan cartafoles desde o sistema host, e non desde outro contedor.

Problemas que podes atopar

Agardando a preparación

Cando se está a executar un contedor cun servizo, isto non significa que estea preparado para aceptar conexións. Debes esperar a que continúe a conexión.

Este problema ás veces resólvese mediante un script agarda-lo.sh, que espera unha oportunidade para establecer unha conexión TCP. Non obstante, LocalStack pode xerar un erro 502 Bad Gateway. Ademais, consta de moitos servizos, e se un deles está listo, isto non di nada dos outros.

decisión: scripts de aprovisionamento de LocalStack que esperan unha resposta de 200 tanto de SQS como de SNS.

Conflitos de tarefas paralelas

Pódense executar varias probas simultaneamente no mesmo host de Docker, polo que os nomes dos contedores e da rede deben ser únicos. Ademais, as probas de diferentes ramas do mesmo servizo tamén se poden executar simultaneamente, polo que non é suficiente con escribir os seus nomes en cada ficheiro de composición.

decisión: o script establece a variable COMPOSE_PROJECT_NAME nun valor único.

Características de Windows

Hai unha serie de cousas que quero sinalar ao usar Docker en Windows, xa que estas experiencias son importantes para comprender por que se producen os erros.

  1. Os scripts de shell nun contedor deben ter finais de liña Linux.
    O símbolo CR de shell é un erro de sintaxe. É difícil dicir pola mensaxe de erro que este é o caso. Ao editar estes scripts en Windows, necesitas un editor de texto axeitado. Ademais, o sistema de control de versións debe estar configurado correctamente.

Así se configura git:

git config core.autocrlf input

  1. Git-bash emula os cartafoles estándar de Linux e, ao chamar a un ficheiro exe (incluíndo docker.exe), substitúe as rutas absolutas de Linux por rutas de Windows. Non obstante, isto non ten sentido para as rutas que non están na máquina local (ou as rutas nun contedor). Este comportamento non se pode desactivar.

decisión: engade unha barra adicional ao comezo do camiño: //bin no canto de /bin. Linux entende tales camiños; para iso, varias barras inclinadas son o mesmo que unha. Pero git-bash non recoñece tales camiños e non intenta convertelos.

Saída do rexistro

Ao realizar probas, gustaríame ver os rexistros de Newman e do servizo que se está a probar. Dado que os eventos destes rexistros están interconectados, combinalos nunha consola é moito máis conveniente que dous ficheiros separados. Newman lanza vía execución docker-compose, e así a súa saída acaba na consola. Só queda asegurarse de que a saída do servizo tamén vai alí.

A solución orixinal era facer docker-compose up sen bandeira -d, pero usando as capacidades do shell, envía este proceso a un segundo plano:

docker-compose up <service> &

Isto funcionou ata que foi necesario enviar rexistros desde Docker a un servizo de terceiros. docker-compose up deixou de enviar rexistros á consola. Con todo, o equipo traballou anexo docker.

decisión:

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

Conflito de identificador durante as iteracións da proba

As probas realízanse en varias iteracións. Non se borra a base de datos. Os rexistros da base de datos teñen ID únicos. Se anotamos ID específicos nas solicitudes, aparecerá un conflito na segunda iteración.

Para evitalo, ou os ID deben ser únicos ou deben eliminarse todos os obxectos creados pola proba. Algúns obxectos non se poden eliminar debido aos requisitos.

decisión: xera GUID usando scripts Postman.

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

A continuación, use o símbolo na consulta {{myUUID}}, que se substituirá polo valor da variable.

Colaboración a través de LocalStack

Se o servizo que se está a probar le ou escribe nunha cola SQS, para verificalo, a propia proba tamén debe funcionar con esta cola.

decisión: solicitudes de Postman a LocalStack.

A API de servizos de AWS está documentada, o que permite realizar consultas sen un SDK.

Se un servizo escribe nunha cola, lemos e comprobamos o contido da mensaxe.

Se o servizo envía mensaxes a SNS, na fase de preparación LocalStack tamén crea unha cola e subscríbese a este tema de SNS. Entón todo se reduce ao descrito anteriormente.

Se o servizo precisa ler unha mensaxe da cola, no paso de proba anterior escribimos esta mensaxe na cola.

Probando solicitudes HTTP orixinadas do microservizo en proba

Algúns servizos funcionan a través de HTTP con outra cousa que non sexa AWS, e algunhas funcións de AWS non están implementadas en LocalStack.

decisión: nestes casos pode axudar MockServer, que ten unha imaxe preparada en Hub Docker. As solicitudes e respostas esperadas a elas configúranse mediante unha solicitude HTTP. A API está documentada, polo que facemos solicitudes desde Postman.

Probando a autenticación e autorización OAuth

Usamos OAuth e Fichas web JSON (JWT). A proba require un provedor de OAuth que poidamos executar localmente.

Toda interacción entre o servizo e o provedor de OAuth redúcese a dúas solicitudes: primeiro, pídese a configuración /.coñecida/configuración-openid, e despois pídese a chave pública (JWKS) no enderezo da configuración. Todo isto é contido estático.

decisión: O noso provedor de proba de OAuth é un servidor de contido estático e hai dous ficheiros nel. O token xérase unha vez e comprométese a Git.

Características das probas SignalR

Postman non funciona con websockets. Creouse unha ferramenta especial para probar SignalR.

Un cliente SignalR pode ser máis que un simple navegador. Hai unha biblioteca cliente para iso baixo .NET Core. O cliente, escrito en .NET Core, establece unha conexión, autentícase e espera unha secuencia específica de mensaxes. Se se recibe unha mensaxe inesperada ou se perde a conexión, o cliente sae cun código de 1. Se se recibe a última mensaxe esperada, o cliente sae cun código de 0.

Newman traballa simultaneamente co cliente. Lanzan varios clientes para comprobar que as mensaxes se entregan a todos os que as necesitan.

Probas automatizadas de microservizos en Docker para unha integración continua

Para executar varios clientes, use a opción --escala na liña de comandos docker-compose.

Antes de executarse, o script Postman agarda a que todos os clientes establezan conexións.
Xa nos atopamos co problema de agardar unha conexión. Pero había servidores, e aquí está o cliente. É necesario un enfoque diferente.

decisión: o cliente no contedor utiliza o mecanismo Chequeo de saúdepara informar o script do host sobre o seu estado. O cliente crea un ficheiro nun camiño específico, digamos /healthcheck, tan pronto como se establece a conexión. O script HealthCheck no ficheiro docker ten o seguinte aspecto:

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

Equipo inspección do docker Mostra o estado normal, o estado de saúde e o código de saída do contedor.

Despois de que Newman complete, o script comproba que todos os contedores co cliente finalizaron, co código 0.

A felicidade existe

Despois de superar as dificultades descritas anteriormente, tivemos un conxunto de probas de carreira estables. Nas probas, cada servizo funciona como unha única unidade, interactuando coa base de datos e con Amazon LocalStack.

Estas probas protexen a un equipo de máis de 30 desenvolvedores de erros nunha aplicación cunha interacción complexa de máis de 10 microservizos con despregamentos frecuentes.

Fonte: www.habr.com

Engadir un comentario