Cet article intéressera tant les testeurs que les développeurs, mais il est principalement destiné aux automatisateurs qui ont rencontré le problème de configuration de GitLab CI/CD pour réaliser des tests d'intégration dans des environnements avec des ressources d'infrastructure insuffisantes et/ou en l'absence d'une plateforme d'orchestration de conteneurs. Je vais expliquer comment configurer le déploiement des environnements de test à l'aide de docker compose sur un seul runner GitLab shell, de manière à ce que les services lancés lors du déploiement de plusieurs environnements ne se gênent pas mutuellement.
Contenu
Prérequis
Dans ma pratique, il m'est souvent arrivé de "réparer" les tests d'intégration sur les projets. Et souvent, le premier et le plus important problème est le pipeline CI dans lequel les tests d'intégration du service(s) en cours de développement est effectué dans un environnement dev/stage. Cela a causé de nombreux problèmes : En raison de défauts dans un service ou un autre, le circuit de test peut être corrompu par des données erronées lors des tests d'intégration. Il y a eu des cas où l'envoi d'une requête avec un format JSON erroné faisait planter le service, rendant l'ensemble du stand totalement non fonctionnel.
- Un ralentissement du circuit de test avec l'augmentation des données de test. Je pense qu'il n'est pas nécessaire de décrire un exemple avec le nettoyage/reversement de la base de données. Dans ma pratique, je n'ai pas rencontré de projet où cette procédure se déroulait sans accroc.
- Le risque de compromettre le bon fonctionnement du circuit de test lors de l'évaluation des configurations communes du système. Par exemple, user/group/password/application policy.
- Les données de test des tests automatisés gênent les testeurs manuels.
- Certains diront que de bons tests automatisés devraient nettoyer les données après leur exécution. J'ai des arguments contre cela :
Les environnements dynamiques sont très pratiques à utiliser.
- Tous les objets ne peuvent pas être supprimés du système via l'API. Par exemple, l'appel pour supprimer un objet n'est pas implémenté, car il contredirait la logique métier.
- Lors de la création d'un objet via l'API, un grand nombre de métadonnées peuvent être générées, ce qui est problématique à supprimer.
- Si les tests ont des dépendances entre eux, le processus de nettoyage des données après l'exécution des tests devient un véritable casse-tête.
- Des appels supplémentaires (et, à mon avis, injustifiés) à l'API.
- Des appels supplémentaires (qui, à mon avis, ne sont pas justifiés) à l'API.
- Et le principal argument : lorsque les données de test commencent à être nettoyées directement à partir de la base de données. Cela se transforme en un véritable cirque PK/FK ! Les développeurs disent : « J'ai juste ajouté/supprimé/renommé une table, pourquoi 100500 tests d'intégration échouent-ils ? »
À mon avis, la solution la plus optimale est un environnement dynamique.
- Beaucoup utilisent docker-compose pour lancer un environnement de test, mais peu l'utilisent lors des tests d'intégration dans CI/CD. Et ici, je ne prends pas en compte Kubernetes, Swarm et d'autres plateformes d'orchestration de conteneurs. Elles ne sont pas présentes dans toutes les entreprises. Ce serait bien que le fichier docker-compose.yml soit universel.
- Même si nous avons notre propre runner QA, comment pouvons-nous faire en sorte que les services lancés via docker-compose ne se gênent pas ?
- Comment collecter les logs des services testés ?
- Comment nettoyer le runner ?
J'ai mon propre GitLab runner pour mes projets et j'ai rencontré ces questions lors du développement. pour . Plus précisément lors de l'exécution des tests d'intégration. C'est là que nous allons résoudre ces questions avec des exemples de ce projet.
GitLab Shell Runner
Pour le runner, je recommande une machine virtuelle Linux avec 4 vCPU, 4 Go de RAM, 50 Go de disque dur.
Il y a beaucoup d'informations sur Internet concernant la configuration de gitlab-runner, donc en bref :
- Connectez-vous à la machine via SSH
Si vous avez moins de 8 Go de RAM, je recommande , pour ne pas avoir de OOM killer qui tue nos tâches à cause d'un manque de RAM. Cela peut arriver lorsque plus de 5 tâches sont lancées simultanément. Les tâches prendront plus de temps, mais fonctionneront de manière stable.
Exemple avec OOM killer
Si dans les logs de la tâche vous voyez
bash: line 82: 26474 Killed, exécutez simplement sur le runnersudo 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:0kBEt si l'image ressemble à cela, alors soit ajoutez du swap, soit augmentez la RAM.
- Nous installons , , , make.
- Ajoutons l'utilisateur
gitlab-runnerau groupedockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Ouvrons pour édition
/etc/gitlab-runner/config.tomlet ajoutonsconcurrent=20 [[runners]] request_concurrency = 10Cela permettra d'exécuter des tâches parallèles sur un seul runner. Lire plus en détail .
Si vous avez une machine plus puissante, par exemple 8 vCPU, 16 Go de RAM, ces chiffres peuvent être multipliés par au moins 2. Tout dépend de ce qui sera exécuté précisément sur ce runner et en quelle quantité.
C'est suffisant.
Préparation de docker-compose.yml
La tâche principale est de créer un fichier docker-compose.yml universel que les développeurs/testeurs peuvent utiliser à la fois localement et dans le pipeline CI.
Tout d'abord, nous donnons des noms uniques aux services pour CI. L'une des variables uniques dans GitLab CI est la variable CI_JOB_ID. Si vous indiquez container_name avec la valeur "service-${CI_JOB_ID:-local}", alors dans le cas où :
- si
CI_JOB_IDn'est pas définie dans les variables d'environnement,
le nom du service seraservice-local - si
CI_JOB_IDdéfinie dans les variables d'environnement (par exemple 123),
le nom du service seraservice-123
Deuxièmement, nous créons un réseau commun pour les services exécutés. Cela nous donne une isolation au niveau du réseau lors de l'exécution de plusieurs environnements de test.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}C'est en fait le premier pas vers le succès =)
Exemple de mon docker-compose.yml avec des commentaires
version: "3"
# Pour le bon fonctionnement de web (php) et fmt, il faut que les conteneurs aient un contenu exécutable commun.
# Dans notre cas, c'est le répertoire /var/www/testrail
volumes:
static-content:
# Isoler l'environnement au niveau du réseau
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Chaque container_name contient ${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 les variables TR_HTTP_PORT ou TR_HTTPS_PORT ne sont pas définies,
# alors le service se lève sur le port 80 et 443 respectivement.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultExemple de lancement local
docker-compose -f docker-compose.yml up -d
Démarrage testrail-mysql-local ... fait
Démarrage testrail-migration-local ... fait
Démarrage testrail-fpm-local ... fait
Recréer testrail-web-local ... faitMais tout n'est pas si simple avec le lancement en CI.
Préparation de Makefile
J'utilise Makefile car c'est très pratique pour la gestion locale de l'environnement ainsi qu'en CI. Ensuite, les commentaires en ligne
# У меня в проектах все вспомогательные вещи лежат в директории `.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
Vérification
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml kill
Killing testrail-web-local ... done
Killing testrail-fpm-local ... done
Killing testrail-mysql-local ... done
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
Pulling db ... done
Pulling migration ... done
Pulling fpm ... done
Pulling web ... done
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Recreating testrail-mysql-local ... done
Recreating testrail-fpm-local ... done
Recreating testrail-migration-local ... done
Recreating testrail-web-local ... done
docker ps
CONTAINER ID PORTS NAMES
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-migration-local
0e7900c23f37 3306/tcp testrail-mysql-local
make docker-logs
$ make docker-logs
mkdir .\/logs || true
mkdir: cannot create directory ‘.\/logs’: File exists
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
Préparation de .gitlab-ci.yml
Lancement des tests d'intégration
Intégration:
étape: test
tags:
- my-shell-runner
before_script:
# Authentification dans le registre
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Génération des ports TR_HTTP_PORT et TR_HTTPS_PORT pseudo-uniques
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# Création d'un répertoire avec l'identifiant de la tâche
- mkdir ${CI_JOB_ID}
# Copie de notre docker-compose.yml dans le répertoire créé
# afin que le contexte soit différent pour chaque tâche
- cp .indirect\/docker-compose.yml ${CI_JOB_ID}\/docker-compose.yml
script:
# Mise en place de notre environnement
- make docker-up
# Exécution des tests avec jar exécutable (c'est comme cela que je fonctionne)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# ou dans le conteneur
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# Récupération des logs
- make docker-logs
# Arrêt de l'environnement
- make docker-kill
artifacts:
# Sauvegarde des logs
when: always
paths:
- logs
expire_in: 30 daysEn conséquence de l'exécution d'une telle tâche, le répertoire logs des artefacts contiendra les logs des services et des tests. Ce qui est très pratique en cas d'erreurs. Chaque test en parallèle écrit son propre log, mais j'en parlerai séparément.

Nettoyage du runner
La tâche ne sera exécutée que selon un calendrier.
stages:
- clean
- build
- test
Nettoyage:
étape: clean
only:
- schedules
tags:
- my-shell-runner
script:
- make docker-cleanEnsuite, allons dans notre projet GitLab -> CI/CD -> Schedules -> New Schedule et ajoutons un nouvel emploi du temps
Résultat
Nous lançons 4 tâches dans GitLab CI
Dans les journaux de la dernière tâche avec les tests d'intégration, nous voyons des conteneurs de différentes tâches
IDENTIFIANT DU CONTENEUR NOMS
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-204553539Journal plus détaillé
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
AVERTISSEMENT ! Utiliser --password via la CLI n'est pas sécurisé. Utilisez --password-stdin.
AVERTISSEMENT ! Votre mot de passe sera stocké en texte clair dans /home/gitlab-runner/.docker/config.json.
Configurez un assistant d'identification pour supprimer cet avertissement. Voir
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Connexion réussie
$ 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
Erreur : Pas de tel réseau : testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Tirage de web ... fait
Tirage de fpm ... fait
Tirage de migration ... fait
Tirage de db ... fait
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Création du volume "204645172_static-content" avec le pilote par défaut
Création de testrail-mysql-204645172 ...
Création de testrail-mysql-204645172 ... terminé
Création de testrail-migration-204645172 ... terminé
Création de testrail-fpm-204645172 ... terminé
Création de testrail-web-204645172 ... terminé
docker ps
ID CONTENEUR IMAGE COMMANDE CRÉÉ ÉTAT PORTS NOMS
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon de…" Il y a 13 secondes En ligne 1 seconde 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…" Il y a 16 secondes En ligne 13 secondes 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" Il y a 16 secondes En ligne 13 secondes 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" Il y a 18 secondes En ligne 16 secondes 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon de…" Il y a 36 secondes En ligne 22 secondes 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…" Il y a 38 secondes En ligne 35 secondes 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" Il y a 40 secondes En ligne 37 secondes 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon de…" Il y a environ une minute En ligne 53 secondes 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…" Il y a environ une minute En ligne environ une minute 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" Il y a environ une minute En ligne environ une minute 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon de…" Il y a environ une minute En ligne environ une minute 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…" Il y a environ une minute En ligne environ une minute 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" Il y a environ une minute En ligne environ une minute 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon de…" Il y a 5 heures En ligne 5 heures 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…" Il y a 5 heures En ligne 5 heures 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" Il y a 5 heures En ligne 5 heures 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon de…" Il y a 5 heures En ligne 5 heures 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…" Il y a 5 heures En ligne 5 heures 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" Il y a 5 heures En ligne 5 heures 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon de…" Il y a 5 heures En ligne 5 heures 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…" Il y a 5 heures En ligne 5 heures 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" Il y a 5 heures En ligne 5 heures 3306/tcp testrail-mysql-204553539Toutes les tâches ont été terminées avec succès
Les artefacts de la tâche contiennent les journaux des services et des tests
Tout semble en ordre, mais il y a un détail. Le pipeline peut être annulé de force pendant l'exécution des tests d'intégration, et dans ce cas, les conteneurs lancés ne seront pas arrêtés. Il est parfois nécessaire de nettoyer le runner. Malheureusement, la tâche de mise à jour dans GitLab CE est toujours en statut.
Mais nous avons ajouté l'exécution de la tâche selon un calendrier, et personne ne nous interdit de la lancer manuellement.
Passons à notre projet -> CI/CD -> Schedules et lançons la tâche Nettoyer le runner
Total :
- Nous avons un runner shell.
- Il n'y a pas de conflits entre les tâches et l'environnement.
- Nous avons un lancement parallèle de tâches avec les tests d'intégration.
- Les tests d'intégration peuvent être lancés à la fois localement et dans un conteneur.
- Les journaux des services et des tests sont collectés et attachés à la tâche du pipeline.
- Il est possible de nettoyer le runner des anciennes images docker.
Le temps de configuration — ~2 heures.
Voilà, c'est à peu près tout. Je serais ravi d'avoir vos retours.
Source : habr.com
