Denne artikel vil være interessant for både testere og udviklere, men er primært rettet mod automatisører, der står over for udfordringer med at konfigurere GitLab CI/CD til at udføre integrationstest under begrænsede infrastrukturessourcer og/eller fraværet af en containerorkestreringsplatform. Jeg vil fortælle om, hvordan man opsætter udrulning af testmiljøer ved hjælp af docker compose på en enkelt GitLab shell runner, så de tjenester, der startes, ikke forstyrrer hinanden under udrulningen af flere miljøer.
Indhold
Forudsætninger
I min erfaring har jeg ofte måtte 'reparere' integrationstest i projekter. Og ofte er den første og mest betydningsfulde udfordring CI-pipelinen, hvor integrationstest af den udviklede service(r) udføres i dev/stage-miljøet. Dette har skabt en del problemer:
- På grund af fejl i den ene eller anden service kan testmiljøet i integrationstestning blive ødelagt af korrupte data. Der har været tilfælde, hvor en forespørgsel med et beskadiget JSON-format har fået servicen til at gå ned, hvilket har gjort det samlede system ubrugeligt.
- Nedsat ydeevne for testmiljøet i takt med at testdata vokser. Jeg tror, det er unødvendigt at beskrive et eksempel med rensning/rollback af DB. Jeg har ikke mødt et projekt, hvor denne procedure forløb glat.
- Risiko for at bryde funktionaliteten i testmiljøet ved test af fælles systemindstillinger. For eksempel bruger/grupper/adgangskoder/applikationspolitikker.
- Testdata fra automatiserede tests skaber problemer for manuelle testere.
Nogle vil sige, at gode automatiserede tests bør rydde op efter sig selv. Jeg har argumenter imod:
- Dynamiske miljøer er meget praktiske at bruge.
- Ikke alle objekter kan slettes fra systemet via API. For eksempel er kald på sletning af objekter ikke implementeret, da det strider mod forretningslogikken.
- Ved oprettelse af objekter via API kan der genereres en stor mængde metadata, der er besværligt at slette.
- Hvis testene har afhængighed mellem hinanden, så bliver processen med at rense data efter tests en hovedpine.
- Yderligere (og efter min mening, uberettigede) kald til API'en.
- Og det vigtigste argument: når testdata begynder at blive renset direkte fra databasen. Det bliver til et ægte PK/FK cirkus! Man hører udviklerne sige: „Jeg har bare tilføjet/fjernet/omdøbt en tabel, hvorfor blev 100500 integrationstest påvirket?"
Efter min mening er den mest optimale løsning et dynamisk miljø.
- Mange bruger docker-compose til at starte testmiljøet, men få bruger docker-compose ved udførelse af integrationstest i CI/CD. Og her tager jeg ikke hensyn til Kubernetes, Swarm og andre containerorchestrationplatforme. Ikke alle virksomheder har dem. Det ville være godt, hvis docker-compose.yml var universelt.
- Hvis vi har vores egen QA-runner, hvordan får vi så tjenesterne, der startes via docker-compose, til ikke at forstyrre hinanden?
- Hvordan samler vi logs fra de testede tjenester?
- Hvordan renser vi runneren?
Jeg har min egen GitLab-runner for mine projekter, og jeg har mødt disse spørgsmål under udviklingen. for . Og mere præcist ved udførelse af integrationstest. Her vil vi addressere disse spørgsmål med eksempler fra dette projekt.
GitLab Shell Runner
For runneren anbefaler jeg en Linux-virtuel maskine med 4 vCPU, 4 GB RAM, 50 GB HDD.
Der er meget information online om opsætning af gitlab-runner, så kort sagt:
- Log ind på maskinen via SSH.
Hvis du har mindre end 8 GB RAM, så anbefaler jeg , så OOM killer ikke kommer og dræber vores opgaver på grund af mangel på RAM. Det kan ske, når mere end 5 opgaver kører samtidigt. Opgaverne vil køre langsommere, men mere stabilt.
Eksempel med OOM killer
Hvis du ser i logs for opgaven
bash: line 82: 26474 Killed, så udfør blot 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 billedet ser nogenlunde sådan ud, så tilføj enten swap eller tilføj mere RAM.
- Installerer , , , gør.
- Tilføjer bruger
gitlab-runnertil gruppedockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Åbner for redigering
/etc/gitlab-runner/config.tomlog tilføjerconcurrent=20 [[runners]] request_concurrency = 10Dette vil muliggøre kørsel af parallelle opgaver på én runner. Læs mere detaljeret .
Hvis du har en kraftigere maskine, for eksempel 8 vCPU, 16 GB RAM, kan disse tal gøres mindst 2 gange større. Men det afhænger af, hvad der konkret vil blive kørt på denne runner, og i hvilken mængde.
Det er tilstrækkeligt.
Forberedelse af docker-compose.yml
Den primære opgave er en universel docker-compose.yml, som udviklere/testere kan bruge både lokalt og i CI-pipeline.
Først og fremmest laver vi unikke navne til services i CI. En af de unikke variabler i GitLab CI er variablen CI_JOB_ID. Hvis du angiver container_name med værdien "service-${CI_JOB_ID:-local}", så i tilfældet:
- hvis
CI_JOB_IDikke er defineret i miljøvariabler,
vil navnet på servicen væreservice-local - hvis
CI_JOB_IDdefineret i miljøvariabler (for eksempel 123),
vil navnet på servicen væreservice-123
For det andet laver vi et fælles netværk for de kørte services. Dette giver os isolation på netværksniveau, når der køres flere testmiljøer.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}Det er faktisk det første skridt mod succes =)
Eksempel på min docker-compose.yml med kommentarer
version: "3"
# For korrekt drift af web (php) og fmt, skal
# containerne have fælles eksekverbart indhold.
# I vores tilfælde er det mappen /var/www/testrail
volumes:
static-content:
# Isolerer miljøet på netværksniveau
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Hver container_name indeholder ${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 variablerne TR_HTTP_PORT eller TR_HTTPS_PORTS ikke er defineret,
# så startes servicen på henholdsvis port 80 og 443.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultEksempel på lokal kørsel
docker-compose -f docker-compose.yml up -d
Starter testrail-mysql-local ... færdig
Starter testrail-migration-local ... færdig
Starter testrail-fpm-local ... færdig
Genopretter testrail-web-local ... færdigMen det er ikke så simpelt at køre i CI.
Forberedelse af Makefile
Jeg bruger Makefile, da det er ganske praktisk både til lokal håndtering af miljøet og i CI. Her er 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
Vi tjekker
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Dræber testrail-web-local ... færdig
Dræber testrail-fpm-local ... færdig
Dræber testrail-mysql-local ... færdig
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
Henter db ... færdig
Henter migration ... færdig
Henter fpm ... færdig
Henter web ... færdig
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Genopretter testrail-mysql-local ... færdig
Genopretter testrail-fpm-local ... færdig
Genopretter testrail-migration-local ... færdig
Genopretter testrail-web-local ... færdig
docker ps
KONTAINER ID PORTS NAVNE
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 ikke oprette mappe ‘./logs’: Mappen eksisterer
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 af .gitlab-ci.yml
Kørsel af integrationstest
Integration:
stage: test
tags:
- my-shell-runner
before_script:
# Authentificering til registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Genererer pseudo-unikke TR_HTTP_PORT og TR_HTTPS_PORT
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# Opretter mappe med opgave-id
- mkdir ${CI_JOB_ID}
# Kopierer vores docker-compose.yml til den oprettede mappe
# for at konteksten er forskellig for hver opgave
- cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
script:
# Starter vores miljø
- make docker-up
# Kører tests med eksekverbart jar (sådan gør jeg)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# eller i container
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# Samler logs
- make docker-logs
# Stopper miljøet
- make docker-kill
artifacts:
# Gemmer logs
when: always
paths:
- logs
expire_in: 30 daysSom et resultat af at køre sådan en opgave, vil logs-mappen i artefakterne indeholde logs fra tjenesterne og tests. Dette er meget praktisk i tilfælde af fejl. Hvert test kører parallelt og skriver sin egen log, men det vil jeg tale om separat.

Rydning af runneren
Opgaven vil kun blive kørt efter en tidsplan.
stages:
- clean
- build
- test
Clean runner:
stage: clean
only:
- schedules
tags:
- my-shell-runner
script:
- make docker-cleanDerefter går vi ind i vores GitLab-projekt -> CI/CD -> Tidsplaner -> Ny tidsplan og tilføjer en ny tidsplan
Resultat
Vi starter 4 opgaver i GitLab CI
I loggen for den seneste opgave med integrations-tests ser vi containere fra forskellige opgaver
CONTAINER ID NAVNE
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-204553539Mere detaljeret log
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
ADVARSEL! At bruge --password via CLI er usikkert. Brug --password-stdin.
ADVARSEL! Dit kodeord vil blive gemt ukrypteret i /home/gitlab-runner/.docker/config.json.
Konfigurer en legitimationshelper for at fjerne denne advarsel. Se
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login lykkedes
$ 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
Fejl: Ingen sådan netværk: 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 ... færdig
Henter fpm ... færdig
Henter migration ... færdig
Henter db ... færdig
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Opretter volumen "204645172_static-content" med standarddriver
Opretter testrail-mysql-204645172 ...
Opretter testrail-mysql-204645172 ... færdig
Opretter testrail-migration-204645172 ... færdig
Opretter testrail-fpm-204645172 ... færdig
Opretter testrail-web-204645172 ... færdig
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 sekunder siden Kører 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 Kører 13 sekunder 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 sekunder siden Kører 13 sekunder 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 sekunder siden Kører 16 sekunder 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 36 sekunder siden Kører 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 Kører 35 sekunder 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 sekunder siden Kører 37 sekunder 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" For omkring et minut siden Kører 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…" For omkring et minut siden Kører For omkring et minut 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" For omkring et minut siden Kører For omkring et minut 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" For omkring et minut siden Kører For omkring et 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…" For omkring et minut siden Kører For omkring et minut 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" For omkring et minut siden Kører For omkring et minut 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" For 5 timer siden Kører 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 Kører 5 timer 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" For 5 timer siden Kører 5 timer 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" For 5 timer siden Kører 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 Kører 5 timer 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" For 5 timer siden Kører 5 timer 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" For 5 timer siden Kører 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 Kører 5 timer 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" For 5 timer siden Kører 5 timer 3306/tcp testrail-mysql-204553539Alle opgaver er afsluttet med succes
Opgaveartefakterne indeholder logs fra tjenester og tests
Det ser ud til at være i orden, men der er en detalje. Pipeline kan blive tvunget annulleret under udførelsen af integrations-testene, og i så fald vil de startede containere ikke blive stoppet. Det er nødvendigt at rydde op i runneren fra tid til anden. Desværre er opgaven til forbedring i GitLab CE stadig i status.
Men vi har tilføjet udførelse af opgaven efter schedule, og ingen forhindrer os i at starte den manuelt.
Lad os gå ind i vores projekt -> CI/CD -> Schedules og starte opgaven Ryd runner
I alt:
- Vi har én shell runner.
- Der er ingen konflikter mellem opgaverne og miljøet.
- Vi har parallel udførelse af opgaver med integrations-testene.
- Integrations-testene kan køres både lokalt og i containeren.
- Logs fra tjenesterne og testene indsamles og vedhæftes pipeline-opgaven.
- Der er mulighed for at rydde runneren for gamle Docker-images.
Opsætningstiden er ~2 timer.
Det var vist det hele. Jeg ser frem til feedback.
Kilde: habr.com
