Ovaj članak će zanimati i testere i programere, ali je prvenstveno usmjeren na automatizatore koji su se susreli s problemom podešavanja GitLab CI/CD za provođenje integracijskog testiranja u uvjetima nedostatka infrastrukturnih resursa i/ili nedostatka platforme za orkestraciju kontejnera. Objasnit ću kako postaviti implementaciju testnih okruženja pomoću Docker Compose na jednom jedinom GitLab shell runneru, tako da pri implementaciji više okruženja pokretani servisi ne smetaju jedni drugima.
Sadržaj
Preduslovi
U mojoj praksi često se događalo "liječenje" integracijskog testiranja na projektima. I često je prvi i najznačajniji problem CI pipeline, u kojem se integracijsko testiranje razvijenih usluga provodi u dev/stage okruženju. To je izazvalo brojne probleme:
- Zbog grešaka u nekoj usluzi tijekom integracijskog testiranja, testni okruženje može biti oštećeno pokvarenom podacima. Bilo je slučajeva kada je slanje zahtjeva s pokvarenim JSON formatom obustavilo servis, što je dovelo do potpunog nefunkcioniranja okruženja.
- Usporavanje rada testnog okruženja s povećanjem testnih podataka. Mislim da nema smisla opisivati primjer s čišćenjem/rollbackom baze podataka. U svojoj praksi nisam naišao na projekt gdje je ova procedura protekla bez problema.
- Rizik od narušavanja funkcionalnosti testnog okruženja prilikom testiranja zajedničkih postavki sustava. Na primjer, user/group/password/application policy.
- Testni podaci iz automatiziranih testova ometaju rad ručnih testera.
Netko bi rekao da bi dobri automatizirani testovi trebali brisati podatke za sobom. Imam argumente protiv:
- Dinamička okruženja su vrlo pogodna za korištenje.
- Nije svaku objekt moguće izbrisati iz sustava putem API-a. Na primjer, poziv za brisanje objekta nije implementiran, jer se protivi poslovnoj logici.
- Pri stvaranju objekta putem API-a može se stvoriti ogromna količina metapodataka koje je teško izbrisati.
- Ako testovi imaju međusobnu zavisnost, proces čišćenja podataka nakon izvođenja testova postaje prava glavobolja.
- Dodatni (i, po mom mišljenju, neopravdani) pozivi na API.
- I glavni argument: kada testni podaci počnu da se čiste direktno iz baze podataka. To se pretvara u pravi PK/FK cirkus! Od developera se čuje: 'Samo sam dodao/obešio/preimenovao tabelu, zašto je 100500 integracionih testova propalo?'
Po mom mišljenju, najsvrsishodnije rešenje je dinamično okruženje.
- Mnogi koriste docker-compose za pokretanje testnog okruženja, ali malo ko koristi docker-compose prilikom izvođenja integracionih testova u CI/CD. I ovde ne uzimam u obzir kubernetis, swarm i druge platforme za orkestraciju kontejnera. Nisu svake kompanije prisutne. Bilo bi dobro da je docker-compose.yml univerzalan.
- I ako imamo svog QA runnera, kako možemo učiniti da usluge pokretane putem docker-compose ne ometaju jedna drugu?
- Kako prikupiti logove testiranih usluga?
- Kako očistiti runner?
Imam svoj GitLab runner za svoje projekte i sa ovim pitanjima sam se suočio tokom razvoja. za . Tačnije, prilikom pokretanja integracionih testova. Onda ćemo rešavati ova pitanja sa primerima iz ovog projekta.
GitLab Shell Runner
Za runner preporučujem linux virtuelnu mašinu sa 4 vCPU, 4 GB RAM, 50 GB HDD.
Na internetu ima mnogo informacija o podešavanju gitlab-runnera, pa ukratko:
- Prijavite se na mašinu putem SSH.
Ako imate manje od 8 GB RAM, preporučujem , kako OOM killer ne bi ubijao naše zadatke zbog nedostatka RAM-a. To se može desiti kada se istovremeno pokrene više od 5 zadataka. Zadatci će se sporije prolaziti, ali stabilno.
Primer sa OOM killerom
Ako u logovima zadatka vidite
bash: line 82: 26474 Ubijen, jednostavno izvršite na runnerusudo dmesg | grep 26474[26474] 1002 26474 1061935 123806 339 0 0 java Nema dovoljno memorije: Ubiti proces 26474 (java) rezultat 127 ili žrtvovati dete Ubijen proces 26474 (java) ukupno-vm:4247740kB, anon-rss:495224kB, file-rss:0kB, shmem-rss:0kBI ako situacija izgleda otprilike ovako, ili dodajte swap, ili povećajte RAM.
- Instaliramo , , , make.
- Dodajemo korisnika
gitlab-runneru grupudockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Otvorite za uređivanje
/etc/gitlab-runner/config.tomli dodajteconcurrent=20 [[runners]] request_concurrency = 10To će omogućiti pokretanje paralelnih zadataka na jednom runneru. Pročitajte više .
Ako imate jaču mašinu, poput 8 vCPU i 16 GB RAM, onda ove brojke možete povećati barem dvostruko. No, sve ovisi o tome što konkretno će se pokretati na ovom runneru i u kojem broju.
To je dovoljno.
Priprema docker-compose.yml
Glavna zadaća je univerzalni docker-compose.yml koji programeri/testeri mogu koristiti kako lokalno, tako i u CI pipeline-u.
Prvo, radimo jedinstvene nazive servisa za CI. Jedna od jedinstvenih varijabli u GitLab CI je varijabla CI_JOB_ID. Ako navedete container_name s vrijednošću "service-${CI_JOB_ID:-local}", u slučaju:
- ako
CI_JOB_IDnije definirana u varijablama okruženja,
onda će ime servisa bitiservice-local - ako
CI_JOB_IDje definirana u varijablama okruženja (na primjer 123),
onda će ime servisa bitiservice-123
Drugo, kreiramo zajedničku mrežu za pokretane servise. To nam daje izolaciju na razini mreže prilikom pokretanja nekoliko testnih okruženja.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}U stvari, to je prvi korak ka uspjehu =)
Primjer mog docker-compose.yml s komentarima
version: "3"
# Za pravilno funkcioniranje web (php) i fmt potrebni su,
# da konteneri imaju zajednički izvršni sadržaj.
# U našem slučaju, to je direktorij /var/www/testrail
volumes:
static-content:
# Izoliramo okruženje na mrežnoj razini
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Svaki container_name sadrži ${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}"
# Ako varijable TR_HTTP_PORT ili TR_HTTPS_PORTS nisu definirane,
# servis se pokreće na 80 i 443 portu odgovarajuće.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultPrimjer lokalnog pokretanja
docker-compose -f docker-compose.yml up -d
Pokreće testrail-mysql-local ... gotovo
Pokreće testrail-migration-local ... gotovo
Pokreće testrail-fpm-local ... gotovo
Ponovno kreira testrail-web-local ... gotovoAli nije sve tako jednostavno s pokretanjem u CI.
Priprema Makefile
Koristim Makefile jer je vrlo praktičan za lokalno upravljanje okruženjem i u CI. Sljedeći su inline komentari.
# У меня в проектах все вспомогательные вещи лежат в директории `.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
Provjeravamo
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Killing testrail-web-local ... gotov
Killing testrail-fpm-local ... gotov
Killing testrail-mysql-local ... gotov
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 ... gotov
Pulling migration ... gotov
Pulling fpm ... gotov
Pulling web ... gotov
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Ponovno kreiranje testrail-mysql-local ... gotov
Ponovno kreiranje testrail-fpm-local ... gotov
Ponovno kreiranje testrail-migration-local ... gotov
Ponovno kreiranje testrail-web-local ... gotov
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’: Directory already 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
Priprema .gitlab-ci.yml
Pokretanje integracijskih testova
Integracija:
faza: test
oznake:
- my-shell-runner
before_script:
# Autentifikacija u registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Generiranje pseudo-jedinstvenih TR_HTTP_PORT i TR_HTTPS_PORT
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# kreiranje direktorija s ID-jem zadatka
- mkdir ${CI_JOB_ID}
# kopiranje našeg docker-compose.yml u kreirani direktorij
# kako bi kontekst bio različit za svaku zadaću
- cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
script:
# pokretanje našeg okruženja
- make docker-up
# pokretanje testova pomoću jar datoteke (kod mene tako ide)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# ili unutar kontejnera
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# prikupljanje logova
- make docker-logs
# zaustavljanje okruženja
- make docker-kill
artefakti:
# spremanje logova
when: always
paths:
- logs
expire_in: 30 daysKao rezultat pokretanja ovakve zadaće, u artefaktima direktorij logs će sadržavati logove servisa i testova. Što je vrlo korisno u slučaju pojave grešaka. Svaki moj test u paraleli zapisuje svoj log, ali o tome ću posebno govoriti.

Čišćenje runneta
Zadatak će se pokretati samo prema rasporedu.
faze:
- čišćenje
- izgradnja
- test
Čistač:
faza: čišćenje
samo:
- rasporedi
oznake:
- my-shell-runner
skripte:
- make docker-cleanNakon toga idemo u naš GitLab projekt -> CI/CD -> Rasporedi -> Novi raspored i dodajemo novi raspored
Rezultat
Pokrećemo 4 zadatka u GitLab CI
U logovima posljednjeg zadatka s integracijskim testovima vidimo kontejnere iz različitih zadataka
ID KONTENERA IMENA
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-204553539Detaljniji log
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
UPOZORENJE! Korištenje --password putem CLI-a nije sigurno. Koristite --password-stdin.
UPOZORENJE! Vaša lozinka će biti pohranjena nekriptirana u /home/gitlab-runner/.docker/config.json.
Konfigurirajte pomoći za vjerodajnice kako biste uklonili ovu upozorenje. Pogledajte
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Prijava uspješna
$ 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
Greška: Nema takve mreže: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Preuzimanje web ... gotovo
Preuzimanje fpm ... gotovo
Preuzimanje migracija ... gotovo
Preuzimanje db ... gotovo
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Stvaranje volumena "204645172_static-content" s zadanim vozačem
Stvaranje testrail-mysql-204645172 ...
Stvaranje testrail-mysql-204645172 ... gotovo
Stvaranje testrail-migration-204645172 ... gotovo
Stvaranje testrail-fpm-204645172 ... gotovo
Stvaranje testrail-web-204645172 ... gotovo
docker ps
ID KONTAINERA SLIKA NAREDBA STVORENO STATUS PORTOVI IMENA
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" prije 13 sekundi Uključeno 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…" prije 16 sekundi Uključeno 13 sekundi 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" prije 16 sekundi Uključeno 13 sekundi 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" prije 18 sekundi Uključeno 16 sekundi 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" prije 36 sekundi Uključeno 22 sekundi 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…" prije 38 sekundi Uključeno 35 sekundi 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" prije 40 sekundi Uključeno 37 sekundi 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" prije minutu Uključeno 53 sekundi 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…" prije minutu Uključeno minutu 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" prije minutu Uključeno minutu 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" prije minutu Uključeno minutu 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…" prije minutu Uključeno minutu 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" prije minutu Uključeno minutu 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" prije 5 sati Uključeno 5 sati 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…" prije 5 sati Uključeno 5 sati 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" prije 5 sati Uključeno 5 sati 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" prije 5 sati Uključeno 5 sati 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…" prije 5 sati Uključeno 5 sati 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" prije 5 sati Uključeno 5 sati 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" prije 5 sati Uključeno 5 sati 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…" prije 5 sati Uključeno 5 sati 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" prije 5 sati Uključeno 5 sati 3306/tcp testrail-mysql-204553539Sve su zadaće uspješno završene
Artefakti zadaće sadrže logove servisa i testova
Izgleda da je sve u redu, ali postoji jedan problem. Pipeline se može prisilno otkazati tijekom izvođenja integracijskih testova, a u tom slučaju pokrenuti kontejneri neće biti zaustavljeni. Ponekad je potrebno očistiti runner. Nažalost, zadaća za doradu u GitLab CE još uvijek je u statusu.
No, dodali smo pokretanje zadaće po rasporedu, i nitko nam ne brani da ju pokrenemo ručno.
Idemo u naš projekt -> CI/CD -> Rasporedi i pokrećemo zadaću Clean runner
Ukratko:
- Imamo jedan shell runner.
- Nema sukoba između zadaća i okruženja.
- Imamo paralelno pokretanje zadaća s integracijskim testovima.
- Integracijske testove možemo pokretati kako lokalno, tako i u kontejneru.
- Logovi servisa i testova se prikupljaju i prilažu zadaći pipeline-a.
- Postoji mogućnost čišćenja runnera od starih docker slika.
Vrijeme postavljanja — ~2 sata.
To je to. Rado ću primiti povratnu informaciju.
Izvor: habr.com
