GitLab Shell Runner. Lancement concurrentiel de services de test à l'aide de Docker Compose


Cet article intéressera à la fois les testeurs et les développeurs, mais il s'adresse davantage aux automates confrontés au problème de configuration de GitLab CI / CD pour les tests d'intégration dans des conditions de ressources d'infrastructure insuffisantes et / ou de manque de plateforme d'orchestration de conteneurs. Je vais vous expliquer comment configurer le déploiement d'environnements de test à l'aide de Docker Composer sur un seul exécuteur de shell GitLab et afin que lors du déploiement de plusieurs environnements, les services lancés n'interfèrent pas entre eux.



Table des matières




Contexte


  1. Dans ma pratique, il arrivait souvent de «soigner» 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 des services développés sont effectués dans un environnement de développement / étape. Cela a causé pas mal de problèmes:


    • En raison de défauts dans un service particulier lors des tests d'intégration, le circuit de test peut être corrompu par des données cassées. Il y a eu des cas où l'envoi d'une demande avec un format JSON cassé a suspendu un service, ce qui a rendu le stand complètement inopérant.
    • Ralentir la boucle de test avec la croissance des données de test. Je pense que cela n'a aucun sens de décrire un exemple de nettoyage / restauration d'une base de données. Dans ma pratique, je n'ai pas vu de projet où cette procédure s'est bien déroulée.
    • Risque de perturber les performances du circuit de test lors du test des paramètres généraux du système. Par exemple, utilisateur / groupe / mot de passe / stratégie d'application.
    • Les données de test des autotests empêchent les testeurs manuels de vivre.

    Quelqu'un dira que de bons autotests devraient nettoyer les données après eux-mêmes. J'ai des arguments contre:


    • Les supports dynamiques sont très pratiques à utiliser.
    • Tous les objets ne peuvent pas être supprimés du système via l'API. Par exemple, un appel pour supprimer un objet n'est pas implémenté, car il contredit la logique métier.
    • Lors de la création d'un objet via l'API, une énorme quantité de métadonnées peut être créée, ce qui est problématique à supprimer.
    • Si les tests sont interdépendants, le processus de nettoyage des données une fois les tests terminés devient un casse-tête.
    • Appels supplémentaires (et, à mon avis, non justifiés) à l'API.
    • Et l'argument principal: lorsque les données de test commencent à être nettoyées directement de la base de données. Il se transforme en véritable cirque PK / FK! D'après les développeurs, il est audible: "J'ai seulement ajouté / supprimé / renommé la plaque signalétique, pourquoi les tests d'intégration 100500 ont-ils échoué?"

    À mon avis, la solution la plus optimale est un environnement dynamique.


  2. Beaucoup de gens utilisent docker-compose pour exécuter un environnement de test, mais peu utilisent docker-compose lors des tests d'intégration dans CI / CD. Et ici, je ne prends pas en compte les plates-formes kubernetes, swarm et autres conteneurs d'orchestration. Toutes les entreprises n'en ont pas. Ce serait bien si docker-compose.yml était universel.
  3. Même si nous avons notre propre runner QA, comment pouvons-nous nous assurer que les services lancés via docker-compose n'interfèrent pas entre eux?
  4. Comment collecter les logs des services testés?
  5. Comment nettoyer le coureur?

J'ai mon propre runner GitLab pour mes projets et je suis tombé sur ces problèmes lors du développement d'un client Java pour TestRail . Ou plutôt, lors de l'exécution de tests d'intégration. Ci-après, nous allons résoudre ces problèmes avec des exemples de ce projet.


Vers le contenu



Gitlab shell runner


Pour le coureur, je recommande une machine virtuelle Linux avec 4 vCPU, 4 Go de RAM, 50 Go de disque dur.
Sur Internet, beaucoup d'informations sur la configuration de gitlab-runner, donc brièvement:


  • Nous allons à la machine sur SSH
  • Si vous avez moins de 8 Go de RAM, je vous recommande de faire un échange de 10 Go afin que le tueur OOM ne vienne pas et ne nous tue pas par manque de RAM. Cela peut se produire lorsque plus de 5 tâches sont démarrées simultanément. Les tâches seront plus lentes, mais stables.


    Exemple de tueur OOM

    Si vous voyez bash: line 82: 26474 Killed dans les journaux des tâches, exécutez simplement sudo dmesg | grep 26474 sudo 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:0kB 

    Et si l'image ressemble à quelque chose comme ça, alors ajoutez un swap ou supprimez de la RAM.




  • Installez gitlab-runner , docker , docker-compose , make.
  • Ajouter gitlab-runner utilisateur gitlab-runner au groupe de docker
     sudo groupadd docker sudo usermod -aG docker gitlab-runner 
  • Enregistrez gitlab-runner.
  • Ouvrez pour éditer /etc/gitlab-runner/config.toml et ajoutez


     concurrent=20 [[runners]] request_concurrency = 10 

    Cela vous permettra d'exécuter des tâches parallèles sur un seul coureur. Lisez plus ici .
    Si votre machine est plus puissante, par exemple, 8 processeurs virtuels, 16 Go de RAM, ces chiffres peuvent être au moins 2 fois plus importants. Mais tout dépend de ce qui sera lancé exactement sur ce coureur et en quelle quantité.



Ça suffit.


Vers le contenu



Préparation de docker-compose.yml


La tâche principale est docker-compose.yml, qui sera utilisé à la fois localement et dans le pipeline CI.


La variable COMPOSE_PROJECT_NAME sera utilisée pour démarrer plusieurs instances de l'environnement (voir makefile ).


Un exemple de mon docker-compose.yml


 version: "3" #    web (php)  fmt , #      . #   ,   /var/www/testrail volumes: static-content: services: db: image: mysql:5.7.22 environment: MYSQL_HOST: db MYSQL_DATABASE: mydb MYSQL_ROOT_PASSWORD: 1234 SKIP_GRANT_TABLES: 1 SKIP_NETWORKING: 1 SERVICE_TAGS: dev SERVICE_NAME: mysql migration: image: registry.gitlab.com/touchbit/image/testrail/migration:latest links: - db depends_on: - db 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 web: image: registry.gitlab.com/touchbit/image/testrail/web:latest #   TR_HTTP_PORT  TR_HTTPS_PORTS  , #     80  443  . ports: - ${TR_HTTP_PORT:-80}:80 - ${TR_HTTPS_PORT:-443}:443 volumes: - static-content:/var/www/testrail links: - db - fpm 

Vers le contenu



Préparation du Makefile


J'utilise Makefile, car il est très pratique pour la gestion locale de l'environnement et dans CI.


D'autres commentaires sont en ligne


 #           `.indirect`, #     `docker-compose.yml` #  bash   pipefail # pipefail -   ,      SHELL=/bin/bash -o pipefail #   CI_JOB_ID   ifeq ($(CI_JOB_ID),) #   local CI_JOB_ID := local endif #    export COMPOSE_PROJECT_NAME = $(CI_JOB_ID)-testrail #    , , volumes docker-down: docker-compose -f .indirect/docker-compose.yml down #   docker-down () docker-up: docker-down #     docker-registry docker-compose -f .indirect/docker-compose.yml pull #   # force-recreate -    # renew-anon-volumes -   volumes   docker-compose -f .indirect/docker-compose.yml up --force-recreate --renew-anon-volumes -d #  ,   ,           docker ps #    docker-logs: mkdir -p ./logs docker logs $${COMPOSE_PROJECT_NAME}_web_1 >& logs/testrail-web.log || true docker logs $${COMPOSE_PROJECT_NAME}_fpm_1 >& logs/testrail-fpm.log || true docker logs $${COMPOSE_PROJECT_NAME}_migration_1 >& logs/testrail-migration.log || true docker logs $${COMPOSE_PROJECT_NAME}_db_1 >& logs/testrail-mysql.log || true #   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érifier le lancement local
 $ make docker-up docker-compose -f .indirect/docker-compose.yml pull Pulling db ... done Pulling migration ... done Pulling fpm ... done Pulling web ... done docker-compose -f .indirect/docker-compose.yml up --force-recreate --renew-anon-volumes -d Creating network "local-testrail_default" with the default driver Recreating local-testrail_db_1 ... done Recreating local-testrail_migration_1 ... done Recreating local-testrail_fpm_1 ... done Recreating local-testrail_web_1 ... done docker ps CONTAINER ID NAMES 3b8f9d4af29c local-testrail_web_1 5622c7d742d5 local-testrail_fpm_1 b580e3392038 local-testrail_migration_1 e467630bd3a5 local-testrail_db_1 

Vérification du lancement de CI
 $ export CI_JOB_ID=123456789 $ make docker-up docker-compose -f .indirect/docker-compose.yml pull Pulling db ... done Pulling migration ... done Pulling fpm ... done Pulling web ... done docker-compose -f .indirect/docker-compose.yml up --force-recreate --renew-anon-volumes -d Creating network "123456789-testrail_default" with the default driver Creating volume "123456789-testrail_static-content" with default driver Creating 123456789-testrail_db_1 ... done Creating 123456789-testrail_fpm_1 ... done Creating 123456789-testrail_migration_1 ... done Creating 123456789-testrail_web_1 ... done docker ps CONTAINER ID NAMES ccf1ad33d0e8 123456789-testrail_web_1 bc079964f681 123456789-testrail_fpm_1 10dc9d4d8f2a 123456789-testrail_migration_1 fe98d43c380e 123456789-testrail_db_1 

Vérifier la collecte des journaux
 $ make docker-logs mkdir -p ./logs docker logs ${COMPOSE_PROJECT_NAME}_web_1 >& logs/testrail-web.log || true docker logs ${COMPOSE_PROJECT_NAME}_fpm_1 >& logs/testrail-fpm.log || true docker logs ${COMPOSE_PROJECT_NAME}_migration_1 >& logs/testrail-migration.log || true docker logs ${COMPOSE_PROJECT_NAME}_db_1 >& logs/testrail-mysql.log || true 


Vers le contenu



Préparation de .gitlab-ci.yml



Exécutez des tests d'intégration


 Integration: stage: test tags: - my-shell-runner before_script: #   registry - docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY} #   TR_HTTP_PORT  TR_HTTPS_PORT - export TR_HTTP_PORT=$(shuf -i10000-60000 -n1) - export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1) script: #    - make docker-up #    jar (  ) - java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT} #    - docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest after_script: #   - make docker-logs #   - make docker-down artifacts: #   when: always paths: - logs expire_in: 30 days 

À la suite de l'exécution d'une telle tâche dans les artefacts, le répertoire logs contiendra les journaux des services et des tests. Ce qui est très pratique en cas d'erreur. Pour moi, chaque test en parallèle écrit son propre journal, mais je vais en parler séparément.



Vers le contenu



Nettoyage des coureurs


La tâche sera exécutée uniquement dans les délais.


 stages: - clean - build - test Clean runner: stage: clean only: - schedules tags: - my-shell-runner script: - make docker-clean 

Ensuite, accédez à notre projet GitLab -> CI / CD -> Horaires -> Nouveau calendrier et ajoutez un nouveau calendrier



Vers le contenu



Résultat


Exécutez 4 tâches dans GitLab CI


Dans les journaux de la dernière tâche avec des tests d'intégration, nous voyons des conteneurs de différentes tâches


 CONTAINER ID NAMES c6b76f9135ed 204645172-testrail-web_1 01d303262d8e 204645172-testrail-fpm_1 2cdab1edbf6a 204645172-testrail-migration_1 826aaf7c0a29 204645172-testrail-mysql_1 6dbb3fae0322 204645084-testrail-web_1 3540f8d448ce 204645084-testrail-fpm_1 70fea72aa10d 204645084-testrail-mysql_1 d8aa24b2892d 204644881-testrail-web_1 6d4ccd910fad 204644881-testrail-fpm_1 685d8023a3ec 204644881-testrail-mysql_1 1cdfc692003a 204644793-testrail-web_1 6f26dfb2683e 204644793-testrail-fpm_1 029e16b26201 204644793-testrail-mysql_1 c10443222ac6 204567103-testrail-web_1 04339229397e 204567103-testrail-fpm_1 6ae0accab28d 204567103-testrail-mysql_1 b66b60d79e43 204553690-testrail-web_1 033b1f46afa9 204553690-testrail-fpm_1 a8879c5ef941 204553690-testrail-mysql_1 069954ba6010 204553539-testrail-web_1 ed6b17d911a5 204553539-testrail-fpm_1 1a1eed057ea0 204553539-testrail-mysql_1 

Journal plus détaillé
 $ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY} WARNING! Using --password via the CLI is insecure. Use --password-stdin. WARNING! Your password will be stored unencrypted in /home/gitlab-runner/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded $ 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 such network: testrail-network-204645172 docker network create testrail-network-${CI_JOB_ID:-local} 0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331 docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull Pulling web ... done Pulling fpm ... done Pulling migration ... done Pulling db ... done docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d Creating volume "204645172-testrail_static-content" with default driver Creating 204645172-testrail-mysql_1 ... Creating 204645172-testrail-mysql_1 ... done Creating 204645172-testrail-migration_1 ... done Creating 204645172-testrail-fpm_1 ... done Creating 204645172-testrail-web_1 ... done docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 seconds ago Up 1 second 0.0.0.0:51148->80/tcp, 0.0.0.0:25426->443/tcp 204645172-testrail-web_1 01d303262d8e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 16 seconds ago Up 13 seconds 9000/tcp 204645172-testrail-fpm_1 2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 seconds ago Up 13 seconds 3306/tcp, 33060/tcp 204645172-testrail-migration_1 826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 seconds ago Up 16 seconds 3306/tcp 204645172-testrail-mysql_1 6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 36 seconds ago Up 22 seconds 0.0.0.0:44202->80/tcp, 0.0.0.0:20151->443/tcp 204645084-testrail-web_1 3540f8d448ce registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 38 seconds ago Up 35 seconds 9000/tcp 204645084-testrail-fpm_1 70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 seconds ago Up 37 seconds 3306/tcp 204645084-testrail-mysql_1 d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" About a minute ago Up 53 seconds 0.0.0.0:31103->80/tcp, 0.0.0.0:43872->443/tcp 204644881-testrail-web_1 6d4ccd910fad registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" About a minute ago Up About a minute 9000/tcp 204644881-testrail-fpm_1 685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp 204644881-testrail-mysql_1 1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" About a minute ago Up About a minute 0.0.0.0:44752->80/tcp, 0.0.0.0:23540->443/tcp 204644793-testrail-web_1 6f26dfb2683e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" About a minute ago Up About a minute 9000/tcp 204644793-testrail-fpm_1 029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp 204644793-testrail-mysql_1 c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 hours ago Up 5 hours 0.0.0.0:57123->80/tcp, 0.0.0.0:31657->443/tcp 204567103-testrail-web_1 04339229397e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 5 hours ago Up 5 hours 9000/tcp 204567103-testrail-fpm_1 6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp 204567103-testrail-mysql_1 b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 hours ago Up 5 hours 0.0.0.0:56321->80/tcp, 0.0.0.0:58749->443/tcp 204553690-testrail-web_1 033b1f46afa9 registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 5 hours ago Up 5 hours 9000/tcp 204553690-testrail-fpm_1 a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp 204553690-testrail-mysql_1 069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 hours ago Up 5 hours 0.0.0.0:32869->80/tcp, 0.0.0.0:16066->443/tcp 204553539-testrail-web_1 ed6b17d911a5 registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 5 hours ago Up 5 hours 9000/tcp 204553539-testrail-fpm_1 1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp 204553539-testrail-mysql_1 

Toutes les tâches ont été exécutées avec succès

Les artefacts de tâche contiennent des journaux de service et de test



Il semble que tout soit beau, mais il y a une nuance. Le pipeline peut être annulé de force lors de l'exécution des tests d'intégration, auquel cas l'exécution des conteneurs ne sera pas arrêtée. De temps en temps, vous devez nettoyer le coureur. Malheureusement, la tâche de révision dans GitLab CE est toujours en statut ouvert


Mais nous avons ajouté le lancement de tâche planifié, et personne ne nous interdit de le démarrer manuellement.
Accédez à notre projet -> CI / CD -> Horaires et exécutez la tâche Clean runner



Total:


  • Nous avons un coureur d'obus.
  • Il n'y a aucun conflit entre les tâches et l'environnement.
  • Nous avons un lancement parallèle de tâches avec des tests d'intégration.
  • Vous pouvez exécuter des tests d'intégration à la fois localement et dans le conteneur.
  • Les journaux des services et des tests sont collectés et attachés à la tâche de pipeline.
  • Il est possible de nettoyer le coureur des anciennes images docker.

Le temps d'installation est d'environ 2 heures.
En fait, c'est tout. Je serai heureux de vos commentaires.


PS
Un merci spécial au freeseacher vvasilenok ivanych . Vos commentaires ont été très précieux dans le contexte de la publication.


Vers le contenu

Source: https://habr.com/ru/post/fr449910/


All Articles