Dit artikel is interessant voor zowel testers als ontwikkelaars, maar is vooral gericht op automatiseringsspecialisten die geconfronteerd worden met de uitdaging om GitLab CI/CD in te stellen voor integratietests onder omstandigheden van onvoldoende infrastructuur en/of het ontbreken van een containerorkestratieplatform. Ik zal uitleggen hoe je testomgevingen kunt implementeren met behulp van docker compose op een enkele GitLab shell runner, zodat bij het implementeren van meerdere omgevingen de services elkaar niet storen.
Inhoud
Achtergronden
In mijn praktijk kwam het vaak voor dat ik 'het' integratietests op projecten moest 'genezen'. En vaak is het eerste en grootste probleem de CI-pipeline waarin integratietests van de ontwikkelde service(s) worden uitgevoerd in de dev/stage omgeving. Dit leidde tot verschillende problemen:
- Door defecten in één of andere service kan het testframework tijdens integratietests worden verstoord door corrupte gegevens. Er waren gevallen waarin het verzenden van een verzoek met een corrupte JSON-indeling de service deed vastlopen, waardoor de stand volledig onbruikbaar werd.
- Vertraagde werking van het testframework naarmate de testgegevens toenamen. Ik denk niet dat het nodig is om een voorbeeld te beschrijven van het schonen/terugzetten van de database. In mijn ervaring heb ik geen project gezien waar deze procedure soepel verliep.
- Het risico om de werking van het testframework te verstoren bij het testen van algemene systeeminstellingen, zoals user/group/password/application policy.
- Testgegevens van geautomatiseerde tests belemmeren handmatige testers.
Iemand zal zeggen dat goede geautomatiseerde tests hun gegevens na afloop moeten opschonen. Ik heb argumenten tegen:
- Dynamische omgevingen zijn zeer gebruiksvriendelijk.
- Niet elk object kan via de API uit het systeem worden verwijderd. Bijvoorbeeld, de aanroep om een object te verwijderen is niet geïmplementeerd omdat dit in strijd is met de bedrijfslogica.
- Bij het aanmaken van een object via de API kan een enorme hoeveelheid metadata worden aangemaakt, die moeilijk te verwijderen is.
- Als tests afhankelijk van elkaar zijn, wordt het proces van het opschonen van gegevens na het uitvoeren van tests een hoofdpijn.
- Extra (en naar mijn mening onterecht) aanroepen naar de API.
- En het belangrijkste argument: wanneer testdata direct uit de database worden schoongemaakt. Dit verandert in een echte PK/FK circus! Van de ontwikkelaars is te horen: 'Ik heb alleen een tabel toegevoegd/verwijderd/ hernoemd, waarom kwamen 100500 integratietests in gevaar?'
Naar mijn mening is de meest optimale oplossing een dynamische omgeving.
- Veel mensen gebruiken docker-compose voor het opstarten van testomgevingen, maar weinigen gebruiken docker-compose bij het uitvoeren van integratietests in CI/CD. Hier reken ik Kubernetes, Swarm en andere containerorkestratieplatforms niet mee. Niet elke onderneming heeft deze. Het zou goed zijn als docker-compose.yml universeel was.
- Als we zelfs onze eigen QA-runner hebben, hoe zorgen we er dan voor dat de services die via docker-compose worden opgestart elkaar niet in de weg zitten?
- Hoe verzamel je logs van de geteste services?
- Hoe maak je de runner schoon?
Ik heb mijn eigen GitLab-runner voor mijn projecten en met deze vragen stuitte ik tijdens de ontwikkeling. voor . En meer specifiek bij het uitvoeren van integratietests. Laten we deze vragen verder oplossen met voorbeelden uit dit project.
GitLab Shell Runner
Voor de runner raad ik een Linux-virtuele machine aan met 4 vCPU, 4 GB RAM, 50 GB HDD.
Er is ontzettend veel informatie op het internet over het instellen van gitlab-runner, dus in het kort:
- Log in op de machine via SSH
Als je minder dan 8 GB RAM hebt, raad ik aan , zodat de OOM killer ons geen taken killt vanwege onvoldoende RAM. Dit kan gebeuren wanneer meer dan 5 taken tegelijkertijd worden opgestart. De taken zullen iets langzamer verlopen, maar wel stabiel.
Voorbeeld met OOM killer
Als je in de logs van de taak ziet
bash: line 82: 26474 Killed, voer dan gewoon op de runner uitsudo 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:0kBEn als het plaatje er ongeveer zo uitziet, voeg dan swap toe of voeg RAM toe.
- Instellen , , , make.
- Voeg gebruiker
gitlab-runnertoe aan de groepdockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Open voor bewerking
/etc/gitlab-runner/config.tomlen voeg toeconcurrent=20 [[runners]] request_concurrency = 10Dit maakt het mogelijk om parallelle taken op één runner uit te voeren. Meer gedetailleerd lezen. .
Als je een krachtiger machine hebt, bijvoorbeeld 8 vCPU, 16 GB RAM, dan kunnen deze cijfers minstens verdubbeld worden. Maar het hangt allemaal af van wat er precies op deze runner uitgevoerd zal worden en in welke hoeveelheden.
Dat is voldoende.
Voorbereiding docker-compose.yml
De belangrijkste taak is om een universele docker-compose.yml te maken die ontwikkelaars/testers zowel lokaal als in de CI-pipeline kunnen gebruiken.
Als eerste maken we unieke namen voor de services in de CI. Een van de unieke variabelen in GitLab CI is de variabele CI_JOB_ID. Als je container_name met de waarde "service-${CI_JOB_ID:-local}", dan is in het geval van:
- als
CI_JOB_IDniet gedefinieerd in de omgevingsvariabelen,
de servicenaamservice-local - als
CI_JOB_IDgedefinieerd in de omgevingsvariabelen (bijvoorbeeld 123),
de servicenaamservice-123
Als tweede creëren we een gezamenlijk netwerk voor de services die worden uitgevoerd. Dit biedt ons isolatie op netwerkniveau bij het uitvoeren van meerdere testomgevingen.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}Dit is eigenlijk de eerste stap naar succes =)
Voorbeeld van mijn docker-compose.yml met opmerkingen
version: "3"
# Voor een goede werking van web (php) en fmt, moet
de containers gemeenschappelijke uitvoerbare inhoud hebben.
# In ons geval is dat de directory /var/www/testrail
volumes:
static-content:
# We isoleren de omgeving op netwerkniveau
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Elke container_name bevat ${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}"
# Als de variabelen TR_HTTP_PORT of TR_HTTPS_PORTS niet gedefinieerd zijn,
# wordt de service respectievelijk op poort 80 en 443 gestart.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultVoorbeeld van lokale uitvoering
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 ... doneMaar het is niet zo eenvoudig met het starten in CI.
Voorbereiding Makefile
Ik gebruik Makefile, omdat het zeer handig is voor zowel lokaal omgevingsbeheer als in CI. Hieronder inline opmerkingen.
# У меня в проектах все вспомогательные вещи лежат в директории `.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
Controleer
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Killing testrail-web-local ... gedaan
Killing testrail-fpm-local ... gedaan
Killing testrail-mysql-local ... gedaan
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
Pullen db ... gedaan
Pullen migratie ... gedaan
Pullen fpm ... gedaan
Pullen web ... gedaan
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Vernieuwt testrail-mysql-local ... gedaan
Vernieuwt testrail-fpm-local ... gedaan
Vernieuwt testrail-migratie-local ... gedaan
Vernieuwt testrail-web-local ... gedaan
docker ps
CONTAINER ID POORTEN NAAM
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-migratie-local
0e7900c23f37 3306/tcp testrail-mysql-local
make docker-logs
$ make docker-logs
mkdir ./logs || true
mkdir: kan map ‘./logs’ niet aanmaken: Bestand bestaat al
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-migratie-${CI_JOB_ID:-local} >& logs/testrail-migratie.log
docker logs testrail-mysql-${CI_JOB_ID:-local} >& logs/testrail-mysql.log
Voorbereiding .gitlab-ci.yml
Uitvoering van integratietests
Integratie:
stage: test
tags:
- my-shell-runner
before_script:
# Authenticeren in de registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Genereer pseudo-unik TR_HTTP_PORT en TR_HTTPS_PORT
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# Maak een directory met de taak-ID
- mkdir ${CI_JOB_ID}
# Kopieer ons docker-compose.yml naar de aangemaakte directory
# zodat de context voor elke taak verschillend is
- cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
script:
# Start onze omgeving op
- make docker-up
# Voer tests uit met een uitvoerbare jar (ik heb het zo)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# of in een container
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# Verzamel logs
- make docker-logs
# Stop de omgeving
- make docker-kill
artifacts:
# Bewaar logs
when: altijd
paths:
- logs
expire_in: 30 dagenNa het draaien van zo'n taak zal de directory logs in de artifacts de logs van de services en tests bevatten. Wat zeer handig is in het geval van fouten. Iedere test schrijft zijn eigen log parallel, maar daarover zal ik apart vertellen.

Schoonmaken van de runner
De taak zal alleen volgens schema worden uitgevoerd.
stages:
- clean
- build
- test
Clean runner:
stage: clean
only:
- schedules
tags:
- my-shell-runner
script:
- make docker-cleanVervolgens gaan we naar ons GitLab-project -> CI/CD -> Schema's -> Nieuw schema en voegen we een nieuw schema toe
Resultaat
We starten 4 taken in GitLab CI
In de logs van de laatste taak met integratietests zien we containers van verschillende taken
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-204553539Gedetailleerdere log
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
WAARSCHUWING! Het gebruik van --password via de CLI is onveilig. Gebruik --password-stdin.
WAARSCHUWING! Uw wachtwoord wordt onversleuteld opgeslagen in /home/gitlab-runner/.docker/config.json.
Configureer een credential helper om deze waarschuwing te verwijderen. Zie
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Inloggen geslaagd
$ 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
Fout: Geen dergelijk netwerk: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Web aan het ophalen ... gedaan
FPM aan het ophalen ... gedaan
Migratie aan het ophalen ... gedaan
DB aan het ophalen ... gedaan
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Volume "204645172_static-content" wordt aangemaakt met standaarddriver
Aanmaken testrail-mysql-204645172 ...
Aanmaken testrail-mysql-204645172 ... gedaan
Aanmaken testrail-migration-204645172 ... gedaan
Aanmaken testrail-fpm-204645172 ... gedaan
Aanmaken testrail-web-204645172 ... gedaan
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 seconden geleden Up 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…" 16 seconden geleden Up 13 seconden 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 seconden geleden Up 13 seconden 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 seconden geleden Up 16 seconden 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 36 seconden geleden Up 22 seconden 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…" 38 seconden geleden Up 35 seconden 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 seconden geleden Up 37 seconden 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Ongeveer een minuut geleden Up 53 seconden 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…" Ongeveer een minuut geleden Up Ongeveer een minuut 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" Ongeveer een minuut geleden Up Ongeveer een minuut 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Ongeveer een minuut geleden Up Ongeveer een minuut 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…" Ongeveer een minuut geleden Up Ongeveer een minuut 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" Ongeveer een minuut geleden Up Ongeveer een minuut 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 uur geleden Up 5 uur 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…" 5 uur geleden Up 5 uur 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" 5 uur geleden Up 5 uur 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 uur geleden Up 5 uur 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…" 5 uur geleden Up 5 uur 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" 5 uur geleden Up 5 uur 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 uur geleden Up 5 uur 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…" 5 uur geleden Up 5 uur 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" 5 uur geleden Up 5 uur 3306/tcp testrail-mysql-204553539Alle taken zijn met succes voltooid
De taakartefacten bevatten logs van de services en tests
Het ziet er allemaal goed uit, maar er is een kanttekening. De pipeline kan handmatig worden afgebroken tijdens het uitvoeren van integratietests, en in dat geval worden de gestarte containers niet gestopt. Af en toe moet je de runner opschonen. Helaas is de wijzigingstaak in GitLab CE nog steeds in status
Maar we hebben het starten van de taak op schema toegevoegd, en niemand verbiedt ons om het handmatig te starten.
Laten we naar ons project gaan -> CI/CD -> Schedules en de taak starten Clean runner
Kortom:
- We hebben één shell runner.
- Er zijn geen conflicten tussen taken en omgevingen.
- We hebben een parallelle uitvoering van taken met integratietests.
- Integratietests kunnen zowel lokaal als in de container worden uitgevoerd.
- Logs van services en tests worden verzameld en aan de pipeline-taak gehecht.
- Er is de mogelijkheid om de runner te reinigen van oude docker-images.
Insteltijd — ~2 uur.
Dat was het dan. Ik kijk uit naar feedback.
Bron: habr.com
