ProHoster > blogg > administration > GitLab Shell Runner. Konkurrenskraftig lansering av testade tjänster med Docker Compose
GitLab Shell Runner. Konkurrenskraftig lansering av testade tjänster med Docker Compose
Den här artikeln kommer att vara av intresse för både testare och utvecklare, men är främst avsedd för automationsspecialister som står inför problemet med att ställa in GitLab CI/CD för integrationstestning under förhållanden med otillräckliga infrastrukturresurser och/eller frånvaro av en container orkestreringsplattform. Jag kommer att berätta för dig hur du ställer in distributionen av testmiljöer med docker compose på en enda GitLab-skallöpare och så att de lanserade tjänsterna inte stör varandra när du distribuerar flera miljöer.
I min praktik hände det ofta att integrationstestning ”behandlades” på projekt. Och ofta är det första och mest betydande problemet CI-pipeline, där integrationstestning utvecklas tjänst(er) utförs i en dev/stage-miljö. Detta orsakade en del problem:
På grund av defekter i en viss tjänst under integrationstestning kan testkretsen skadas av trasiga data. Det fanns fall när en förfrågan med ett trasigt JSON-format kraschade tjänsten, vilket gjorde stativet helt obrukbart.
Avmattning av testkretsen när testdata ökar. Jag tycker att det inte är meningsfullt att beskriva ett exempel med att rensa/rulla tillbaka databasen. I min praktik har jag inte stött på ett projekt där denna procedur gick smidigt.
Risk för att testkretsens funktionalitet störs vid testning av allmänna systeminställningar. Till exempel användar/grupp/lösenord/applikationspolicy.
Testdata från automatiserade tester gör livet svårt för manuella testare.
Vissa kommer att säga att bra autotester bör rensa upp data efter sig. Jag har argument mot:
Dynamiska stativ är mycket bekväma att använda.
Inte alla objekt kan tas bort från systemet via API:et. Till exempel implementerades inte ett anrop för att ta bort ett objekt eftersom det strider mot affärslogik.
När man skapar ett objekt via API:et kan en enorm mängd metadata skapas, vilket är problematiskt att radera.
Om tester har beroenden sinsemellan, blir processen att rensa data efter att ha kört tester till en huvudvärk.
Ytterligare (och, enligt min mening, inte motiverade) anrop till API.
Och huvudargumentet: när testdata börjar rensas direkt från databasen. Det här håller på att förvandlas till en riktig PK/FK-cirkus! Vi hör från utvecklare: "Jag lade precis till/tog bort/döpte om en skylt, varför fastnade 100500 XNUMX integrationstester?"
Enligt min mening är den mest optimala lösningen en dynamisk miljö.
Många använder docker-compose för att köra en testmiljö, men få använder docker-compose när de utför integrationstestning i CI/CD. Och här tar jag inte hänsyn till kubernetes, svärmar och andra containerorkestreringsplattformar. Inte alla företag har dem. Det skulle vara trevligt om docker-compose.yml var universell.
Även om vi har en egen QA-runner, hur kan vi se till att tjänster som lanseras via docker-compose inte stör varandra?
Hur samlar man in loggar över testade tjänster?
Hur rengör man löparen?
Jag har en egen GitLab-runner för mina projekt och jag stötte på dessa frågor under utvecklingen Java-klient för TestRail. Närmare bestämt när man kör integrationstester. Nedan kommer vi att lösa dessa problem med hjälp av exempel från detta projekt.
För en löpare rekommenderar jag en virtuell Linux-maskin med 4 vCPU, 4 GB RAM, 50 GB hårddisk.
Det finns mycket information om hur du ställer in gitlab-runner på Internet, så kortfattat:
Logga in på maskinen via SSH
Om du har mindre än 8 GB RAM, så rekommenderar jag gör swap 10 GBså att OOM-mördaren inte kommer och dödar våra uppgifter på grund av brist på RAM. Detta kan hända när mer än 5 uppgifter startas samtidigt. Uppgifterna kommer att utvecklas långsammare, men stadigt.
Exempel med OOM killer
Om du ser i uppgiftsloggarna bash: line 82: 26474 Killed, sedan är det bara att köra på löparen 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
Och om bilden ser ut ungefär så här, lägg antingen till swap eller lägg till RAM.
Detta gör att du kan köra parallella uppgifter på en löpare. Läs mer här.
Om du har en kraftfullare maskin, till exempel 8 vCPU, 16 GB RAM, så kan dessa siffror göras minst 2 gånger större. Men allt beror på exakt vad som kommer att lanseras på denna löpare och i vilken mängd.
Huvuduppgiften är en universell docker-compose.yml, som utvecklare/testare kan använda både lokalt och i CI-pipeline.
Först och främst skapar vi unika tjänstenamn för CI. En av de unika variablerna i GitLab CI är variabeln CI_JOB_ID. Om du specificerar container_name med mening "service-${CI_JOB_ID:-local}", sedan i fallet:
om CI_JOB_ID inte definierad i miljövariabler,
då blir tjänstens namn service-local
om CI_JOB_ID definieras i miljövariabler (till exempel 123),
då blir tjänstens namn service-123
För det andra skapar vi ett gemensamt nätverk för lanserade tjänster. Detta ger oss isolering på nätverksnivå när vi kör flera testmiljöer.
Detta är faktiskt det första steget till framgång =)
Exempel på min docker-compose.yml med kommentarer
version: "3"
# Для корректной работы web (php) и fmt нужно,
# чтобы контейнеры имели общий исполняемый контент.
# В нашем случае, это директория /var/www/testrail
volumes:
static-content:
# Изолируем окружение на сетевом уровне
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Каждый container_name содержит ${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}"
# Если переменные 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
networks:
- default
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)
# создаем директорию с идентификатором задачи
- mkdir ${CI_JOB_ID}
# копируем в созданную директорию наш docker-compose.yml
# чтобы контекст был разный для каждой задачи
- cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
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-kill
artifacts:
# сохраняем логи
when: always
paths:
- logs
expire_in: 30 days
Som ett resultat av att köra en sådan uppgift kommer loggkatalogen i artefakterna att innehålla service- och testloggar. Vilket är väldigt bekvämt vid fel. Varje test parallellt skriver sin egen logg, men jag kommer att prata om detta separat.
$ 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_static-content" with default driver
Creating testrail-mysql-204645172 ...
Creating testrail-mysql-204645172 ... done
Creating testrail-migration-204645172 ... done
Creating testrail-fpm-204645172 ... done
Creating testrail-web-204645172 ... 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 testrail-web-204645172
01d303262d8e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 16 seconds ago Up 13 seconds 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 seconds ago Up 13 seconds 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 seconds ago Up 16 seconds 3306/tcp testrail-mysql-204645172
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 testrail-web-204645084
3540f8d448ce registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 38 seconds ago Up 35 seconds 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 seconds ago Up 37 seconds 3306/tcp testrail-mysql-204645084
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 testrail-web-204644881
6d4ccd910fad registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" About a minute ago Up About a minute 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp testrail-mysql-204644881
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 testrail-web-204644793
6f26dfb2683e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" About a minute ago Up About a minute 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp testrail-mysql-204644793
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 testrail-web-204567103
04339229397e registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 5 hours ago Up 5 hours 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp testrail-mysql-204567103
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 testrail-web-204553690
033b1f46afa9 registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 5 hours ago Up 5 hours 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp testrail-mysql-204553690
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 testrail-web-204553539
ed6b17d911a5 registry.gitlab.com/touchbit/image/testrail/fpm:latest "docker-php-entrypoi…" 5 hours ago Up 5 hours 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp testrail-mysql-204553539
Alla uppgifter slutfördes framgångsrikt
Uppgiftsartefakter innehåller service- och testloggar
Allt verkar vara vackert, men det finns en nyans. Pipeline kan tvångsavbrytas medan integrationstester körs, i vilket fall kör behållare inte stoppas. Då och då behöver du rengöra löparen. Tyvärr är uppgiften att förbättra GitLab CE fortfarande i status Öppen
Men vi har lagt till lanseringen av en uppgift enligt ett schema, och ingen förbjuder oss att köra den manuellt.
Gå till vårt projekt -> CI/CD -> Scheman och kör uppgiften Clean runner
Totalt:
Vi har en skallöpare.
Det finns inga konflikter mellan arbetsuppgifter och miljö.
Vi kör uppgifter med integrationstester parallellt.
Du kan köra integrationstester antingen lokalt eller i en container.
Service- och testloggar samlas in och bifogas pipelineuppgiften.
Det är möjligt att rensa löparen från gamla Docker-bilder.
Inställningstiden är ~2 timmar.
Det är allt, faktiskt. Jag tar gärna emot feedback.