Denna artikel kommer att intressera både testare och utvecklare, men är främst riktad till automatiserare som har stött på problem med att konfigurera GitLab CI/CD för att utföra integrationstester i en miljö med otillräckliga infrastruktursresurser och/eller avsaknad av en containerorkestreringsplattform. Jag kommer att berätta hur man konfigurerar distributionen av testmiljöer med hjälp av docker compose på en enda GitLab shell runner, så att de körda tjänsterna inte stör varandra vid distribution av flera miljöer.
Innehåll
Förutsättningar
I min praktik har jag ofta behövt 'läka' integrationstester på projekt. Och ofta är det första och mest betydande problemet CI-pipelinen, i vilken integrationstester av den utvecklade tjänsten/erna utförs i dev/stage-miljö. Detta har orsakat en hel del problem:
- På grund av defekter i någon tjänst kan testkretsen under integrationstestning bli skadad av ogiltiga data. Det har funnits fall där en begäran med ogiltig JSON-format hängde tjänsten, vilket ledde till att hela standen blev oanvändbar.
- Fördröjning av testkretsens arbete med ökande testdata. Jag tror att det inte är meningsfullt att beskriva ett exempel med rensning/återställning av databasen. I min praktik har jag aldrig sett ett projekt där denna procedur gick smidigt.
- Risk för att rubba testkretsens funktionalitet vid testning av gemensamma systeminställningar. Till exempel användare/grupp/lösenord/applikationspolicy.
- Testdata från automatiska tester stör manuella testare.
Någon skulle säga att bra automatiska tester bör rensa upp efter sig. Jag har argument som talar emot det:
- Dynamiska testmiljöer är mycket bekväma att använda.
- Inte varje objekt kan raderas från systemet via API. Till exempel är anropet för att radera ett objekt inte implementerat, eftersom det strider mot affärslogiken.
- Vid skapandet av ett objekt via API kan en mängd metadata skapas, vilket är problematiskt att ta bort.
- Om testen har beroenden mellan sig blir rensningen av data efter testerna en huvudvärk.
- Ytterligare (och, enligt min åsikt, obefogade) anrop till API.
- Och det största argumentet: när testdata börjar rensas direkt från databasen. Det förvandlas till en riktig PK/FK-cirkus! Utvecklarna säger: "Jag har bara lagt till/tagit bort/omdöpt en tabell, varför blev 100500 integrationstester drabbade?"
Enligt min mening är den mest optimala lösningen en dynamisk miljö.
- Många använder docker-compose för att starta testmiljöer, men få använder docker-compose vid integrationstestning i CI/CD. Här tar jag inte hänsyn till Kubernetes, Swarm och andra containerorkestreringsplattformar. De finns inte i varje företag. Det skulle vara bra om docker-compose.yml var universell.
- Om vi till och med har vår egen QA-runner, hur gör vi så att tjänsterna som startas via docker-compose inte stör varandra?
- Hur samlar vi loggar från de testade tjänsterna?
- Hur rensar vi runnern?
Jag har en egen GitLab-runner för mina projekt och jag stötte på dessa frågor under utvecklingen. för . Mer specifikt när vi kör integrationstester. Här kommer vi att lösa dessa frågor med exempel från detta projekt.
GitLab Shell Runner
För runnern rekommenderar jag en Linux-virtuell maskin med 4 vCPU, 4 GB RAM, 50 GB HDD.
Det finns mycket information på internet om hur man konfigurerar gitlab-runner, så här kommer en kort sammanfattning:
- Logga in på maskinen via SSH
Om du har mindre än 8 GB RAM, rekommenderar jag , så att OOM-killer inte kommer och dödar våra uppgifter på grund av brist på RAM. Detta kan hända när mer än 5 uppgifter körs samtidigt. Uppgifterna kommer att köras något långsammare, men stabilt.
Exempel på OOM-killer
Om du ser i loggarna för uppgiften
bash: line 82: 26474 Killed, kör bara på runnernsudo 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:0kBOch om bilden ser ut ungefär så här, antingen lägg till swap eller öka RAM.
- Installera , , , gör.
- Lägg till användaren
gitlab-runnertill gruppendockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Öppna för redigering
/etc/gitlab-runner/config.tomloch lägg tillconcurrent=20 [[runners]] request_concurrency = 10Detta kommer att tillåta parallella uppgifter på en runner. Läs mer detaljerat. .
Om du har en mer kraftfull maskin, till exempel 8 vCPU, 16 GB RAM, kan dessa siffror göras minst dubbelt så stora. Men allt beror på vad som specifikt kommer att köras på denna runner och i vilken mängd.
Det är tillräckligt.
Förberedelse av docker-compose.yml
Huvuduppgiften är att skapa en universell docker-compose.yml som utvecklare/testare kan använda både lokalt och i CI-pipeline.
Först skapar vi unika namn för tjänsterna för CI. En av de unika variablerna i GitLab CI är variabeln CI_JOB_ID. Om du anger container_name med värdet "service-${CI_JOB_ID:-local}", så i fallet:
- om
CI_JOB_IDinte är definierad i miljövariablerna,
blir tjänstens namnservice-local - om
CI_JOB_IDär definierad i miljövariablerna (till exempel 123),
blir tjänstens namnservice-123
För det andra skapar vi ett gemensamt nätverk för de körande tjänsterna. Detta ger oss isolering på nätverksnivå när vi kör flera testmiljöer.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}Detta är i själva verket det första steget mot framgång =)
Exempel på min docker-compose.yml med kommentarer
version: "3"
# För att web (php) och fmt ska fungera korrekt,
# måste containrarna ha gemensamt körbart innehåll.
# I vårt fall är detta katalogen /var/www/testrail
volumes:
static-content:
# Isolera miljön på nätverksnivå
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Varje container_name innehåller ${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}"
# Om variablerna TR_HTTP_PORT eller TR_HTTPS_PORTS inte är definierade,
# startar tjänsten på port 80 respektive 443.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultExempel på lokal körning
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 ... doneMen det är inte så enkelt med körning i CI.
Förberedelse av Makefile
Jag använder Makefile eftersom det är mycket praktiskt för både lokal hantering av miljön och i CI. Härnäst kommer inline-kommentarer.
# У меня в проектах все вспомогательные вещи лежат в директории `.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
Kontrollerar
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml kill
Dödar testrail-web-local ... klart
Dödar testrail-fpm-local ... klart
Dödar testrail-mysql-local ... klart
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
Hämtar db ... klart
Hämtar migration ... klart
Hämtar fpm ... klart
Hämtar web ... klart
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Återställs testrail-mysql-local ... klart
Återställs testrail-fpm-local ... klart
Återställs testrail-migration-local ... klart
Återställs testrail-web-local ... klart
docker ps
KONTROLLER ID PORTAR NAMN
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: kan inte skapa katalog ‘.\/logs’: Katalogen finns
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
Förberedelse av .gitlab-ci.yml
Körning av integrationstester
Integration:
steg: test
tags:
- my-shell-runner
before_script:
# Loggar in i registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Genererar pseudo-unika TR_HTTP_PORT och TR_HTTPS_PORT
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# Skapar en katalog med uppgiftens ID
- mkdir ${CI_JOB_ID}
# Kopierar vår docker-compose.yml till den skapade katalogen
# för att kontexten ska vara olika för varje uppgift
- cp .indirect\/docker-compose.yml ${CI_JOB_ID}\/docker-compose.yml
script:
# Startar vår miljö
- make docker-up
# Kör tester med en körbar jar (så gör jag)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# eller i en container
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# Samlar in loggar
- make docker-logs
# Stoppar miljön
- make docker-kill
artifacts:
# Sparar loggar
när: alltid
sökvägar:
- logs
utgång_in: 30 dagarSom ett resultat av att denna uppgift körs kommer katalogen logs i artefakterna att innehålla loggar från tjänster och tester. Detta är mycket praktiskt i händelse av fel. Jag har varje test som skriver sina egna loggar parallellt, men det kommer jag att berätta om separat.

Rensa runner
Uppgiften kommer att köras endast enligt schema.
steg:
- rensa
- bygga
- testa
Rensa körning:
steg: rensa
endast:
- scheman
tags:
- my-shell-runner
script:
- make docker-cleanGå vidare till vårt GitLab-projekt -> CI/CD -> Scheman -> Nytt schema och lägg till ett nytt schema
Resultat
Vi kör 4 uppgifter i GitLab CI
I loggarna för den senaste uppgiften med integrationstester ser vi containrar från olika uppgifter
CONTAINER ID NAMN
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-204553539Mer detaljerad logg
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
VARNING! Användning av --password via CLI är osäkert. Använd --password-stdin.
VARNING! Ditt lösenord kommer att lagras okrypterat i /home/gitlab-runner/.docker/config.json.
Konfigurera en autentiseringshjälpare för att ta bort denna varning. Se
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Inloggning lyckades
$ 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
Fel: Ingen sådan nätverksanslutning: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Hämtar webb ... klar
Hämtar fpm ... klar
Hämtar migrering ... klar
Hämtar db ... klar
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Skapar volymen "204645172_static-content" med standarddrivrutinen
Skapar testrail-mysql-204645172 ...
Skapar testrail-mysql-204645172 ... klart
Skapar testrail-migration-204645172 ... klart
Skapar testrail-fpm-204645172 ... klart
Skapar testrail-web-204645172 ... klart
docker ps
KONTROLLERINGS-ID BILD KOMMANDO SKAPAD STATUS PORTAR NAMN
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 sekunder sedan Up 1 sekund 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 sekunder sedan Up 13 sekunder 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 sekunder sedan Up 13 sekunder 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 sekunder sedan Up 16 sekunder 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 36 sekunder sedan Up 22 sekunder 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 sekunder sedan Up 35 sekunder 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 sekunder sedan Up 37 sekunder 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Cirkus en minut sedan Up 53 sekunder 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…" Cirkus en minut sedan Up Cirkus en minut 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" Cirkus en minut sedan Up Cirkus en minut 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Cirkus en minut sedan Up Cirkus en minut 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…" Cirkus en minut sedan Up Cirkus en minut 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" Cirkus en minut sedan Up Cirkus en minut 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" För 5 timmar sedan Up 5 timmar 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…" För 5 timmar sedan Up 5 timmar 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" För 5 timmar sedan Up 5 timmar 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" För 5 timmar sedan Up 5 timmar 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…" För 5 timmar sedan Up 5 timmar 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" För 5 timmar sedan Up 5 timmar 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" För 5 timmar sedan Up 5 timmar 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…" För 5 timmar sedan Up 5 timmar 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" För 5 timmar sedan Up 5 timmar 3306/tcp testrail-mysql-204553539Alla uppgifter har slutförts framgångsrikt
Uppgiftens artefakter innehåller loggar från tjänster och tester
Det ser bra ut, men det finns en detalj. Pipelines kan tvingas avbrytas under körning av integrationstester, och i sådana fall kommer de startade containrarna inte att stoppas. Ibland behöver man rengöra runnern. Tyvärr är uppgiften för vidareutveckling i GitLab CE fortfarande i status
Men vi har lagt till att köra uppgiften enligt schema, och ingen hindrar oss från att köra den manuellt.
Vi går till vårt projekt -> CI/CD -> Scheman och kör uppgiften Rengör runner
Totalt:
- Vi har en shell runner.
- Det finns inga konflikter mellan uppgifterna och miljön.
- Vi har parallell körning av uppgifter med integrationstester.
- Integrationstester kan köras både lokalt och i en container.
- Loggar från tjänster och tester samlas in och bifogas pipeline-uppgiften.
- Det finns möjlighet att rengöra runnern från gamla dockerbilder.
Inställningstiden är cirka 2 timmar.
Och det är egentligen allt. Jag ser fram emot feedback.
Källa: habr.com
