Este artículo será de interés tanto para testers como para desarrolladores, pero está dirigido principalmente a automatizadores que se enfrentan al problema de configurar GitLab CI/CD para realizar pruebas de integración en condiciones de recursos de infraestructura insuficientes y/o falta de una plataforma de orquestación de contenedores. Voy a explicar cómo configurar el despliegue de entornos de prueba utilizando docker compose en un único runner de GitLab shell, de modo que al desplegar varios entornos los servicios en ejecución no interfieran entre sí.
Contenido
Antecedentes
En mi práctica, a menudo he tenido que "arreglar" las pruebas de integración en proyectos. Y a menudo, el primer y más significativo problema es el pipeline de CI, en el que las pruebas de integración del servicio(s) en desarrollo se llevan a cabo en un entorno dev/stage. Esto ha causado varios problemas: La prueba del servicio(s) se realiza en un entorno dev/stage. Esto ha provocado varios problemas:
- Debido a defectos en algún servicio, durante el proceso de pruebas de integración, el circuito de prueba puede verse afectado por datos corruptos. Ha habido casos en los que enviar una solicitud con JSON dañado ha colapsado el servicio, lo que ha llevado a que el entorno esté completamente inoperativo.
- La ralentización del circuito de prueba a medida que aumentan los datos de prueba. Creo que no tiene sentido describir un ejemplo de limpieza/reversión de la base de datos. En mi experiencia, no he encontrado ningún proyecto en el que este procedimiento se haya realizado sin problemas.
- El riesgo de comprometer la funcionalidad del circuito de prueba al probar configuraciones generales del sistema. Por ejemplo, políticas de usuario/grupo/contraseña/aplicación.
- Los datos de prueba de las pruebas automatizadas interfieren con el trabajo de los testers manuales.
Alguien podría decir que buenas pruebas automatizadas deberían limpiar los datos después de sí mismas. Tengo argumentos en contra:
- Los entornos dinámicos son muy convenientes de usar.
- No se puede eliminar cada objeto del sistema a través de la API. Por ejemplo, la llamada para eliminar un objeto no está implementada, ya que contradice la lógica empresarial.
- Al crear un objeto a través de la API, se pueden generar una gran cantidad de metadatos que son problemáticos de eliminar.
- Si las pruebas tienen dependencias entre ellas, el proceso de limpieza de datos después de ejecutar pruebas se convierte en un dolor de cabeza.
- Llamadas adicionales (y, en mi opinión, injustificadas) a la API.
- Y el principal argumento: cuando los datos de prueba comienzan a limpiarse directamente de la base de datos. ¡Se convierte en un verdadero circo de PK/FK! Los desarrolladores dicen: "Solo agregué/eliminé/renombré una tabla, ¿por qué 100500 pruebas de integración fallaron?"
En mi opinión, la solución más óptima es un entorno dinámico.
- Muchos utilizan docker-compose para iniciar un entorno de pruebas, pero son pocos los que lo utilizan en la realización de pruebas de integración en CI/CD. Aquí no estoy considerando Kubernetes, Swarm y otras plataformas de orquestación de contenedores. No todas las empresas las tienen. Sería bueno que docker-compose.yml fuera universal.
- Si incluso tenemos nuestro propio corredor de QA, ¿cómo hacemos para que los servicios iniciados a través de docker-compose no interfieran entre sí?
- ¿Cómo recoger los logs de los servicios que se están probando?
- ¿Cómo limpiar el corredor?
Tengo mi propio corredor de GitLab para mis proyectos y me encontré con estas preguntas durante el desarrollo. para . Más precisamente, al ejecutar pruebas de integración. A continuación, resolveremos estas preguntas con ejemplos de este proyecto.
GitLab Shell Runner
Para el corredor, recomiendo una máquina virtual Linux con 4 vCPU, 4 GB de RAM, 50 GB de HDD.
Hay mucha información en Internet sobre la configuración de gitlab-runner, así que en resumen:
- Accedemos a la máquina por SSH
Si tienes menos de 8 GB de RAM, te recomiendo , para que no llegue el OOM killer y no termine nuestras tareas por falta de RAM. Esto puede ocurrir cuando se inician más de 5 tareas al mismo tiempo. Las tareas tardarán un poco más, pero serán estables.
Ejemplo con el OOM killer
Si en los logs de la tarea ves
bash: line 82: 26474 Killed, simplemente ejecuta en el corredorsudo dmesg | grep 26474[26474] 1002 26474 1061935 123806 339 0 0 java Out of memory: Kill process 26474 (java) score 127 or sacrifice child Killed process 26474 (java) total-vm:4247740kB, anon-rss:495224kB, file-rss:0kB, shmem-rss:0kBY si la imagen se ve aproximadamente así, entonces o agrega swap o añade RAM.
- Instalamos , , , make.
- Agregamos el usuario
gitlab-runneral grupodockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Abrimos para edición
/etc/gitlab-runner/config.tomly añadimosconcurrent=20 [[runners]] request_concurrency = 10Esto permitirá ejecutar tareas en paralelo en un mismo corredor. Leer más detalladamente. .
Si tienes una máquina más potente, por ejemplo, 8 vCPU y 16 GB de RAM, entonces estos números se pueden hacer al menos el doble. Pero todo depende de lo que se ejecute específicamente en este runner y en qué cantidad.
Eso es suficiente.
Preparación de docker-compose.yml
La tarea principal es un docker-compose.yml universal que los desarrolladores/testers pueden usar tanto localmente como en el pipeline de CI.
En primer lugar, creamos nombres únicos para los servicios en CI. Una de las variables únicas en GitLab CI es la variable CI_JOB_ID. Si se especifica container_name con el valor "service-${CI_JOB_ID:-local}", entonces en el caso de:
- si
CI_JOB_IDno definida en las variables de entorno,
el nombre del servicio seráservice-local - si
CI_JOB_IDdefinida en las variables de entorno (por ejemplo, 123),
el nombre del servicio seráservice-123
En segundo lugar, hacemos una red común para los servicios que se lanzan. Esto nos da aislamiento a nivel de red al lanzar múltiples entornos de prueba.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}De hecho, este es el primer paso hacia el éxito =)
Ejemplo de mi docker-compose.yml con comentarios
version: "3"
# Para que web (php) y fmt funcionen correctamente,
# los contenedores deben tener contenido ejecutable común.
# En nuestro caso, es el directorio /var/www/testrail
volumes:
static-content:
# Aislamos el entorno a nivel de red
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Cada container_name contiene ${CI_JOB_ID:-local}
container_name: "testrail-mysql-${CI_JOB_ID:-local}"
environment:
MYSQL_HOST: db
MYSQL_DATABASE: mydb
MYSQL_ROOT_PASSWORD: 1234
SKIP_GRANT_TABLES: 1
SKIP_NETWORKING: 1
SERVICE_TAGS: dev
SERVICE_NAME: mysql
networks:
- default
migration:
image: registry.gitlab.com/touchbit/image/testrail/migration:latest
container_name: "testrail-migration-${CI_JOB_ID:-local}"
links:
- db
depends_on:
- db
networks:
- default
fpm:
image: registry.gitlab.com/touchbit/image/testrail/fpm:latest
container_name: "testrail-fpm-${CI_JOB_ID:-local}"
volumes:
- static-content:/var/www/testrail
links:
- db
networks:
- default
web:
image: registry.gitlab.com/touchbit/image/testrail/web:latest
container_name: "testrail-web-${CI_JOB_ID:-local}"
# Si las variables TR_HTTP_PORT o TR_HTTPS_PORTS no están definidas,
# el servicio se inicia en los puertos 80 y 443 respectivamente.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultEjemplo de ejecución local
docker-compose -f docker-compose.yml up -d
Starting testrail-mysql-local ... done
Starting testrail-migration-local ... done
Starting testrail-fpm-local ... done
Recreating testrail-web-local ... donePero no todo es tan sencillo al ejecutar en CI.
Preparación de Makefile
Utilizo Makefile, ya que es bastante conveniente tanto para la gestión local del entorno como en CI. A continuación, comentarios en línea.
# У меня в проектах все вспомогательные вещи лежат в директории `.indirect`,
# в том числе и `docker-compose.yml`
# Использовать bash с опцией pipefail
# pipefail - фейлит выполнение пайпа, если команда выполнилась с ошибкой
SHELL=/bin/bash -o pipefail
# Останавливаем контейнеры и удаляем сеть
docker-kill:
docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml kill
docker network rm network-$${CI_JOB_ID:-testrail} || true
# Предварительно выполняем docker-kill
docker-up: docker-kill
# Создаем сеть для окружения
docker network create network-$${CI_JOB_ID:-testrail}
# Забираем последние образы из docker-registry
docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml pull
# Запускаем окружение
# force-recreate - принудительное пересоздание контейнеров
# renew-anon-volumes - не использовать volumes предыдущих контейнеров
docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
# Ну и, на всякий случай, вывести что там у нас в принципе запущено на машинке
docker ps
# Коллектим логи сервисов
docker-logs:
mkdir ./logs || true
docker logs testrail-web-$${CI_JOB_ID:-local} >& logs/testrail-web.log
docker logs testrail-fpm-$${CI_JOB_ID:-local} >& logs/testrail-fpm.log
docker logs testrail-migration-$${CI_JOB_ID:-local} >& logs/testrail-migration.log
docker logs testrail-mysql-$${CI_JOB_ID:-local} >& logs/testrail-mysql.log
# Очистка раннера
docker-clean:
@echo Останавливаем все testrail-контейнеры
docker kill $$(docker ps --filter=name=testrail -q) || true
@echo Очистка докер контейнеров
docker rm -f $$(docker ps -a -f --filter=name=testrail status=exited -q) || true
@echo Очистка dangling образов
docker rmi -f $$(docker images -f "dangling=true" -q) || true
@echo Очистка testrail образов
docker rmi -f $$(docker images --filter=reference='registry.gitlab.com/touchbit/image/testrail/*' -q) || true
@echo Очистка всех неиспользуемых volume
docker volume rm -f $$(docker volume ls -q) || true
@echo Очистка всех testrail сетей
docker network rm $(docker network ls --filter=name=testrail -q) || true
docker ps
Verificamos.
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml kill
Matando testrail-web-local ... hecho
Matando testrail-fpm-local ... hecho
Matando testrail-mysql-local ... hecho
docker network rm network-${CI_JOB_ID:-testrail} || true
network-testrail
docker network create network-${CI_JOB_ID:-testrail}
d2ec063324081c8bbc1b08fd92242c2ea59d70cf4025fab8efcbc5c6360f083f
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml pull
Extrayendo db ... hecho
Extrayendo migración ... hecho
Extrayendo fpm ... hecho
Extrayendo web ... hecho
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Recreando testrail-mysql-local ... hecho
Recreando testrail-fpm-local ... hecho
Recreando testrail-migración-local ... hecho
Recreando testrail-web-local ... hecho
docker ps
ID DEL CONTENEDOR PUERTOS NOMBRES
a845d3cb0e5a 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp testrail-web-local
19d8ef001398 9000/tcp testrail-fpm-local
e28840a2369c 3306/tcp, 33060/tcp testrail-migración-local
0e7900c23f37 3306/tcp testrail-mysql-local
make docker-logs
$ make docker-logs
mkdir .\/logs || true
mkdir: no se puede crear el directorio ‘.\/logs’: El archivo ya existe
docker logs testrail-web-${CI_JOB_ID:-local} >& logs\/testrail-web.log
docker logs testrail-fpm-${CI_JOB_ID:-local} >& logs\/testrail-fpm.log
docker logs testrail-migration-${CI_JOB_ID:-local} >& logs\/testrail-migration.log
docker logs testrail-mysql-${CI_JOB_ID:-local} >& logs\/testrail-mysql.log
Preparación de .gitlab-ci.yml
Ejecución de pruebas de integración
Integración:
etapa: prueba
etiquetas:
- my-shell-runner
antes_del_script:
# Autenticarse en el registro
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Generar TR_HTTP_PORT y TR_HTTPS_PORT pseudo-únicos
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# crear un directorio con el identificador de tarea
- mkdir ${CI_JOB_ID}
# copiar nuestro docker-compose.yml al directorio creado
# para que el contexto sea diferente para cada tarea
- cp .indirect\/docker-compose.yml ${CI_JOB_ID}\/docker-compose.yml
script:
# levantamos nuestro entorno
- make docker-up
# ejecutar pruebas con jar (así lo hago yo)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# o en un contenedor
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# recopilamos los logs
- make docker-logs
# detenemos el entorno
- make docker-kill
artefactos:
# guardamos los logs
when: siempre
paths:
- logs
expire_in: 30 díasComo resultado de ejecutar esta tarea, el artefacto directorio logs contendrá los logs de los servicios y pruebas. Lo cual es muy conveniente en caso de errores. Cada prueba que se ejecuta en paralelo escribe su propio log, pero de eso hablaré en otro momento.

Limpieza del runner
La tarea solo se ejecutará según lo programado.
etapas:
- limpiar
- construir
- prueba
Correr limpiador:
etapa: limpiar
solo:
- horarios
etiquetas:
- my-shell-runner
script:
- make docker-cleanA continuación, vamos a nuestro proyecto de GitLab -> CI/CD -> Programaciones -> Nueva programación y agregamos un nuevo horario
Resultado
Iniciamos 4 tareas en GitLab CI
En los logs de la última tarea con las pruebas de integración, vemos contenedores de diferentes tareas
CONTAINER ID NAMES
c6b76f9135ed testrail-web-204645172
01d303262d8e testrail-fpm-204645172
2cdab1edbf6a testrail-migration-204645172
826aaf7c0a29 testrail-mysql-204645172
6dbb3fae0322 testrail-web-204645084
3540f8d448ce testrail-fpm-204645084
70fea72aa10d testrail-mysql-204645084
d8aa24b2892d testrail-web-204644881
6d4ccd910fad testrail-fpm-204644881
685d8023a3ec testrail-mysql-204644881
1cdfc692003a testrail-web-204644793
6f26dfb2683e testrail-fpm-204644793
029e16b26201 testrail-mysql-204644793
c10443222ac6 testrail-web-204567103
04339229397e testrail-fpm-204567103
6ae0accab28d testrail-mysql-204567103
b66b60d79e43 testrail-web-204553690
033b1f46afa9 testrail-fpm-204553690
a8879c5ef941 testrail-mysql-204553690
069954ba6010 testrail-web-204553539
ed6b17d911a5 testrail-fpm-204553539
1a1eed057ea0 testrail-mysql-204553539Registro más detallado
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
¡ADVERTENCIA! Usar --password a través de la CLI no es seguro. Utilice --password-stdin.
¡ADVERTENCIA! Su contraseña se almacenará sin cifrar en /home/gitlab-runner/.docker/config.json.
Configure un asistente de credenciales para eliminar esta advertencia. Ver
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Inicio de sesión exitoso
$ export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
$ export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
$ mkdir ${CI_JOB_ID}
$ cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
docker network rm testrail-network-${CI_JOB_ID:-local} || true
Error: No existe la red: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Descargando web ... hecho
Descargando fpm ... hecho
Descargando migración ... hecho
Descargando db ... hecho
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Creando volumen "204645172_static-content" con el controlador predeterminado
Creando testrail-mysql-204645172 ...
Creando testrail-mysql-204645172 ... hecho
Creando testrail-migration-204645172 ... hecho
Creando testrail-fpm-204645172 ... hecho
Creando testrail-web-204645172 ... hecho
docker ps
ID DEL CONTENEDOR IMAGEN COMANDO CREADO ESTADO PUERTOS NOMBRES
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Hace 13 segundos En funcionamiento 1 segundo 0.0.0.0:51148->80/tcp, 0.0.0.0:25426->443/tcp testrail-web-204645172
01d303262d8e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" Hace 16 segundos En funcionamiento 13 segundos 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" Hace 16 segundos En funcionamiento 13 segundos 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" Hace 18 segundos En funcionamiento 16 segundos 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Hace 36 segundos En funcionamiento 22 segundos 0.0.0.0:44202->80/tcp, 0.0.0.0:20151->443/tcp testrail-web-204645084
3540f8d448ce registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" Hace 38 segundos En funcionamiento 35 segundos 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" Hace 40 segundos En funcionamiento 37 segundos 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Hace aproximadamente un minuto En funcionamiento 53 segundos 0.0.0.0:31103->80/tcp, 0.0.0.0:43872->443/tcp testrail-web-204644881
6d4ccd910fad registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" Hace aproximadamente un minuto En funcionamiento Hace aproximadamente un minuto 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" Hace aproximadamente un minuto En funcionamiento Hace aproximadamente un minuto 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Hace aproximadamente un minuto En funcionamiento Hace aproximadamente un minuto 0.0.0.0:44752->80/tcp, 0.0.0.0:23540->443/tcp testrail-web-204644793
6f26dfb2683e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" Hace aproximadamente un minuto En funcionamiento Hace aproximadamente un minuto 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" Hace aproximadamente un minuto En funcionamiento Hace aproximadamente un minuto 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Hace 5 horas En funcionamiento 5 horas 0.0.0.0:57123->80/tcp, 0.0.0.0:31657->443/tcp testrail-web-204567103
04339229397e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" Hace 5 horas En funcionamiento 5 horas 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" Hace 5 horas En funcionamiento 5 horas 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Hace 5 horas En funcionamiento 5 horas 0.0.0.0:56321->80/tcp, 0.0.0.0:58749->443/tcp testrail-web-204553690
033b1f46afa9 registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" Hace 5 horas En funcionamiento 5 horas 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" Hace 5 horas En funcionamiento 5 horas 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Hace 5 horas En funcionamiento 5 horas 0.0.0.0:32869->80/tcp, 0.0.0.0:16066->443/tcp testrail-web-204553539
ed6b17d911a5 registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" Hace 5 horas En funcionamiento 5 horas 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" Hace 5 horas En funcionamiento 5 horas 3306/tcp testrail-mysql-204553539Todas las tareas se han completado con éxito
Los artefactos de la tarea contienen los registros de servicios y pruebas
Todo parece estar bien, pero hay un matiz. El pipeline puede ser detenido forzosamente durante la ejecución de pruebas de integración, y en ese caso, los contenedores iniciados no se detendrán. De vez en cuando, es necesario limpiar el runner. Desafortunadamente, la tarea de mejora en GitLab CE todavía está en estado
Pero hemos añadido la ejecución de la tarea según un horario, y nadie nos prohíbe ejecutarla manualmente.
Vamos a nuestro proyecto -> CI/CD -> Programaciones y ejecutamos la tarea Limpiar runner
Total:
- Tenemos un shell runner.
- No hay conflictos entre tareas y el entorno.
- Tenemos ejecución paralela de tareas con pruebas de integración.
- Se pueden ejecutar pruebas de integración tanto localmente como en un contenedor.
- Los registros de servicios y pruebas se recopilan y se adjuntan a la tarea del pipeline.
- Hay una opción para limpiar el runner de imágenes de docker antiguas.
El tiempo de configuración es de aproximadamente 2 horas.
Eso es todo. Agradeceré su retroalimentación.
Fuente: habr.com
