Denne artikkelen vil være interessant både for testere og utviklere, men er i større grad rettet mot automatiserere som har støtt på problemer med å konfigurere GitLab CI/CD for integrasjonstesting under forhold med utilstrekkelige infrastrukturressurser og/eller mangel på en containerorkestreringsplattform. Jeg vil fortelle hvordan du kan sette opp distribusjon av testede miljøer ved hjelp av docker compose på én eneste GitLab shell runner, slik at de lanserte tjenestene ikke forstyrrer hverandre når flere miljøer distribueres.
Innhold
Forutsetninger
I min erfaring har jeg ofte vært nødt til å «fikse» integrasjonstesting på prosjekter. Og ofte er det første og mest betydelige problemet CI-pipelinen, hvor integrasjonstesting av det utviklede tjenestene foregår i dev/stage miljøet. Dette har ført til ikke få problemer:
- På grunn av feil i én eller annen tjeneste kan testoppsettet bli ødelagt av ødelagte data under integrasjonstesten. Det har vært tilfeller der sending av en forespørsel med ødelagt JSON-format har fått tjenesten til å henge, noe som førte til at hele oppsettet ble ubrukelig.
- Reduksjon av ytelsen til testoppsettet med økningen av testdata. Jeg tror det ikke gir mening å beskrive et eksempel med rensing/tilbakeføring av databasen. I min erfaring har jeg ikke møtt et prosjekt hvor denne prosedyren har gått glatt.
- Risikoen for å forstyrre funksjonaliteten til testoppsettet under testing av fellessysteminnstillinger. For eksempel bruker/grupper/passord/applikasjonspolicy.
- Testdata fra autotester forstyrrer manuelle testere.
Noen vil si at gode autotester bør rense data etter seg. Jeg har argumenter imot:
- Dynamiske oppsett er svært praktiske i bruk.
- Ikke alle objekter kan slettes fra systemet via API. For eksempel er ikke samtalen for å slette et objekt implementert, da det strider mot forretningslogikken.
- Når et objekt opprettes via API, kan det oppstå enorme mengder metadata som er vanskelig å slette.
- Hvis testene har avhengighet seg imellom, blir prosessen med å rense data etter utførelsen av testene en hodepine.
- Ytterligere (og etter min mening, uberettigede) anrop til API.
- Og hovedargumentet: når testdata begynner å bli renset rett fra databasen. Dette blir et ordentlig PK/FK sirkus! Utviklerne hører: «Jeg la bare til/fjernet/omdøpte en tabell, hvorfor ble 100500 integrasjonstester påvirket?»
Etter min mening er den mest optimale løsningen et dynamisk miljø.
- Mange bruker docker-compose for å starte testmiljøet, men få bruker docker-compose når de utfører integrasjonstesting i CI/CD. Her tar jeg ikke hensyn til Kubernetes, Swarm og andre plattformer for enhetsorkestrering. Ikke alle selskaper har dem. Det ville vært bra om docker-compose.yml var universell.
- Hvis vi i det hele tatt har vår egen QA-runners, hvordan kan vi sørge for at tjenestene som startes gjennom docker-compose ikke forstyrrer hverandre?
- Hvordan samler vi logger fra testede tjenester?
- Hvordan rydde opp i runneren?
Jeg har min egen GitLab-runner for prosjektene mine, og jeg møtte disse spørsmålene under utviklingen. for . Mer presist ved kjøring av integrasjonstester. Her vil vi begynne å løse disse spørsmålene med eksempler fra dette prosjektet.
GitLab Shell Runner
For runneren anbefaler jeg en Linux-virtuel med 4 vCPU, 4 GB RAM, 50 GB HDD.
Det finnes mye informasjon på nettet om oppsett av gitlab-runner, så kort oppsummert:
- Vi logger oss inn på maskinen via SSH.
Hvis du har mindre enn 8 GB RAM, anbefaler jeg , slik at OOM-killer ikke kommer og dreper oppgavene våre på grunn av manglende RAM. Dette kan skje når mer enn 5 oppgaver kjøres samtidig. Oppgavene vil gå saktere, men mer stabilt.
Eksempel med OOM-killer
Hvis du ser i loggene til oppgaven
bash: line 82: 26474 Killed, så utfør bare følgende på runneren: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:0kBOg hvis bildet ser omtrent slik ut, så må du enten legge til swap eller øke RAM.
- Installerer , , , make.
- Legg til bruker
gitlab-runneri gruppendockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Åpne for redigering
/etc/gitlab-runner/config.tomlog legg tilconcurrent=20 [[runners]] request_concurrency = 10Dette vil tillate kjøring av parallelle oppgaver på én runner. Les mer detaljert .
Hvis du har en kraftigere maskin, for eksempel 8 vCPU, 16 GB RAM, kan du muligens doble disse tallene. Men alt avhenger av hva som konkret skal kjøre på denne runneren og i hvilken mengde.
Det er nok.
Forberedelse av docker-compose.yml
Hovedoppgaven er en universell docker-compose.yml som utviklere/testere kan bruke både lokalt og i CI-pipeline.
Først lager vi unike navn for tjenester for CI. En av de unike variablene i GitLab CI er variabelen CI_JOB_ID. Hvis du spesifiserer container_name med verdien "service-${CI_JOB_ID:-local}", vil navnet på tjenesten være:
- hvis
CI_JOB_IDikke definert i miljøvariabler,
vil tjenesten heteservice-local - hvis
CI_JOB_IDdefinert i miljøvariabler (for eksempel 123),
vil tjenesten heteservice-123
For det andre lager vi et felles nettverk for kjørende tjenester. Dette gir oss isolasjon på nettverksnivå når vi kjører flere testmiljøer.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}Dette er egentlig første skritt mot suksess =)
Eksempel på min docker-compose.yml med kommentarer
version: "3"
# For at web (php) og fmt skal fungere korrekt, må
# containerne ha felles kjørbar innhold.
# I vårt tilfelle er dette katalogen /var/www/testrail
volumes:
static-content:
# Isolerer miljøet på nettverksnivå
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Hver container_name inneholder ${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}"
# Hvis variablene TR_HTTP_PORT eller TR_HTTPS_PORTS ikke er definert,
# vil tjenesten kjøre på port 80 og 443 henholdsvis.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultEksempel på lokal kjøring
docker-compose -f docker-compose.yml up -d
Starter testrail-mysql-local ... gjort
Starter testrail-migration-local ... gjort
Starter testrail-fpm-local ... gjort
Gjenoppretter testrail-web-local ... gjortMen det er ikke så enkelt å kjøre i CI.
Forberedelse av Makefile
Jeg bruker Makefile, da dette er veldig praktisk både for lokal administrasjon av miljøet og i CI. Heretter følger 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
Sjekk
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Killing testrail-web-local ... done
Killing testrail-fpm-local ... done
Killing testrail-mysql-local ... done
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
Pulling db ... done
Pulling migration ... done
Pulling fpm ... done
Pulling web ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Recreating testrail-mysql-local ... done
Recreating testrail-fpm-local ... done
Recreating testrail-migration-local ... done
Recreating testrail-web-local ... done
docker ps
CONTAINER ID PORTS NAMES
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: cannot create directory ‘./logs’: File exists
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
Forberedelse av .gitlab-ci.yml
Kjøring av integrasjonstester
Integration:
stage: test
tags:
- my-shell-runner
before_script:
# Authenticate in registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Generate pseudo-unique TR_HTTP_PORT and TR_HTTPS_PORT
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# create a directory with the task identifier
- mkdir ${CI_JOB_ID}
# copy our docker-compose.yml to the created directory
# so the context is different for each task
- cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
script:
# bring up our environment
- make docker-up
# run tests with executable jar (that's how I do it)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# or in a container
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# collect logs
- make docker-logs
# stop the environment
- make docker-kill
artifacts:
# save logs
when: always
paths:
- logs
expire_in: 30 daysAs a result of running such a task, the logs directory in the artifacts will contain logs from the services and tests. This is very convenient in case of errors. Each test writes its own log in parallel, but I will discuss that separately.

Rengjøring av runner
The task will only run on a schedule.
stages:
- clean
- build
- test
Clean runner:
stage: clean
only:
- schedules
tags:
- my-shell-runner
script:
- make docker-cleanNext, go to our GitLab project -> CI/CD -> Schedules -> New Schedule and add a new schedule
Resultatet
Run 4 tasks in GitLab CI
I loggene for den siste oppgaven med integrasjonstestene ser vi containere fra ulike oppgaver
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-204553539Mer detaljert logg
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
WARNING! Å bruke --password via CLI er usikkert. Bruk --password-stdin.
WARNING! Passordet ditt vil bli lagret ukryptert i /home/gitlab-runner/.docker/config.json.
Konfigurer en påloggingshjelper for å fjerne denne advarselen. Se
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Innlogging lyktes
$ 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
Feil: Ingen slik nettverks: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Henter web ... ferdig
Henter fpm ... ferdig
Henter migrasjon ... ferdig
Henter db ... ferdig
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Oppretter volum "204645172_static-content" med standarddriver
Oppretter testrail-mysql-204645172 ...
Oppretter testrail-mysql-204645172 ... ferdig
Oppretter testrail-migrasjon-204645172 ... ferdig
Oppretter testrail-fpm-204645172 ... ferdig
Oppretter testrail-web-204645172 ... ferdig
docker ps
KONTROLLER ID BILDE KJØRING OPPRETTET STATUS PORTER NAVN
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 sekunder siden Opp 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 siden Opp 13 sekunder 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 sekunder siden Opp 13 sekunder 3306/tcp, 33060/tcp testrail-migrasjon-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 sekunder siden Opp 16 sekunder 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 36 sekunder siden Opp 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 siden Opp 35 sekunder 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 sekunder siden Opp 37 sekunder 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Omtrent ett minutt siden Opp 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…" Omtrent ett minutt siden Opp 53 sekunder 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" Omtrent ett minutt siden Opp 53 sekunder 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Omtrent ett minutt siden Opp 53 sekunder 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…" Omtrent ett minutt siden Opp 53 sekunder 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" Omtrent ett minutt siden Opp 53 sekunder 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" For 5 timer siden Opp 5 timer 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…" For 5 timer siden Opp 5 timer 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" For 5 timer siden Opp 5 timer 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" For 5 timer siden Opp 5 timer 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…" For 5 timer siden Opp 5 timer 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" For 5 timer siden Opp 5 timer 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" For 5 timer siden Opp 5 timer 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…" For 5 timer siden Opp 5 timer 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" For 5 timer siden Opp 5 timer 3306/tcp testrail-mysql-204553539Alle oppgavene er vellykket fullført
Oppgaveartefakter inneholder logger fra tjenester og tester
Alt ser bra ut, men det er en detalj. Pipeline kan bli tvunget avbrutt under utføring av integrasjonstester, og i så fall vil de kjørte containerne ikke bli stoppet. Av og til må man rense runneren. Dessverre er oppgaven for forbedring i GitLab CE fortsatt i status
Men vi har lagt til kjøring av oppgaven etter timeplan, og ingen forbyr oss å kjøre den manuelt.
Vi går til prosjektet vårt -> CI/CD -> Schedules og kjører oppgaven Rens runner
Totalt:
- Vi har én shell runner.
- Det er ingen konflikter mellom oppgaver og miljø.
- Vi har parallell kjøring av oppgaver med integrasjonstester.
- Integrasjonstestene kan kjøres både lokalt og i container.
- Logger fra tjenester og tester samles inn og legges ved pipeline-oppgaven.
- Det er mulig å rense runneren for gamle Docker-bilder.
Tid for oppsett – ca. 2 timer.
Det var egentlig alt. Jeg ser frem til tilbakemeldinger.
Kilde: habr.com
