Acest articol va fi de interes atât pentru testeri, cât și pentru dezvoltatori, dar se adresează în mare parte automatizatorilor care s-au confruntat cu problema configurării GitLab CI/CD pentru realizarea testării de integrare în condiții de resurse infrastructurale insuficiente și/sau absența unei platforme de orchestrare a containerelor. Voi explica cum să configurezi desfășurarea mediilor testate folosind docker compose pe un singur GitLab shell runner, astfel încât, la desfășurarea mai multor medii, serviciile lansate să nu interfereze între ele.
Cuprins
Premise
În practica mea, s-a întâmplat adesea să "rezolv" testarea de integrare în proiecte. Și adesea, prima și cea mai semnificativă problemă este CI pipeline-ul, în care testarea de integrare a serviciului(elor) în dezvoltare se desfășoară în mediul dev/stage. Aceasta a generat nu puține probleme: Din cauza defectelor în unul sau alt serviciu, în timpul testării de integrare, conturul de testare poate fi afectat de date corupte. Au existat cazuri când trimiterea unei solicitări cu un format JSON corupt bloca serviciul, ceea ce ducea la un stadiu complet nefuncțional.
- Încetinirea funcționării conturului de testare pe măsură ce datele de testare cresc. Cred că nu are rost să descriu un exemplu cu curățarea/întoarcerea bazei de date. În practica mea, nu am întâlnit un proiect în care această procedură să decurgă fără probleme.
- Riscul de a afecta funcționarea conturului de testare în timpul testării setărilor comune ale sistemului. De exemplu, utilizator/group/parolă/politica aplicației.
- Datele de testare generate de testele automate interferează cu activitatea testerilor manuali.
- Cineva ar putea spune că testele automate bune ar trebui să curețe datele după ele. Am argumente împotriva acestei afirmații:
Standuri dinamice sunt foarte ușor de folosit.
- Nu fiecare obiect poate fi șters din sistem prin API. De exemplu, apelul pentru ștergerea unui obiect nu este implementat, deoarece contravine logicii de afaceri.
- Atunci când se creează un obiect prin API, se pot genera o cantitate imensă de metadate, care sunt greu de șters.
- La crearea unui obiect prin API, pot fi generate o mulțime de metadate, care sunt dificil de șters.
- Dacă testele au dependențe între ele, procesul de curățare a datelor după ce testele s-au finalizat devine o durere de cap.
- Apeluri suplimentare (și, din punctul meu de vedere, nejustificate) către API.
- Și argumentul principal: când datele de testare încep să fie curățate direct din Baza de Date. Acest lucru se transformă într-un adevărat circ PK/FK! De la dezvoltatori se aude: „Am adăugat/eliminat/renumit doar o tabelă, de ce 100500 de teste de integrare au fost afectate?”
În opinia mea, cea mai optimă soluție este un mediu dinamic.
- Mulți folosesc docker-compose pentru a lansa un mediu de testare, dar puțini folosesc docker-compose în timpul executării testelor de integrare în CI/CD. Și aici nu iau în considerare Kubernetes, swarm și alte platforme de orchestrare a containerelor. Nu toate companiile au aceste soluții. Ar fi bine dacă docker-compose.yml ar fi universal.
- Chiar dacă avem un QA runner propriu, cum putem face astfel încât serviciile lansate prin docker-compose să nu interfereze între ele?
- Cum să colectăm jurnalele serviciilor testate?
- Cum să curățăm runner-ul?
Am un GitLab runner propriu pentru proiectele mele și m-am confruntat cu aceste întrebări în timpul dezvoltării pentru . Mai precis, în timpul lansării testelor de integrare. Aici vom aborda aceste întrebări cu exemple din acest proiect.
GitLab Shell Runner
Pentru runner, recomand o mașină virtuală Linux cu 4 vCPU, 4 GB RAM, 50 GB HDD.
Există foarte multe informații pe internet despre configurarea gitlab-runner, așa că, pe scurt:
- Accesați mașina prin SSH
Dacă aveți mai puțin de 8 GB RAM, recomand , pentru a evita OOM killer și a nu ne omorî sarcinile din cauza lipsei de RAM. Acest lucru se poate întâmpla atunci când mai mult de 5 sarcini sunt lansate simultan. Sarcinile vor rula mai lent, dar stabil.
Exemplu cu OOM killer
Dacă în jurnalele sarcinii vedeți
bash: line 82: 26474 Killed, atunci executați pe runnersudo 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Și dacă imaginea arată cam așa, atunci fie adăugați swap, fie suplimentați RAM-ul.
- Instalăm , , , face.
- Adăugăm utilizatorul
gitlab-runnerîn grupuldockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Deschidem spre editare
/etc/gitlab-runner/config.tomlși adăugămconcurrent=20 [[runners]] request_concurrency = 10Aceasta va permite rularea sarcinilor paralele pe un singur runner. Citiți mai multe detalii .
Dacă aveți un server mai puternic, de exemplu 8 vCPU, 16 GB RAM, atunci aceste cifre pot fi cel puțin de două ori mai mari. Dar totul depinde de ce anume va fi rulat pe acest runner și în ce cantitate.
Aceasta este suficient.
Pregătirea docker-compose.yml
Obiectivul principal este un docker-compose.yml universal, pe care dezvoltatorii/testerii îl pot folosi atât local, cât și în pipeline-ul CI.
În primul rând, facem nume unice pentru servicii în CI. Una dintre variabilele unice în GitLab CI este variabila CI_JOB_ID. Dacă specificați container_name cu valoarea "service-${CI_JOB_ID:-local}", atunci în cazul:
- dacă
CI_JOB_IDnu este definită în variabilele de mediu,
atunci numele serviciului va fiservice-local - dacă
CI_JOB_IDeste definită în variabilele de mediu (de exemplu 123),
atunci numele serviciului va fiservice-123
În al doilea rând, facem o rețea comună pentru serviciile rulate. Acest lucru ne oferă izolare la nivel de rețea atunci când rulăm mai multe medii de testare.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}Aceasta este primul pas către succes =)
Un exemplu al meu de docker-compose.yml cu comentarii
version: "3"
# Pentru funcționarea corectă a web (php) și fmt, trebuie
# ca containerele să aibă un conținut executabil comun.
# În cazul nostru, aceasta este directorul /var/www/testrail
volumes:
static-content:
# Izolăm mediul la nivel de rețea
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Fiecare container_name conține ${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/tesrail/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}"
# Dacă variabilele TR_HTTP_PORT sau TR_HTTPS_PORTS nu sunt definite,
# atunci serviciul va fi lansat pe porturile 80 și 443 respective.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultUn exemplu de lansare locală
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 ... doneDar nu este atât de simplu cu lansarea în CI.
Pregătirea Makefile
Folosesc Makefile pentru că este foarte convenabil atât pentru gestionarea mediului local, cât și pentru CI. Urmează comentariile inline
# У меня в проектах все вспомогательные вещи лежат в директории `.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
Verificăm
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml kill
Oprind testrail-web-local ... finalizat
Oprind testrail-fpm-local ... finalizat
Oprind testrail-mysql-local ... finalizat
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
Se coboară db ... finalizat
Se coboară migration ... finalizat
Se coboară fpm ... finalizat
Se coboară web ... finalizat
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Recreez testrail-mysql-local ... finalizat
Recreez testrail-fpm-local ... finalizat
Recreez testrail-migration-local ... finalizat
Recreez testrail-web-local ... finalizat
docker ps
ID CONTAINER PORTURI NUME
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: nu se poate crea directorul ‘.\/logs’: Directorul există
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
Pregătirea .gitlab-ci.yml
Lansarea testelor de integrare
Integrare:
etapă: test
etichete:
- my-shell-runner
before_script:
# Ne autentificăm în registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Generăm TR_HTTP_PORT și TR_HTTPS_PORT pseudo-unic
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# Cream un director cu ID-ul job-ului
- mkdir ${CI_JOB_ID}
# Copiem în directorul creat docker-compose.yml nostru
# pentru ca contextul să fie diferit pentru fiecare job
- cp .indirect\/docker-compose.yml ${CI_JOB_ID}\/docker-compose.yml
script:
# Ridicăm mediul nostru
- make docker-up
# Executăm teste cu jar executabil (așa fac eu)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# sau într-un container
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# Colectăm logurile
- make docker-logs
# Oprim mediul
- make docker-kill
artefacte:
# Salvăm logurile
când: întotdeauna
căi:
- logs
expiră_in: 30 zileCa urmare a rulării unei astfel de sarcini, în artefacte, directorul logs va conține logurile serviciilor și testelor. Acest lucru este foarte convenabil în caz de erori. Fiecare test pe paralel scrie propriul log, dar despre asta voi povesti separat.

Curățarea runner-ului
Sarcina va fi executată doar conform programului.
etape:
- curățare
- construire
- test
Curățare runner:
etapă: curățare
doar:
- programe
etichete:
- my-shell-runner
script:
- make docker-cleanApoi mergem în proiectul nostru GitLab -> CI/CD -> Programări -> Programare nouă și adăugăm un nou program
Rezultatul
Executăm 4 sarcini în GitLab CI
În jurnalele celei de-a patra sarcini cu teste de integrare vedem containere de la diferite sarcini
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-204553539Jurnal mai detaliat
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
ATENȚIE! Utilizarea --password prin CLI este nesigură. Folosiți --password-stdin.
ATENȚIE! Parola dumneavoastră va fi stocată necriptată în /home/gitlab-runner/.docker/config.json.
Configurați un ajutor pentru acreditive pentru a elimina acest avertisment. Consultați
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Autentificare reușită
$ 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
Eroare: Nu există o rețea: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Se trag imagini web ... finalizat
Se trag imagini fpm ... finalizat
Se trag imagini migrare ... finalizat
Se trag imagini db ... finalizat
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Se creează volumul "204645172_static-content" cu driverul implicit
Se creează testrail-mysql-204645172 ...
Se creează testrail-mysql-204645172 ... finalizat
Se creează testrail-migrare-204645172 ... finalizat
Se creează testrail-fpm-204645172 ... finalizat
Se creează testrail-web-204645172 ... finalizat
docker ps
ID CONTAINER IMAGINE COMANDĂ CREAT STATUS PORȚI NUME
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 secunde în urmă Activ 1 secundă 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 secunde în urmă Activ 13 secunde 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 secunde în urmă Activ 13 secunde 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 secunde în urmă Activ 16 secunde 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 36 secunde în urmă Activ 22 secunde 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 secunde în urmă Activ 35 secunde 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 secunde în urmă Activ 37 secunde 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Aproape un minut în urmă Activ 53 secunde 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…" Aproape un minut în urmă Activ Aproape un minut 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" Aproape un minut în urmă Activ Aproape un minut 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Aproape un minut în urmă Activ Aproape un 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…" Aproape un minut în urmă Activ Aproape un minut 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" Aproape un minut în urmă Activ Aproape un minut 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Cu 5 ore în urmă Activ 5 ore 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…" Cu 5 ore în urmă Activ 5 ore 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" Cu 5 ore în urmă Activ 5 ore 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Cu 5 ore în urmă Activ 5 ore 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…" Cu 5 ore în urmă Activ 5 ore 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" Cu 5 ore în urmă Activ 5 ore 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Cu 5 ore în urmă Activ 5 ore 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…" Cu 5 ore în urmă Activ 5 ore 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" Cu 5 ore în urmă Activ 5 ore 3306/tcp testrail-mysql-204553539Toate sarcinile au fost finalizate cu succes
Artefactele sarcinii conțin jurnale ale serviciilor și testelor
Totul pare în regulă, dar există un detaliu. Pipeline-ul poate fi anulat forțat în timpul executării testelor de integrare, iar în acest caz, containerele lansate nu vor fi oprite. Din când în când, trebuie curățat runner-ul. Din păcate, sarcina de îmbunătățire în GitLab CE este încă în status
Dar avem adăugată execuția sarcinii conform unui program, iar nimeni nu ne interzice să o lansăm manual.
Trecem la proiectul nostru -> CI/CD -> Schedules și lansăm sarcina Curăță runner
În total:
- Avem un shell runner.
- Nu există conflicte între sarcini și mediu.
- Avem execuția paralelă a sarcinilor cu testele de integrare.
- Testele de integrare pot fi lansate atât local, cât și în container.
- Jurnalele serviciilor și testelor sunt adunate și atașate la sarcina pipeline.
- Există posibilitatea de a curăța runner-ul de vechile imagini docker.
Timp de configurare — ~2 ore.
Cam asta e tot. Aș fi bucuros să primesc feedback.
Sursa: habr.com
