Acest articol va fi de interes atât pentru testeri, cât și pentru dezvoltatori, dar este destinat în principal specialiștilor în automatizare care se confruntă cu problema instalării GitLab CI/CD pentru testarea integrării în condiții de resurse insuficiente de infrastructură și/sau absența unei orchestrații container. platformă. Vă voi spune cum să configurați implementarea mediilor de testare folosind docker compose pe un singur runner shell GitLab și astfel încât, atunci când implementați mai multe medii, serviciile lansate să nu interfereze între ele.
În practica mea, sa întâmplat adesea ca testarea integrării să fie „tratată” pe proiecte. Și adesea prima și cea mai semnificativă problemă este conducta CI, în care testarea integrării în curs de dezvoltare serviciul(e) este realizat într-un mediu de dezvoltare/etapă. Acest lucru a cauzat destul de multe probleme:
Din cauza defectelor unui anumit serviciu în timpul testării integrării, circuitul de testare poate fi deteriorat de date întrerupte. Au existat cazuri când trimiterea unei solicitări cu un format JSON rupt a blocat serviciul, ceea ce a făcut standul complet inoperabil.
Încetinirea circuitului de testare pe măsură ce datele de testare cresc. Cred că nu are sens să descriem un exemplu cu curățarea/rularea înapoi a bazei de date. În practica mea, nu am întâlnit un proiect în care această procedură a decurs fără probleme.
Risc de întrerupere a funcționalității circuitului de testare la testarea setărilor generale ale sistemului. De exemplu, utilizator/grup/parolă/politica aplicației.
Datele de testare din testele automate fac viața dificilă pentru testeri manuali.
Unii vor spune că autotestele bune ar trebui să curețe datele după sine. Am argumente impotriva:
Standurile dinamice sunt foarte convenabile de utilizat.
Nu orice obiect poate fi eliminat din sistem prin intermediul API-ului. De exemplu, un apel de ștergere a unui obiect nu a fost implementat deoarece contrazice logica de afaceri.
Când se creează un obiect prin intermediul API-ului, se poate crea o cantitate mare de metadate, ceea ce este problematic de șters.
Dacă testele au dependențe între ele, atunci procesul de curățare a datelor după rularea testelor se transformă într-o durere de cap.
Apeluri suplimentare (și, în opinia mea, nejustificate) către API.
Și argumentul principal: când datele de testare încep să fie șterse direct din baza de date. Acesta se transformă într-un adevărat circ PK/FK! Auzim de la dezvoltatori: „Tocmai am adăugat/am eliminat/am redenumit un semn, de ce au fost prinși 100500 de teste de integrare?”
După părerea mea, cea mai optimă soluție este un mediu dinamic.
Mulți oameni folosesc docker-compose pentru a rula un mediu de testare, dar puțini oameni folosesc docker-compose atunci când efectuează teste de integrare în CI/CD. Și aici nu iau în calcul kubernetes, swarm și alte platforme de orchestrare a containerelor. Nu toate companiile le au. Ar fi bine dacă docker-compose.yml ar fi universal.
Chiar dacă avem propriul nostru QA runner, cum ne putem asigura că serviciile lansate prin docker-compose nu interferează unele cu altele?
Cum se colectează jurnalele serviciilor testate?
Cum să curățați alergătorul?
Am propriul meu GitLab runner pentru proiectele mele și am întâlnit aceste întrebări în timpul dezvoltării Client Java pentru TestRail. Mai exact, atunci când rulează teste de integrare. Mai jos vom rezolva aceste probleme folosind exemple din acest proiect.
Pentru un alergător, recomand o mașină virtuală Linux cu 4 vCPU, 4 GB RAM, 50 GB HDD.
Există o mulțime de informații despre configurarea gitlab-runner pe Internet, așa că pe scurt:
Conectați-vă la mașină prin SSH
Daca ai mai putin de 8 GB RAM, atunci recomand face schimb de 10 GBastfel încât criminalul OOM să nu vină și să ne ucidă sarcinile din cauza lipsei de memorie RAM. Acest lucru se poate întâmpla atunci când mai mult de 5 sarcini sunt lansate simultan. Sarcinile vor progresa mai lent, dar constant.
Exemplu cu OOM killer
Dacă vedeți în jurnalele de activități bash: line 82: 26474 Killed, apoi executați doar pe alergător 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:0kB
Și dacă imaginea arată cam așa, atunci fie adăugați swap, fie adăugați RAM.
Acest lucru vă va permite să rulați sarcini paralele pe un singur alergător. Citeşte mai mult aici.
Dacă aveți o mașină mai puternică, de exemplu 8 vCPU, 16 GB RAM, atunci aceste numere pot fi făcute de cel puțin 2 ori mai mari. Dar totul depinde de ce anume va fi lansat pe acest alergător și în ce cantitate.
Sarcina principală este un docker-compose.yml universal, pe care dezvoltatorii/testerii îl pot folosi atât local, cât și în conducta CI.
În primul rând, creăm nume de servicii unice pentru CI. Una dintre variabilele unice din GitLab CI este variabila CI_JOB_ID. Dacă specificați container_name cu sens "service-${CI_JOB_ID:-local}", apoi în cazul:
dacă CI_JOB_ID nedefinit în variabilele de mediu,
atunci numele serviciului va fi service-local
dacă CI_JOB_ID definit în variabilele de mediu (de exemplu 123),
atunci numele serviciului va fi service-123
În al doilea rând, creăm o rețea comună pentru serviciile lansate. Acest lucru ne oferă izolarea la nivel de rețea atunci când rulăm mai multe medii de testare.
version: "3"
# Для корректной работы web (php) и fmt нужно,
# чтобы контейнеры имели общий исполняемый контент.
# В нашем случае, это директория /var/www/testrail
volumes:
static-content:
# Изолируем окружение на сетевом уровне
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Каждый container_name содержит ${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}"
# Если переменные TR_HTTP_PORT или TR_HTTPS_PORTS не определены,
# то сервис поднимается на 80 и 443 порту соответственно.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- default
Integration:
stage: test
tags:
- my-shell-runner
before_script:
# Аутентифицируемся в registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Генерируем псевдоуникальные TR_HTTP_PORT и TR_HTTPS_PORT
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# создаем директорию с идентификатором задачи
- mkdir ${CI_JOB_ID}
# копируем в созданную директорию наш docker-compose.yml
# чтобы контекст был разный для каждой задачи
- cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
script:
# поднимаем наше окружение
- make docker-up
# запускаем тесты исполняемым jar (у меня так)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# или в контейнере
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# собираем логи
- make docker-logs
# останавливаем окружение
- make docker-kill
artifacts:
# сохраняем логи
when: always
paths:
- logs
expire_in: 30 days
Ca rezultat al rulării unei astfel de sarcini, directorul jurnalelor din artefacte va conține jurnalele de service și de testare. Ceea ce este foarte convenabil în caz de erori. Fiecare test în paralel își scrie propriul jurnal, dar despre asta voi vorbi separat.
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /home/gitlab-runner/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
$ 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
Error: No such network: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Pulling web ... done
Pulling fpm ... done
Pulling migration ... done
Pulling db ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Creating volume "204645172_static-content" with default driver
Creating testrail-mysql-204645172 ...
Creating testrail-mysql-204645172 ... done
Creating testrail-migration-204645172 ... done
Creating testrail-fpm-204645172 ... done
Creating testrail-web-204645172 ... done
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 seconds ago Up 1 second 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 seconds ago Up 13 seconds 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 seconds ago Up 13 seconds 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 seconds ago Up 16 seconds 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 36 seconds ago Up 22 seconds 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 seconds ago Up 35 seconds 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 seconds ago Up 37 seconds 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" About a minute ago Up 53 seconds 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…" About a minute ago Up About a minute 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" About a minute ago Up About a minute 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…" About a minute ago Up About a minute 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 hours ago Up 5 hours 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…" 5 hours ago Up 5 hours 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 hours ago Up 5 hours 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…" 5 hours ago Up 5 hours 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 hours ago Up 5 hours 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…" 5 hours ago Up 5 hours 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" 5 hours ago Up 5 hours 3306/tcp testrail-mysql-204553539
Toate sarcinile au fost finalizate cu succes
Artefactele sarcinilor conțin jurnalele de service și de testare
Totul pare a fi frumos, dar există o nuanță. Conducta poate fi anulată forțat în timpul testelor de integrare, caz în care rularea containerelor nu va fi oprită. Din când în când trebuie să curățați alergătorul. Din păcate, sarcina de îmbunătățire a GitLab CE este încă în stare Operatii Deschise
Dar am adăugat lansarea unei sarcini conform unui program și nimeni nu ne interzice să o rulăm manual.
Accesați proiectul nostru -> CI/CD -> Programe și rulați sarcina Clean runner
Total:
Avem un shell runner.
Nu există conflicte între sarcini și mediu.
Executăm sarcini cu teste de integrare în paralel.
Puteți rula teste de integrare fie local, fie într-un container.
Jurnalele de service și de testare sunt colectate și atașate sarcinii de conductă.
Este posibil să curățați runnerul de imaginile vechi Docker.
Timpul de configurare este de aproximativ 2 ore.
Asta e tot, de fapt. Voi fi bucuros să primesc feedback.