Questo articolo sarà interessante sia per i tester che per gli sviluppatori, ma è principalmente rivolto agli automatizzatori che si sono trovati a fronteggiare il problema della configurazione di GitLab CI/CD per l'esecuzione di test di integrazione in presenza di risorse infrastrutturali insufficienti e/o assenza di una piattaforma di orchestrazione dei container. Spiegherò come configurare il dispiegamento degli ambienti da test utilizzando docker compose su un unico runner GitLab shell, in modo che, durante il dispiegamento di più ambienti, i servizi in esecuzione non si disturbino a vicenda.
Contenuto
Prerequisiti
Nella mia esperienza, mi è capitato spesso di "curare" il test di integrazione nei progetti. E spesso il primo e più significativo problema è rappresentato dal pipeline CI in cui il test di integrazione del servizio(e) sviluppato viene eseguito nell'ambiente dev/stage. Questo ha causato non pochi problemi: A causa di difetti in uno o più servizi, nel processo di test di integrazione il contorno di test può essere rovinato da dati corrotti. Ci sono stati casi in cui l'invio di una richiesta con un formato JSON corrotto bloccava il servizio, portando il sistema in uno stato completamente non funzionante.
- Rallentamento del funzionamento del contorno di test con l'aumento dei dati di test. Penso che descrivere un esempio di pulizia/rollback del database non abbia senso. Nella mia esperienza, non ho mai incontrato un progetto in cui questa procedura sia stata eseguita senza intoppi.
- Rischio di compromettere la funzionalità del contorno di test durante la verifica delle impostazioni di sistema comuni. Ad esempio, user/group/password/application policy.
- I dati di test degli autotest ostacolano il lavoro dei tester manuali.
- Qualcuno dirà che i buoni autotest dovrebbero pulire i dati dopo di sé. Ho delle obiezioni a riguardo:
Gli stand dinamici sono molto comodi da usare.
- Non ogni oggetto può essere rimosso dal sistema tramite API. Ad esempio, la chiamata per la rimozione di un oggetto non è implementata, poiché contraddice la logica aziendale.
- Quando si crea un oggetto tramite API, possono essere generati un'enorme quantità di metadati che è difficile rimuovere.
- Se i test hanno dipendenze tra di loro, il processo di pulizia dei dati dopo l'esecuzione dei test diventa un vero e proprio problema.
- Chiamate aggiuntive (e, secondo me, non giustificate) all'API.
- Dati non validi
- E il principale argomento: quando i dati di test iniziano a essere ripuliti direttamente dal DB. Questo si trasforma in un vero circo PK/FK! Si sente dagli sviluppatori: «Ho solo aggiunto/rimosso/rinominato una tabella, perché 100500 test di integrazione sono falliti?»
A mio parere, la soluzione più ottimale è un ambiente dinamico.
- Molti usano docker-compose per avviare l'ambiente di test, ma pochi lo utilizzano durante i test di integrazione in CI/CD. E qui non tengo conto di kubernetes, swarm e altre piattaforme di orchestrazione di container. Non in tutte le aziende sono presenti. Sarebbe utile avere un docker-compose.yml universale.
- Se anche abbiamo il nostro runner QA, come possiamo fare in modo che i servizi avviati tramite docker-compose non interferiscano tra loro?
- Come raccogliere i log dei servizi testati?
- Come ripulire il runner?
Ho un mio runner GitLab per i miei progetti e ho affrontato queste domande durante lo sviluppo per . E più precisamente durante l'avvio dei test di integrazione. Discuteremo di queste questioni con esempi di questo progetto.
GitLab Shell Runner
Per il runner, consiglio una VM Linux con 4 vCPU, 4 GB di RAM e 50 GB di HDD.
Ci sono molte informazioni online su come configurare gitlab-runner, quindi in breve:
- Accediamo alla macchina tramite SSH
Se hai meno di 8 GB di RAM, ti consiglio di , per evitare che l'OOM killer entri in scena e uccida i nostri processi per mancanza di RAM. Questo può succedere quando più di 5 operazioni vengono eseguite contemporaneamente. Di conseguenza, i compiti procederanno più lentamente, ma in modo stabile.
Esempio con OOM killer
Se nei log del lavoro vedi
bash: line 82: 26474 Killed, basta eseguire sul 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:0kBE se la situazione appare in questo modo, dovrai aggiungere swap o aumentare la RAM.
- Installiamo , , , make.
- Aggiungiamo l'utente
gitlab-runneral gruppodockersudo groupadd docker sudo usermod -aG docker gitlab-runner - gitlab-runner.
Apriamo per la modifica
/etc/gitlab-runner/config.tomle aggiungiamoconcurrent=20 [[runners]] request_concurrency = 10Questo permetterà di eseguire compiti paralleli su un unico runner. Maggiori dettagli. .
Se hai una macchina più potente, ad esempio 8 vCPU, 16 GB di RAM, puoi aumentare questi numeri di almeno 2 volte. Ma tutto dipende da cosa verrà eseguito su questo runner e in quale quantità.
Questo è sufficiente.
Preparazione del docker-compose.yml
Il compito principale è creare un docker-compose.yml universale che sviluppatori/tester possano utilizzare sia localmente che nel CI pipeline.
In primo luogo, creiamo nomi unici per i servizi nel CI. Una delle variabili uniche in GitLab CI è la variabile CI_JOB_ID. Se specifichi container_name con valore "service-${CI_JOB_ID:-local}", in caso di:
- se
CI_JOB_IDnon definita nelle variabili ambientali,
il nome del servizio saràservice-local - se
CI_JOB_IDdefinita nelle variabili ambientali (ad esempio 123),
il nome del servizio saràservice-123
In secondo luogo, creiamo una rete comune per i servizi in esecuzione. Questo ci fornisce isolamento a livello di rete durante l'esecuzione di più ambienti di test.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}In effetti, questo è il primo passo verso il successo =)
Esempio del mio docker-compose.yml con commenti
version: "3"
# Per un corretto funzionamento di web (php) e fmt, è necessario,
# che i contenitori abbiano contenuti eseguibili comuni.
# Nel nostro caso, si tratta della directory /var/www/testrail
volumes:
static-content:
# Isoliamo l'ambiente a livello di rete
networks:
default:
external:
name: testrail-network-${CI_JOB_ID:-local}
services:
db:
image: mysql:5.7.22
# Ogni container_name include ${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}"
# Se le variabili TR_HTTP_PORT o TR_HTTPS_PORTS non sono definite,
# il servizio verrà avviato sulle porte 80 e 443 rispettivamente.
ports:
- ${TR_HTTP_PORT:-80}:80
- ${TR_HTTPS_PORT:-443}:443
volumes:
- static-content:/var/www/testrail
links:
- db
- fpm
networks:
- defaultEsempio di esecuzione locale
docker-compose -f docker-compose.yml up -d
Avviando testrail-mysql-local ... completato
Avviando testrail-migration-local ... completato
Avviando testrail-fpm-local ... completato
Ricreando testrail-web-local ... completatoMa non è tutto così semplice con l'esecuzione nel CI.
Preparazione del Makefile
Utilizzo Makefile, poiché è molto comodo sia per la gestione locale dell'ambiente che per il CI. Di seguito ci sono commenti in linea.
# У меня в проектах все вспомогательные вещи лежат в директории `.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
Controlliamo
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Uccisione di testrail-web-local ... completato
Uccisione di testrail-fpm-local ... completato
Uccisione di testrail-mysql-local ... completato
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
Recupero db ... completato
Recupero migration ... completato
Recupero fpm ... completato
Recupero web ... completato
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Ricreazione di testrail-mysql-local ... completato
Ricreazione di testrail-fpm-local ... completato
Ricreazione di testrail-migration-local ... completato
Ricreazione di testrail-web-local ... completato
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: impossibile creare la directory ‘./logs’: la directory esiste già
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
Preparazione del .gitlab-ci.yml
Avvio dei test di integrazione
Integrazione:
fase: test
tag:
- my-shell-runner
before_script:
# Effettuiamo il login nel registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Generiamo TR_HTTP_PORT e TR_HTTPS_PORT pseudounici
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# creiamo la directory con l'identificativo del job
- mkdir ${CI_JOB_ID}
# copiamo il nostro docker-compose.yml nella directory creata
# affinché il contesto sia diverso per ogni job
- cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
script:
# avviamo il nostro ambiente
- make docker-up
# eseguiamo i test con jar eseguibile (così è per me)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# oppure nel container
- docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
after_script:
# raccogliamo i log
- make docker-logs
# fermiamo l'ambiente
- make docker-kill
artifact:
# salviamo i log
when: always
paths:
- logs
expire_in: 30 giorniIl risultato dell'esecuzione di un tale job in artefatti sarà che la directory logs conterrà i log dei servizi e dei test. Questo è molto comodo in caso di errori. Ogni mio test scritto in parallelo genera il proprio log, ma di questo ne parlerò separatamente.

Pulizia del runner
Il job verrà eseguito solo su base programmata.
stages:
- clean
- build
- test
Pulizia runner:
fase: clean
only:
- schedules
tag:
- my-shell-runner
script:
- make docker-cleanSuccessivamente andiamo al nostro progetto GitLab -> CI/CD -> Programmazioni -> Nuova programmazione e aggiungiamo un nuovo calendario
Risultato
Avviamo 4 attività in GitLab CI
Nei log dell'ultima attività con i test di integrazione vediamo contenitori da diverse attività
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-204553539Log più dettagliato
$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
ATTENZIONE! L'uso di --password tramite CLI è insicuro. Usa --password-stdin.
ATTENZIONE! La tua password sarà memorizzata in chiaro in /home/gitlab-runner/.docker/config.json.
Configura un helper per le credenziali per rimuovere questo avviso. Vedi
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Accesso riuscito
$ 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
Errore: rete non trovata: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Scaricamento web ... completato
Scaricamento fpm ... completato
Scaricamento migrazione ... completato
Scaricamento db ... completato
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Creazione volume "204645172_static-content" con driver predefinito
Creazione testrail-mysql-204645172 ...
Creazione testrail-mysql-204645172 ... completato
Creazione testrail-migration-204645172 ... completato
Creazione testrail-fpm-204645172 ... completato
Creazione testrail-web-204645172 ... completato
docker ps
ID CONTENITORE IMMAGINE COMANDO CREATO STATO PORTE NOME
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 secondi fa In esecuzione da 1 secondo 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 secondi fa In esecuzione da 13 secondi 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 secondi fa In esecuzione da 13 secondi 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 secondi fa In esecuzione da 16 secondi 3306/tcp testrail-mysql-204645172
6dbb3fae0322 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 36 secondi fa In esecuzione da 22 secondi 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 secondi fa In esecuzione da 35 secondi 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 secondi fa In esecuzione da 37 secondi 3306/tcp testrail-mysql-204645084
d8aa24b2892d registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Circa un minuto fa In esecuzione da 53 secondi 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…" Circa un minuto fa In esecuzione da un minuto 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" Circa un minuto fa In esecuzione da un minuto 3306/tcp testrail-mysql-204644881
1cdfc692003a registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" Circa un minuto fa In esecuzione da un minuto 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…" Circa un minuto fa In esecuzione da un minuto 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" Circa un minuto fa In esecuzione da un minuto 3306/tcp testrail-mysql-204644793
c10443222ac6 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 ore fa In esecuzione da 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…" 5 ore fa In esecuzione da 5 ore 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" 5 ore fa In esecuzione da 5 ore 3306/tcp testrail-mysql-204567103
b66b60d79e43 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 ore fa In esecuzione da 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…" 5 ore fa In esecuzione da 5 ore 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" 5 ore fa In esecuzione da 5 ore 3306/tcp testrail-mysql-204553690
069954ba6010 registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 5 ore fa In esecuzione da 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…" 5 ore fa In esecuzione da 5 ore 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" 5 ore fa In esecuzione da 5 ore 3306/tcp testrail-mysql-204553539Tutti i compiti sono stati completati con successo
Gli artefatti del compito contengono i log dei servizi e dei test
Tutto sembra a posto, ma c'è un piccolo problema. Il pipeline può essere annullato forzatamente durante l'esecuzione dei test di integrazione, e in questo caso i contenitori avviati non verranno fermati. Di tanto in tanto è necessario pulire il runner. Sfortunatamente, il compito di revisione in GitLab CE è ancora in fase di
Ma abbiamo aggiunto l'avvio del compito secondo la pianificazione, e nessuno ci vieta di avviarlo manualmente.
Andiamo al nostro progetto -> CI/CD -> Pianificazioni e avviamo il compito Pulisci runner
In totale:
- Abbiamo un runner shell.
- Non ci sono conflitti tra i compiti e l'ambiente.
- Abbiamo un'esecuzione parallela dei compiti con i test di integrazione.
- È possibile eseguire i test di integrazione sia localmente sia in un contenitore.
- I log dei servizi e dei test vengono raccolti e allegati al compito del pipeline.
- C'è la possibilità di pulire il runner da vecchi docker image.
Tempo di configurazione — ~2 ore.
Ecco, in sostanza, tutto. Sarò felice di ricevere feedback.
Fonte: habr.com
