Tento článek bude zajímavý jak pro testery, tak pro vývojáře, ale je zaměřen především na automatizátory, kteří se setkali s problémou nastavení GitLab CI/CD pro provádění integračního testování za podmínek nedostatečných infrastrukturních zdrojů a/nebo absence platformy pro orchestraci kontejnerů. Popíšu, jak nastavit nasazení testovacích prostředí pomocí docker compose na jediném GitLab shell runneru tak, aby se při nasazení několika prostředí spouštěné služby navzájem nezdržovaly.
Obsah
Předpoklady
V mé praxi se často stává, že "léčíme" integrační testování na projektech. A často je prvním a nejvýznamnějším problémem CI pipeline, ve které se integrační testování vyvíjeného služby/služeb provádí v dev/stage prostředí. To způsobilo spoustu problémů:
- V důsledku vad v některé službě může být v průběhu integračního testování testovací obvod poškozen poškozenými daty. Byly případy, kdy odeslání žádosti s poškozeným formátem JSON způsobilo selhání služby, což vedlo k tomu, že se stanoviště stalo zcela nefunkčním.
- Zpomalení práce testovacího obvodu s nárůstem testovacích dat. Myslím, že popisovat příklad s údržbou/vracením databáze nemá cenu. Ve své praxi jsem se nesetkal s projektem, kde by tento proces proběhl hladce.
- Riziko narušení funkčnosti testovacího obvodu při testování obecných systémových nastavení. Například uživatelská/ skupinová/ heslová/ aplikační politika.
- Testovací data z automatizovaných testů ztěžují život manuálním testerům.
Někdo řekne, že dobré automatizované testy by měly po sobě uklízet data. Mám argumenty proti:
- Dynamické stanoviště jsou velmi pohodlná na používání.
- Ne každý objekt lze z systému odstranit přes API. Například vymazání objektu není realizováno, protože odporuje obchodní logice.
- Při vytváření objektu prostřednictvím API může být vytvořeno obrovské množství metadat, které jsou problematická k odstranění.
- Pokud mají testy vzájemnou závislost, proces čištění dat po provedení testů se stává skutečnou bolestí hlavy.
- Další (a podle mě neopodstatněné) výzvy na API.
- A hlavní argument: když se testovací data začínají čistit přímo z databáze. To se promění v opravdový cirkus s PK/FK! Od vývojářů je slyšet: „Já jsem jen přidal/odstranil/přejmenoval tabulku, proč selhalo 100500 integračních testů?“
Podle mého názoru nejoptimálnějším řešením je dynamické prostředí.
- Mnoho lidí používá docker-compose pro spuštění testovacího prostředí, ale málo kdo používá docker-compose při provádění integračního testování v CI/CD. A zde nezohledňuji kubernetes, swarm a další platformy pro orchestraci kontejnerů. Ne každá firma je má. Bylo by dobré, kdyby byl docker-compose.yml univerzální.
- Pokud máme vlastní QA runner, jak můžeme zajistit, aby služby spouštěné přes docker-compose si navzájem nebránily?
- Jak sbírat logy testovaných služeb?
- Jak čistit runner?
Mám vlastní GitLab runner pro své projekty a s těmito otázkami jsem se setkal při vývoji. pro . A konkrétně při spuštění integračních testů. Budeme se zabývat těmito otázkami s příklady z tohoto projektu.
GitLab Shell Runner
Pro runner doporučuji linuxovou virtuálku se 4 vCPU, 4 GB RAM a 50 GB HDD.
Na internetu je spousta informací o nastavení gitlab-runner, proto stručně:
- Přihlaste se do stroje přes SSH
Pokud máte méně než 8 GB RAM, doporučuji , abychom se vyhnuli OOM killeru, který nám nezabíjel úkoly kvůli nedostatku RAM. To se může stát, když je současně spuštěno více než 5 úkolů. Úkoly budou probíhat pomaleji, ale stabilně.
Příklad s OOM killerem
Pokud v logách úkolu uvidíte
bash: line 82: 26474 Killed, jednoduše na runneru proveďtesudo 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 pokud situace vypadá přibližně takto, přidejte swap nebo přidejte RAM.
- Instalujeme , , , make.
- Přidáme uživatele
gitlab-runnerdo skupinydockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Otevřeme k úpravě
/etc/gitlab-runner/config.tomla přidámeconcurrent=20 [[runners]] request_concurrency = 10To umožní spouštět paralelní úkoly na jednom runneru. Čtěte více .
Pokud máte výkonnější stroj, například 8 vCPU, 16 GB RAM, lze tyto čísla zvýšit alespoň dvakrát. Záleží však na tom, co konkrétně bude spuštěno na tomto runneru a v jakém množství.
To je dostatečné.
Příprava docker-compose.yml
Hlavním úkolem je univerzální docker-compose.yml, který mohou vývojáři/testerové používat jak lokálně, tak v CI pipeline.
Nejprve vytváříme jedinečné názvy služeb pro CI. Jednou z jedinečných proměnných v GitLab CI je proměnná CI_JOB_ID. Pokud zadáte container_name s hodnotou "service-${CI_JOB_ID:-local}", pak v případě:
- pokud
CI_JOB_IDnení definována v proměnných prostředí,
pak název služby budeservice-local - pokud
CI_JOB_IDdefinována v proměnných prostředí (např. 123),
pak název služby budeservice-123
Za druhé vytváříme společnou síť pro spouštěné služby. To nám poskytuje izolaci na úrovni sítě při spuštění několika testovacích prostředí.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}To je prvním krokem k úspěchu =)
Příklad mého docker-compose.yml s komentáři
version: "3"
# Pro správnou funkci web (php) a fmt je potřeba,
# aby kontejnery měly společný spustitelný obsah.
# V našem případě je to adresář /var/www/testrail
volumes:
static-content:
# Izolujeme prostředí na síťové úrovni
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}"
# Pokud nejsou proměnné TR_HTTP_PORT nebo TR_HTTPS_PORTS definovány,
# pak se služba spouští na portu 80 a 443.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultPříklad lokálního spuštění
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 ... doneAle ne všechno je tak jednoduché s spuštěním v CI.
Příprava Makefile
Používám Makefile, protože je to velmi praktické jak pro místní správu prostředí, tak i v CI. Následují inline komentáře
# У меня в проектах все вспомогательные вещи лежат в директории `.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
Kontrolujeme
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml kill
Zabíjím testrail-web-local ... hotovo
Zabíjím testrail-fpm-local ... hotovo
Zabíjím 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
Stahuji db ... hotovo
Stahuji migraci ... hotovo
Stahuji fpm ... hotovo
Stahuji web ... hotovo
docker-compose -f ${CI_JOB_ID:-.indirect}\/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Znovu vytvářím testrail-mysql-local ... hotovo
Znovu vytvářím testrail-fpm-local ... hotovo
Znovu vytvářím testrail-migraci-local ... hotovo
Znovu vytvářím testrail-web-local ... hotovo
docker ps
ID KONTEJNERU 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-migraci-local
0e7900c23f37 3306/tcp testrail-mysql-local
make docker-logs
$ make docker-logs
mkdir .\/logs || true
mkdir: nelze vytvořit adresář ‘.\/logs’: Adresář již 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-migraci-${CI_JOB_ID:-local} >& logs\/testrail-migraci.log
docker logs testrail-mysql-${CI_JOB_ID:-local} >& logs\/testrail-mysql.log
Příprava .gitlab-ci.yml
Spuštění integračních testů
Integrace:
fáze: test
štítky:
- my-shell-runner
před-skript:
# Přihlásíme se do registru
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Generujeme pseudounikátní TR_HTTP_PORT a TR_HTTPS_PORT
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# vytvoříme adresář s ID úkolu
- mkdir ${CI_JOB_ID}
# zkopírujeme do vytvořeného adresáře náš docker-compose.yml
# aby byl kontext jiný pro každý úkol
- cp .indirect\/docker-compose.yml ${CI_JOB_ID}\/docker-compose.yml
skript:
# zvedáme naše prostředí
- make docker-up
# spouštíme testy pomocí spustitelného jar (mám to tak)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# nebo v kontejneru
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
po-skriptu:
# shromažďujeme protokoly
- make docker-logs
# zastavujeme prostředí
- make docker-kill
artefakty:
# uchováváme protokoly
když: vždy
cesty:
- logs
vyprší: 30 dníVýsledkem spuštění takového úkolu bude, že v artefaktech adresář logs bude obsahovat protokoly služeb a testů. Což je velmi praktické v případě, že dojde k chybám. Mám každý test, který běží paralelně, zapisuje svůj vlastní protokol, ale o tom budu mluvit zvlášť.

Údržba runneru
Úkol se bude spouštět pouze podle plánu.
stages:
- čistit
- sestavit
- test
Čistící runner:
stage: clean
only:
- schedules
tags:
- my-shell-runner
script:
- make docker-cleanDále přejdeme do našeho projektu GitLab -> CI/CD -> Schedules -> New Schedule a přidáme nový plán
Výsledek
Spouštíme 4 úlohy v GitLab CI
V logu poslední úlohy s integračními testy vidíme kontejnery z různých úloh
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-204553539Podrobnější log
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
UPOZORNĚNÍ! Použití --password prostřednictvím CLI je nezabezpečené. Použijte --password-stdin.
UPOZORNĚNÍ! Vaše heslo bude uloženo v nezabezpečeném formátu v /home/gitlab-runner/.docker/config.json.
Nakonfigurujte pomocníka pro ověřování, abyste odstranili tuto výstrahu. Viz
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Úspěšné přihlášení
$ 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: Neexistuje taková síť: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Stahuji web ... hotovo
Stahuji fpm ... hotovo
Stahuji migraci ... hotovo
Stahuji db ... hotovo
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Vytvářím objem "204645172_static-content" s výchozím ovladačem
Vytvářím testrail-mysql-204645172 ...
Vytvářím testrail-mysql-204645172 ... hotovo
Vytvářím testrail-migration-204645172 ... hotovo
Vytvářím testrail-fpm-204645172 ... hotovo
Vytvářím testrail-web-204645172 ... hotovo
docker ps
ID KONTEJNERU OBRAZ PŘÍKAZ VYTVOŘENO STAV PORTY JMÉNA
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" před 13 sekundami Běží 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…" před 16 sekundami Běží 13 sekund 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" před 16 sekundami Běží 13 sekund 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" před 18 sekundami Běží 16 sekund 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" před 36 sekundami Běží 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…" před 38 sekundami Běží 35 sekund 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" před 40 sekundami Běží 37 sekund 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" před minutou Běží 53 sekundy 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…" před minutou Běží přibližně minutu 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" před minutou Běží přibližně minutu 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" před minutou Běží přibližně 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…" před minutou Běží přibližně minutu 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" před minutou Běží přibližně minutu 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" před 5 hodinami Běží 5 hodin 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…" před 5 hodinami Běží 5 hodin 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" před 5 hodinami Běží 5 hodin 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" před 5 hodinami Běží 5 hodin 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…" před 5 hodinami Běží 5 hodin 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" před 5 hodinami Běží 5 hodin 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" před 5 hodinami Běží 5 hodin 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…" před 5 hodinami Běží 5 hodin 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" před 5 hodinami Běží 5 hodin 3306/tcp testrail-mysql-204553539Všechny úkoly byly úspěšně dokončeny
Artefakty úkolu obsahují logy služeb a testů
Vypadá to všechno dobře, ale je tu jeden háček. Pipeline může být během provádění integračních testů nuceně zrušena, a v takovém případě nebudou spuštěné kontejnery zastaveny. Občas je potřeba vyčistit runner. Bohužel, úkol k úpravám v GitLab CE je stále ve stavu
Ale máme přidáno spuštění úkolu podle plánu a nikdo nám nezakazuje spustit ho ručně.
Přejdeme do našeho projektu -> CI/CD -> Plány a spustíme úkol Čistý runner
Celkem:
- Máme jednoho shell runnera.
- Mezi úkoly a prostředím nejsou žádné konflikty.
- Máme paralelní spuštění úkolů s integračními testy.
- Integraci testů lze spouštět jak lokálně, tak v kontejneru.
- Logy služeb a testů se shromažďují a připojují k pipeline-úkolu.
- Existuje možnost čištění runnera od starých docker obrazů.
Čas nastavení — přibližně 2 hodiny.
To je vlastně všechno. Budu rád za zpětnou vazbu.
Zdroj: habr.com
