GitLab Shell Runner. Wettbewerbsfähiger Start von Testdiensten mit Docker Compose


Dieser Artikel ist sowohl für Tester als auch für Entwickler von Interesse, richtet sich jedoch eher an Automatisten, die mit dem Problem konfrontiert sind, GitLab CI / CD für Integrationstests unter Bedingungen unzureichender Infrastrukturressourcen und / oder fehlender Plattform für die Container-Orchestrierung zu konfigurieren. Ich werde Ihnen erklären, wie Sie die Bereitstellung von Testumgebungen mithilfe von Docker Compose auf einem einzelnen GitLab-Shell-Runner konfigurieren, damit sich die gestarteten Dienste beim Bereitstellen mehrerer Umgebungen nicht gegenseitig stören.



Inhalt




Hintergrund


  1. In meiner Praxis kam es häufig vor, dass Integrationstests an Projekten „geheilt“ wurden. Und häufig ist das erste und wichtigste Problem die CI-Pipeline, in der Integrationstests der entwickelten Dienste in einer Entwicklungsumgebung durchgeführt werden. Dies verursachte einige Probleme:


    • Aufgrund von Fehlern in einem bestimmten Dienst während des Integrationstests kann die Testschaltung durch fehlerhafte Daten beschädigt werden. Es gab Zeiten, in denen das Senden einer Anfrage mit einem defekten JSON-Format den Dienst zum Erliegen brachte, was den Stand völlig auĂźer Betrieb setzte.
    • Verlangsamung der Testschleife mit dem Wachstum der Testdaten. Ich denke, es macht keinen Sinn, ein Beispiel fĂĽr das Bereinigen / ZurĂĽcksetzen einer Datenbank zu beschreiben. In meiner Praxis habe ich kein Projekt gesehen, bei dem dieses Verfahren reibungslos verlief.
    • Das Risiko einer Leistungsstörung der Testschaltung beim Testen allgemeiner Systemeinstellungen. Zum Beispiel Benutzer- / Gruppen- / Kennwort- / Anwendungsrichtlinie.
    • Testdaten von Autotests verhindern, dass manuelle Tester leben.

    Jemand wird sagen, dass gute Autotests die Daten nach sich selbst bereinigen sollten. Ich habe Argumente gegen:


    • Dynamische Ständer sind sehr bequem zu bedienen.
    • Nicht jedes Objekt kann ĂĽber die API aus dem System gelöscht werden. Beispielsweise wird ein Aufruf zum Löschen eines Objekts nicht implementiert, da er der Geschäftslogik widerspricht.
    • Beim Erstellen eines Objekts ĂĽber die API kann eine groĂźe Menge von Metadaten erstellt werden, deren Löschung problematisch ist.
    • Wenn die Tests voneinander abhängig sind, wird der Datenbereinigungsprozess nach Abschluss der Tests zu Kopfschmerzen.
    • Zusätzliche (und meiner Meinung nach nicht gerechtfertigte) Aufrufe der API.
    • Und das Hauptargument: Wenn die Testdaten direkt aus der Datenbank bereinigt werden. Es wird ein echter PK / FK-Zirkus! Von den Entwicklern ist hörbar: "Ich habe nur das Typenschild hinzugefĂĽgt / gelöscht / umbenannt. Warum sind 100500 Integrationstests fehlgeschlagen?"

    Meiner Meinung nach ist eine dynamische Umgebung die optimalste Lösung.


  2. Viele Benutzer verwenden Docker-Compose, um eine Testumgebung auszuführen, aber nur wenige verwenden Docker-Compose, um Integrationstests in CI / CD durchzuführen. Und hier berücksichtige ich keine Kubernetes, Schwärme und andere Container-Orchestrierungsplattformen. Nicht jedes Unternehmen hat sie. Es wäre schön, wenn docker-compose.yml universell wäre.
  3. Selbst wenn wir einen eigenen QS-Runner haben, wie können wir sicherstellen, dass sich die über Docker-Compose gestarteten Dienste nicht gegenseitig stören?
  4. Wie sammle ich Protokolle der getesteten Dienste?
  5. Wie reinige ich den Läufer?

Ich habe meinen eigenen GitLab-Runner für meine Projekte und bin bei der Entwicklung eines Java-Clients für TestRail auf diese Probleme gestoßen . Oder besser gesagt, wenn Integrationstests ausgeführt werden. Im Folgenden werden wir diese Probleme anhand von Beispielen aus diesem Projekt lösen.


Zum Inhalt



Gitlab Shell Runner


Für den Läufer empfehle ich eine virtuelle Linux-Maschine mit 4 vCPU, 4 GB RAM und 50 GB Festplatte.
Im Internet viele Informationen zum Einrichten von Gitlab-Runner, also kurz:


  • Wir gehen auf SSH zur Maschine
  • Wenn Sie weniger als 8 GB RAM haben, empfehle ich einen Austausch von 10 GB, damit der OOM-Killer nicht kommt und uns wegen fehlenden RAM nicht tötet. Dies kann passieren, wenn mehr als 5 Aufgaben gleichzeitig gestartet werden. Aufgaben werden langsamer, aber stabil.


    OOM Killer Beispiel

    Wenn in den Aufgabenprotokollen bash: line 82: 26474 Killed , fĂĽhren Sie einfach 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 

    Und wenn das Bild ungefähr so ​​aussieht, fügen Sie entweder Swap hinzu oder lassen Sie RAM fallen.




  • Installieren Sie Gitlab-Runner , Docker , Docker-Compose , Make.
  • FĂĽgen gitlab-runner der docker Gruppe den Benutzer gitlab-runner
     sudo groupadd docker sudo usermod -aG docker gitlab-runner 
  • Registrieren Sie Gitlab-Runner.
  • Zum Bearbeiten von /etc/gitlab-runner/config.toml und hinzufĂĽgen


     concurrent=20 [[runners]] request_concurrency = 10 

    Auf diese Weise können Sie parallele Aufgaben auf einem Läufer ausführen. Lesen Sie hier mehr.
    Wenn Ihr Computer leistungsstärker ist, z. B. 8 vCPU, 16 GB RAM, können diese Zahlen mindestens zweimal größer gemacht werden. Aber alles hängt davon ab, was genau auf diesem Läufer und in welcher Menge gestartet wird.



Das ist genug.


Zum Inhalt



Docker-compose.yml vorbereiten


Die Hauptaufgabe ist docker-compose.yml, die sowohl lokal als auch in der CI-Pipeline verwendet wird.


Die Variable COMPOSE_PROJECT_NAME wird verwendet, um mehrere Instanzen der Umgebung zu starten (siehe Makefile ).


Ein Beispiel fĂĽr meine 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 

Zum Inhalt



Makefile-Vorbereitung


Ich verwende Makefile, da es sowohl fĂĽr die lokale Verwaltung der Umgebung als auch fĂĽr CI sehr praktisch ist.


Weitere Kommentare sind inline


 #           `.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 

ĂśberprĂĽfen Sie den lokalen Start
 $ 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 

ĂśberprĂĽfen des CI-Starts
 $ 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 

ĂśberprĂĽfen Sie die Protokollsammlung
 $ 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 


Zum Inhalt



Vorbereiten von .gitlab-ci.yml



FĂĽhren Sie Integrationstests aus


 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 

Als Ergebnis des Startens einer solchen Aufgabe in den Artefakten enthält das Protokollverzeichnis die Protokolle der Dienste und Tests. Was bei Fehlern sehr praktisch ist. Für mich schreibt jeder Test parallel ein eigenes Protokoll, aber ich werde darüber separat sprechen.



Zum Inhalt



Läuferreinigung


Die Aufgabe wird nur planmäßig ausgeführt.


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

Gehen Sie als nächstes zu unserem GitLab-Projekt -> CI / CD -> Zeitpläne -> Neuer Zeitplan und fügen Sie einen neuen Zeitplan hinzu



Zum Inhalt



Ergebnis


FĂĽhren Sie 4 Aufgaben in GitLab CI aus


In den Protokollen der letzten Aufgabe mit Integrationstests sehen wir Container aus verschiedenen Aufgaben


 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 

Detaillierteres Protokoll
 $ 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 

Alle Aufgaben erfolgreich abgeschlossen

Aufgabenartefakte enthalten Dienst- und Testprotokolle



Es scheint, dass alles schön ist, aber es gibt eine Nuance. Die Pipeline kann während der Ausführung von Integrationstests zwangsweise abgebrochen werden. In diesem Fall werden laufende Container nicht gestoppt. Von Zeit zu Zeit müssen Sie den Läufer reinigen. Leider befindet sich die Revisionsaufgabe in GitLab CE noch im Status " Offen "


Wir haben jedoch den geplanten Start der Aufgabe hinzugefĂĽgt, und niemand verbietet uns, ihn manuell zu starten.
Gehen Sie zu unserem Projekt -> CI / CD -> Zeitpläne und führen Sie die Aufgabe Clean runner



Gesamt:


  • Wir haben einen Shellrunner.
  • Es gibt keine Konflikte zwischen Aufgaben und der Umgebung.
  • Wir haben einen parallelen Start von Aufgaben mit Integrationstests.
  • Sie können Integrationstests sowohl lokal als auch im Container ausfĂĽhren.
  • Protokolle von Diensten und Tests werden gesammelt und an die Pipeline-Aufgabe angehängt.
  • Es ist möglich, den Läufer von alten Docker-Bildern zu reinigen.

Die Einrichtungszeit beträgt ~ 2 Stunden.
Das ist in der Tat alles. Ich freue mich ĂĽber Feedback.


PS
Besonderer Dank geht an freeseacher vvasilenok ivanych . Ihre Kommentare waren im Zusammenhang mit der Veröffentlichung sehr wertvoll.


Zum Inhalt

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


All Articles