Tento článok bude zaujímavý aj pre testerov, aj pre vývojárov, ale je zameraný najmä na automatizátorov, ktorí sa stretli s problémom nastavenia GitLab CI/CD na vykonávanie integračného testovania za podmienok nedostatku infraštruktúrnych zdrojov a/alebo absencie platformy na orchestráciu kontajnerov. Povediem vás, ako nastaviť nasadenie testovacích prostredí pomocou docker compose na jednom jediném GitLab shell runneri, aby sa pri nasadení viacerých prostredí spustené služby navzájom neovplyvnili.
Obsah
Predpoklady
Vo svojej praxi som často musel "liečiť" integračné testovanie na projektoch. A často je prvým a najvýznamnejším problémom CI pipeline, v ktorom sa integračné testovanie vyvíjaného servisu(ov) vykonáva v dev/stage prostredí. To spôsobovalo množstvo problémov:
- Kvôli chybám v niektorom zo servisov môže byť testovací okruh poškodený nekonzistentnými dátami počas integračného testovania. Boli prípady, keď odoslanie požiadavky s poškodeným JSON formátom spadlo službu, čo spôsobilo, že stánok prestal fungovať.
- Zpomalovaním práce testovacieho okruhu s rastom testovacích dát. Myslím, že nemá zmysel opisovať príklad s čistením/rollbackom DB. Vo svojej praxi som nenarazil na projekt, kde by tento proces prebiehal hladko.
- Riziko narušiť funkčnosť testovacieho okruhu pri testovaní spoločných nastavení systému. Napríklad, user/group/password/application policy.
- Testovacie dáta od automatických testov prekážajú manuálnym testerom.
Niektorí povedia, že dobré automatizované testy by mali čistiť dáta po sebe. Mám protiargumenty:
- Dynamické stánky sú veľmi pohodlné na používanie.
- Nie každý objekt je možné z systému odstrániť cez API. Napríklad volanie na odstránenie objektu nie je implementované, pretože odporuje obchodnej logike.
- Pri vytváraní objektu cez API môže byť vytvorených ohromné množstvo metadát, ktoré je problematické odstrániť.
- Ak majú testy vzájomnú závislosť, proces čistenia dát po vykonaní testov sa premení na nočnú moru.
- Dodatočné (a, podľa môjho názoru, neopodstatnené) volania na API.
- A hlavný argument: keď sa testovacie údaje začnú čistiť priamo z databázy. To sa mení na skutočný cirkus PK/FK! Od vývojárov počujeme: „Pridal/vymazal/premenoval som len tabuľku, prečo zaznamenalo 100500 integračných testov?“
Podľa môjho názoru je najoptimálnejším riešením dynamické prostredie.
- Mnohí používajú docker-compose na spustenie testovacieho prostredia, ale málo kto používa docker-compose pri vykonávaní integračného testovania v CI/CD. A tu nezohľadňujem kubernetes, swarm a iné platformy na orchestráciu kontajnerov. Nie v každej firme ich majú. Bolo by dobré, keby bol docker-compose.yml univerzálny.
- Ak máme svoj QA runner, ako zabezpečiť, aby služby spúšťané prostredníctvom docker-compose si navzájom neprekážali?
- Ako zbierať logy testovaných služieb?
- Ako vyčistiť runner?
Mám vlastný GitLab runner pre svoje projekty a s týmito otázkami som sa stretol počas vývoja pre . A presnejšie pri spúšťaní integračných testov. Následne budeme riešiť tieto otázky s príkladmi z tohto projektu.
GitLab Shell Runner
Pre runner odporúčam linuxovú virtuálnu mašinu s 4 vCPU, 4 GB RAM, 50 GB HDD.
Na internete je veľmi veľa informácií o nastavení gitlab-runner, preto v skratke:
- Pripojíme sa na stroj cez SSH
Ak máte menej ako 8 GB RAM, odporúčam , aby nám neprišiel OOM killer a nezabíjal úlohy z nedostatku RAM. To sa môže stať, keď sa súčasne spúšťajú viac ako 5 úloh. Úlohy budú prebiehať pomalšie, ale stabilne.
Príklad s OOM killerom
Ak v logoch úlohy uvidíte
bash: line 82: 26474 Killed, vykonajte na runnerisudo 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:0kBA ak obraz vyzerá približne takto, pridajte swap alebo pridajte RAM.
- Inštalujeme , , , make.
- Pridávame používateľa
gitlab-runnerdo skupinydockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Otvárame na úpravu
/etc/gitlab-runner/config.tomla pridávameconcurrent=20 [[runners]] request_concurrency = 10To umožní spúšťať paralelné úlohy na jednom runnere. Podrobnejšie čítajte .
Ak máte výkonnejší stroj, napríklad 8 vCPU a 16 GB RAM, môžete tieto čísla zvýšiť aspoň dvakrát. Ale všetko záleží od toho, čo konkrétne sa spustí na tomto runneri a v akom množstve.
To je dostatočné.
Príprava docker-compose.yml
Hlavným cieľom je univerzálny docker-compose.yml, ktorý môžu používať vývojári/testeri ako lokálne, tak v CI pipeline.
V prvom rade vytvárame jedinečné názvy služieb pre CI. Jednou z jedinečných premenných v GitLab CI je premenná CI_JOB_ID. Ak sa uvedie container_name s hodnotou "service-${CI_JOB_ID:-local}", tak v prípade:
- ak
CI_JOB_IDnie je definovaná v premenných prostredia,
názov služby budeservice-local - ak
CI_JOB_IDje definovaná v premenných prostredia (napr. 123),
názov služby budeservice-123
Po druhé, vytvárame spoločnú sieť pre spúšťané služby. To nám poskytuje izoláciu na úrovni siete pri spúšťaní viacerých testovacích prostredí.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}Vlastne, to je prvý krok k úspechu =)
Príklad môjho docker-compose.yml s komentármi
version: "3"
# Pre správnu funkciu web (php) a fmt je potrebné,
# aby kontajnery mali spoločný vykonateľný obsah.
# V našom prípade je to adresár /var/www/testrail
volumes:
static-content:
# Izolujeme prostredie na úrovni siete
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Každý container_name obsahuje ${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}"
# Ak nie sú premenné TR_HTTP_PORT alebo TR_HTTPS_PORTS definované,
# služba sa spúšťa na 80 a 443 porte príslušne.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultPríklad lokálneho spustenia
docker-compose -f docker-compose.yml up -d
Spúšťam testrail-mysql-local ... hotovo
Spúšťam testrail-migration-local ... hotovo
Spúšťam testrail-fpm-local ... hotovo
Obnovujem testrail-web-local ... hotovoAle nie všetko je také jednoduché so spustením v CI.
Príprava Makefile
Používam Makefile, pretože je to veľmi praktické, ako pre lokálne riadenie prostredia, tak aj v CI. Ďalej nasledujú inline komentáre
# У меня в проектах все вспомогательные вещи лежат в директории `.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
Overujeme
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml kill
Zabíjam testrail-web-local ... hotovo
Zabíjam testrail-fpm-local ... hotovo
Zabíjam testrail-mysql-local ... hotovo
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
Načítam db ... hotovo
Načítam migráciu ... hotovo
Načítam fpm ... hotovo
Načítam web ... hotovo
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Znovu vytváram testrail-mysql-local ... hotovo
Znovu vytváram testrail-fpm-local ... hotovo
Znovu vytváram testrail-migration-local ... hotovo
Znovu vytváram testrail-web-local ... hotovo
docker ps
ID KONTÉNERA PORTY NÁZVY
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: nemôžem vytvoriť adresár ‘.\/logs’: Adresár už existuje
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
Príprava .gitlab-ci.yml
Spustenie integračných testov
Integrácia:
fáza: test
tagy:
- my-shell-runner
before_script:
# Prihlasujeme sa do registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Generujeme pseudounikátne TR_HTTP_PORT a TR_HTTPS_PORT
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# vytvárame adresár s identifikátorom úlohy
- mkdir ${CI_JOB_ID}
# kopírujeme do vytvoreného adresára náš docker-compose.yml
# aby bol kontext rôzny pre každú úlohu
- cp .indirect\/docker-compose.yml ${CI_JOB_ID}\/docker-compose.yml
script:
# spúšťame naše prostredie
- make docker-up
# spúšťame testy vykonateľným jar (tak to mám)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# alebo v konte
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# zbierame logy
- make docker-logs
# zastavujeme prostredie
- make docker-kill
artifacts:
# uchovávame logy
when: always
paths:
- logs
expire_in: 30 dníVýsledkom spustenia takejto úlohy v artefaktoch bude adresár logs, ktorý bude obsahovať logy služieb a testov. Čo je veľmi praktické v prípade vzniku chýb. Mám každý test paralelne, ktorý zapisuje svoj log, ale o tom poviem podrobnejšie osobitne.

Čistenie runnera
Úloha sa bude spúšťať iba podľa harmonogramu.
stages:
- clean
- build
- test
Clean runner:
fáza: clean
only:
- schedules
tagy:
- my-shell-runner
script:
- make docker-cleanĎalej prejdeme do nášho projektu GitLab -> CI/CD -> Rozvrhy -> Nový rozvrh a pridáme nový harmonogram
Výsledok
Spúšťame 4 úlohy v GitLab CI
V logoch poslednej úlohy s integračnými testami vidíme kontajnery z rôznych úloh
ID KONTÉNRA MENÁ
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-204553539Podrobnejší log
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
Upozornenie! Používanie --password cez CLI je nebezpečné. Použite --password-stdin.
Upozornenie! Vaše heslo bude uložené v nezakódovanej podobe v /home/gitlab-runner/.docker/config.json.
Nakonfigurujte pomocníka pre povolenia, aby ste odstránili toto varovanie. Pozrite
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Prihlásenie úspešné
$ 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
Chyba: Taká sieť neexistuje: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Načítavam web ... hotovo
Načítavam fpm ... hotovo
Načítavam migráciu ... hotovo
Načítavam db ... hotovo
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Vytváram objem "204645172_static-content" s predvoleným ovládačom
Vytváram testrail-mysql-204645172 ...
Vytváram testrail-mysql-204645172 ... hotovo
Vytváram testrail-migration-204645172 ... hotovo
Vytváram testrail-fpm-204645172 ... hotovo
Vytváram testrail-web-204645172 ... hotovo
docker ps
ID KONTÉRA OBRAZ PRÍKAZ VYTVORENÉ STATUS PORTY NÁZVY
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" pred 13 sekundami Beží 1 sekundu 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…" pred 16 sekundami Beží 13 sekúnd 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" pred 16 sekundami Beží 13 sekúnd 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" pred 18 sekundami Beží 16 sekúnd 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" pred 36 sekundami Beží 22 sekundy 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…" pred 38 sekundami Beží 35 sekúnd 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" pred 40 sekundami Beží 37 sekúnd 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" pred 1 minútou Beží 53 sekúnd 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…" pred 1 minútou Beží asi 1 minútu 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" pred 1 minútou Beží asi 1 minútu 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" pred 1 minútou Beží asi 1 minútu 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…" pred 1 minútou Beží asi 1 minútu 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" pred 1 minútou Beží asi 1 minútu 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" pred 5 hodinami Beží 5 hodín 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…" pred 5 hodinami Beží 5 hodín 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" pred 5 hodinami Beží 5 hodín 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" pred 5 hodinami Beží 5 hodín 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…" pred 5 hodinami Beží 5 hodín 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" pred 5 hodinami Beží 5 hodín 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" pred 5 hodinami Beží 5 hodín 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…" pred 5 hodinami Beží 5 hodín 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" pred 5 hodinami Beží 5 hodín 3306/tcp testrail-mysql-204553539Všetky úlohy boli úspešne dokončené
Artefakty úlohy obsahujú logy služieb a testov
Vyzerá to dobre, ale existuje jeden detail. Pipeline môže byť nútene zrušený počas vykonávania integračných testov, a v takom prípade sa spustené kontajnery nezastavia. Od času na čas je potrebné čistiť runner. Bohužiaľ, úloha na vylepšenie v GitLab CE je stále v stave
Ale máme pridanie plánovania spustenia úlohy a nikto nám nezakazuje ju spustiť manuálne.
Prejdeme do nášho projektu -> CI/CD -> Plány a spustíme úlohu Vyčistiť runner
Celkovo:
- Máme jeden shell runner.
- Neexistujú žiadne konflikty medzi úlohami a prostredím.
- Máme paralelné spúšťanie úloh s integračnými testami.
- Integrácie testy môžeme spúšťať ako lokálne, tak aj v kontajneri.
- Logy služieb a testov sa zhromažďujú a pripájajú k pipeline úlohe.
- Existuje možnosť čistenia runnera od starých docker obrazov.
Čas nastavenia — ~2 hodiny.
To je vlastne všetko. Teším sa na spätnú väzbu.
Zdroj: habr.com
