Questo articolo sarà di interesse sia per i tester che per gli sviluppatori, ma è maggiormente rivolto agli automatizzatori che si sono trovati ad affrontare il problema della configurazione di GitLab CI/CD per eseguire test di integrazione in condizioni di insufficienti risorse infrastrutturali e/o assenza di una piattaforma di orchestrazione dei container. Spiegherò come configurare il deployment degli ambienti di test utilizzando docker compose su un unico runner GitLab shell, in modo tale che durante il deployment di più ambienti i servizi avviati non si disturbino a vicenda.
Contenuto
Premesse
Nella mia esperienza, mi è capitato spesso di "curare" il testing di integrazione nei progetti. E spesso il primo e più significativo problema è il CI pipeline, in cui il testing di integrazione del servizio (o servizi) sviluppati viene eseguito in un ambiente dev/stage. Questo ha causato non pochi problemi: A causa di difetti in un certo servizio, durante il testing di integrazione il circuito di test può essere compromesso da dati danneggiati. Ci sono stati casi in cui l'invio di una richiesta con un formato JSON danneggiato ha bloccato il servizio, portando l'ambiente in uno stato completamente non funzionante.
- Rallentamenti nel funzionamento del circuito di test con l'aumentare dei dati di test. Penso che sia inutile descrivere un esempio di pulizia/rollback del DB. Nella mia esperienza, non ho mai incontrato un progetto in cui questa procedura sia andata liscia.
- Rischio di compromettere il funzionamento del circuito di test durante la verifica delle impostazioni comuni del sistema. Ad esempio, user/group/password/application policy.
- I dati di test degli autotest ostacolano il lavoro dei tester manuali.
- Qualcuno dirà che buoni autotest dovrebbero pulire i dati dopo di loro. Ho delle argomentazioni contro:
Gli ambienti dinamici sono molto comodi da usare.
- Non ogni oggetto può essere eliminato dal sistema tramite API. Ad esempio, la chiamata all'eliminazione di un oggetto non è implementata, in quanto contraddice la logica aziendale.
- La creazione di un oggetto tramite API può generare una quantità enorme di metadati, che sono problematici da eliminare.
- Se i test dipendono l'uno dall'altro, il processo di pulizia dei dati dopo l'esecuzione dei test diventa un vero e proprio grattacapo.
- Chiamate aggiuntive (e, a mio avviso, ingiustificate) all'API.
- E il principale argomento: quando i dati di test iniziano a essere puliti direttamente dal DB. Questo si trasforma in un vero circo PK/FK! Si sente dire 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 utilizzano docker-compose durante i test di integrazione in CI/CD. E qui non considero kubernetes, swarm e altre piattaforme di orchestrazione dei container. Non tutte le aziende le hanno. Sarebbe interessante se docker-compose.yml fosse universale.
- Se anche abbiamo il nostro runner QA, come possiamo fare in modo che i servizi avviati tramite docker-compose non si disturbino a vicenda?
- Come raccogliere i log dei servizi testati?
- Come pulire il runner?
- Ho il mio runner GitLab per i miei progetti e mi sono trovato di fronte a queste domande durante lo sviluppo
del client Java per Per il runner, consiglio una VM Linux con 4 vCPU, 4 GB di RAM, 50 GB di HDD.
GitLab Shell Runner
Su Internet ci sono molte informazioni sulla configurazione di gitlab-runner, quindi in breve:
Accediamo alla macchina via SSH
- Se avete meno di 8 GB di RAM, consiglio
di creare uno swap di 10 GB Esempio con OOM killer
Se nei log del task vedete
bash: line 82: 26474 Killed
, semplicemente eseguite 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 più o meno così, allora aggiungete swap o aumentate la RAM., make.
- Installiamo , , Aggiungiamo un utente
- al gruppo
gitlab-runnersudo groupadd docker sudo usermod -aG docker gitlab-runnerdockergitlab-runner. - Apriamo per modifica
e aggiungiamo
/etc/gitlab-runner/config.tomlconcurrent=20 [[runners]] request_concurrency = 10Questo permetterà di eseguire task paralleli su un unico runner. Leggere più dettagliatamenteSe la vostra macchina è più potente, per esempio 8 vCPU, 16 GB di RAM, questi numeri possono essere aumentati di almeno il doppio. Ma tutto dipende da cosa esattamente verrà eseguito su questo runner e in quale quantità. .
Se disponete di una macchina più potente, ad esempio 8 vCPU e 16 GB di RAM, è possibile raddoppiare almeno questi valori. Tuttavia, tutto dipende da cosa verrà eseguito su questo runner e in quale quantità.
È sufficiente.
Preparazione docker-compose.yml
L'obiettivo principale è un docker-compose.yml versatile che possa essere utilizzato dai programmatori/tester sia localmente che nel pipeline CI.
In primo luogo, creiamo nomi di servizi unici per CI. Una delle variabili uniche in GitLab CI è la variabile CI_JOB_ID. Se specifichiamo container_name con il valore "service-${CI_JOB_ID:-local}", in caso di:
- se
CI_JOB_IDnon definita nelle variabili di ambiente,
il nome del servizio saràservice-local - se
CI_JOB_IDdefinita nelle variabili di ambiente (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 quando avviamo più ambienti di test.
networks:
default:
external:
name: service-network-${CI_JOB_ID:-local}Questo è, in effetti, il primo passo verso il successo =)
Esempio del mio docker-compose.yml con commenti
version: "3"
# Per il corretto funzionamento di web (php) e fmt, è necessario che i container condividano il contenuto eseguibile.
# 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 contiene ${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_PORT non sono definite,
# il servizio partirà 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 avvio locale
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 ... doneMa non è così semplice avviare in CI.
Preparazione Makefile
Utilizzo un Makefile, poiché è molto comodo sia per la gestione locale dell'ambiente che in CI. Di seguito commenti 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
Controlliamo
make docker-up
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Killing testrail-web-local ... done
Killing testrail-fpm-local ... done
Killing testrail-mysql-local ... done
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 ... done
Pulling migration ... done
Pulling fpm ... done
Pulling web ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Recreating testrail-mysql-local ... done
Recreating testrail-fpm-local ... done
Recreating testrail-migration-local ... done
Recreating testrail-web-local ... done
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’: File esistente
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 .gitlab-ci.yml
Esecuzione dei test di integrazione
Integrazione:
stage: test
tags:
- my-shell-runner
before_script:
# Autenticati nel registry
- docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
# Genera TR_HTTP_PORT e TR_HTTPS_PORT pseudo-unici
- export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
- export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
# crea una directory con l'identificatore del job
- mkdir ${CI_JOB_ID}
# copia il nostro docker-compose.yml nella directory creata
# in modo che il contesto sia diverso per ogni job
- cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
script:
# alziamo il nostro ambiente
- make docker-up
# eseguiamo i test con jar eseguibile (così faccio io)
- java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
# oppure in 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
artifacts:
# salviamo i log
when: always
paths:
- logs
expire_in: 30 daysDopo l'esecuzione di un tale job, nella directory degli artefatti i log conterranno i log dei servizi e dei test. Questo è molto comodo in caso di errori. Ogni test in parallelo scrive il proprio log, ma ne parlerò separatamente.

Pulizia del runner
Il job verrà eseguito solo su programmazione.
stages:
- clean
- build
- test
Clean runner:
stage: clean
only:
- schedules
tags:
- my-shell-runner
script:
- make docker-cleanPoi andiamo nel nostro progetto GitLab -> CI/CD -> Schedules -> New Schedule e aggiungiamo un nuovo programma
Risultato
Avviamo 4 job in GitLab CI
Nei log dell'ultimo job con i test di integrazione vediamo i container di diversi job
ID CONTENITORE NOME
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! Utilizzare --password tramite CLI è insicuro. Usare --password-stdin.
ATTENZIONE! La tua password sarà memorizzata non criptata in /home/gitlab-runner/.docker/config.json.
Configura un gestore delle 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
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
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 PORTI NOME
c6b76f9135ed registry.gitlab.com/touchbit/image/testrail/web:latest "nginx -g 'daemon of…" 13 secondi fa In esecuzione 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 13 secondi 9000/tcp testrail-fpm-204645172
2cdab1edbf6a registry.gitlab.com/touchbit/image/testrail/migration:latest "docker-entrypoint.s…" 16 secondi fa In esecuzione 13 secondi 3306/tcp, 33060/tcp testrail-migration-204645172
826aaf7c0a29 mysql:5.7.22 "docker-entrypoint.s…" 18 secondi fa In esecuzione 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 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 35 secondi 9000/tcp testrail-fpm-204645084
70fea72aa10d mysql:5.7.22 "docker-entrypoint.s…" 40 secondi fa In esecuzione 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 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 Circa un minuto 9000/tcp testrail-fpm-204644881
685d8023a3ec mysql:5.7.22 "docker-entrypoint.s…" Circa un minuto fa In esecuzione Circa 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 Circa 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 Circa un minuto 9000/tcp testrail-fpm-204644793
029e16b26201 mysql:5.7.22 "docker-entrypoint.s…" Circa un minuto fa In esecuzione Circa 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 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 5 ore 9000/tcp testrail-fpm-204567103
6ae0accab28d mysql:5.7.22 "docker-entrypoint.s…" 5 ore fa In esecuzione 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 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 5 ore 9000/tcp testrail-fpm-204553690
a8879c5ef941 mysql:5.7.22 "docker-entrypoint.s…" 5 ore fa In esecuzione 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 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 5 ore 9000/tcp testrail-fpm-204553539
1a1eed057ea0 mysql:5.7.22 "docker-entrypoint.s…" 5 ore fa In esecuzione 5 ore 3306/tcp testrail-mysql-204553539Tutti i task sono stati completati con successo
Gli artefatti del task contengono i log dei servizi e dei test
Tutto sembra a posto, ma c'è un piccolo dettaglio. Il pipeline può essere interrotto forzatamente durante l'esecuzione dei test di integrazione e in questo caso i container avviati non verranno fermati. Di tanto in tanto è necessario ripulire il runner. Purtroppo, la richiesta di miglioramento in GitLab CE è ancora in attesa.
Ma abbiamo aggiunto l'avvio di attività programmate e nessuno ci impedisce di avviarle manualmente.
Passiamo al nostro progetto -> CI/CD -> Schedules e avviamo l'attività. Clean runner
In totale:
- Abbiamo un solo shell runner.
- Non ci sono conflitti tra le attività e l'ambiente.
- Abbiamo l'esecuzione parallela delle attività con i test di integrazione.
- È possibile eseguire i test di integrazione sia localmente che nel container.
- I log dei servizi e dei test vengono raccolti e allegati al task del pipeline.
- C'è la possibilità di ripulire il runner da vecchi docker images.
Il tempo di configurazione è di circa 2 ore.
Ecco, sostanzialmente, è tutto. Sarò felice di ricevere feedback.
Fonte: habr.com
