Nonostante tutti sappiano perfettamente che testare il proprio software è importante e necessario, e molti lo fanno automaticamente da molto tempo, nella vastità di Habr non esisteva un'unica ricetta per creare una combinazione di prodotti così popolari in questa nicchia come (il nostro preferito) GitLab e JUnit. Colmiamo questa lacuna!
Introduttivo
Per prima cosa, lasciatemi fornire un po’ di contesto:
Poiché tutte le nostre applicazioni funzionano su Kubernetes, prenderemo in considerazione l'esecuzione di test sull'infrastruttura appropriata.
Per l'assemblaggio e la distribuzione utilizziamo werf (in termini di componenti infrastrutturali ciò significa automaticamente anche il coinvolgimento di Helm).
Non entrerò nei dettagli della creazione vera e propria dei test: nel nostro caso è il cliente stesso a scrivere i test, e noi ci limitiamo a garantirne l'avvio (e la presenza di un corrispondente report nella richiesta di fusione).
Come sarà la sequenza generale delle azioni?
Creazione dell'applicazione: ometteremo la descrizione di questa fase.
Distribuisci l'applicazione in uno spazio dei nomi separato del cluster Kubernetes e avvia il test.
Ricerca di artefatti e analisi dei report JUnit con GitLab.
Eliminazione di uno spazio dei nomi creato in precedenza.
Ora - all'implementazione!
registrazione
CI GitLab
Cominciamo con un frammento .gitlab-ci.yaml, che descrive la distribuzione dell'applicazione e l'esecuzione dei test. L'elenco si è rivelato piuttosto voluminoso, quindi è stato completamente integrato con commenti:
variables:
# объявляем версию werf, которую собираемся использовать
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
script:
# создаем namespace в K8s, если его нет
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# загружаем werf и деплоим — подробнее об этом см. в документации
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#deploy-stage)
- type multiwerf && source <(multiwerf use ${WERF_VERSION})
- werf version
- type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
- werf deploy --stages-storage :local
--namespace ${CI_ENVIRONMENT_SLUG}
--set "global.commit_ref_slug=${CI_COMMIT_REF_SLUG:-''}"
# передаем переменную `run_tests`
# она будет использоваться в рендере Helm-релиза
--set "global.run_tests=${RUN_TESTS:-no}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# изменяем timeout (бывают долгие тесты) и передаем его в релиз
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# создаем директорию для будущего отчета, исходя из $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# вынужденный костыль, т.к. GitLab хочет получить артефакты в своем build-dir’е
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# после окончания тестов удаляем релиз вместе с Job’ом
# (и, возможно, его инфраструктурой)
- type multiwerf && source <(multiwerf use ${WERF_VERSION})
- werf version
- type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
- werf dismiss --namespace ${CI_ENVIRONMENT_SLUG} --with-namespace
# мы разрешаем падения, но вы можете сделать иначе
allow_failure: true
variables:
RUN_TESTS: 'yes'
# задаем контекст в werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# используем раннер с тегом `werf-runner`
- werf-runner
artifacts:
# требуется собрать артефакт для того, чтобы его можно было увидеть
# в пайплайне и скачать — например, для более вдумчивого изучения
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# артефакты старше недели будут удалены
expire_in: 7 day
# важно: эти строки отвечают за парсинг отчета GitLab’ом
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# для упрощения здесь показаны всего две стадии
# в реальности же у вас их будет больше — как минимум из-за деплоя
stages:
- build
- tests
build:
stage: build
script:
# сборка — снова по документации по werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#build-stage)
- type multiwerf && source <(multiwerf use ${WERF_VERSION})
- werf version
- type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
- werf build-and-publish --stages-storage :local
tags:
- werf-runner
except:
- schedules
run tests:
<<: *test-base
environment:
# "сама соль" именования namespace’а
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedules
kubernetes
Ora nella directory .helm/templates creiamo YAML con Job - tests-job.yaml — per eseguire i test e le risorse Kubernetes di cui ha bisogno. Vedi le spiegazioni dopo l'elenco:
Che tipo di risorse descritto in questa configurazione? Durante la distribuzione, creiamo uno spazio dei nomi univoco per il progetto (questo è indicato in .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) e stendilo:
Mappa di configurazione con script di prova;
Lavoro con una descrizione del pod e la direttiva specificata command, che esegue semplicemente i test;
FV e PVC, che consentono di memorizzare i dati dei test.
Presta attenzione alla condizione introduttiva con if all'inizio del manifest - di conseguenza, devono essere racchiusi altri file YAML del grafico Helm con l'applicazione retromarcia progettare in modo che non vengano distribuiti durante i test. Questo è:
{{- if ne .Values.global.run_tests "yes" }}
---
я другой ямлик
{{- end }}
Tuttavia, se i test richiedono alcune infrastrutture (ad esempio, Redis, RabbitMQ, Mongo, PostgreSQL...) - i loro YAML possono essere no spegnere. Distribuiscili anche in un ambiente di test... modificandoli come ritieni opportuno, ovviamente.
Tocco finale
Perché assemblaggio e distribuzione utilizzando werf funziona per ora solo sul build server (con gitlab-runner), e il pod con i test viene lanciato sul master, dovrai creare una directory /mnt/tests sul maestro e dallo al corridore, ad esempio, tramite NFS. Un esempio dettagliato con spiegazioni può essere trovato in Documentazione del K8.
Nessuno vieta di creare una condivisione NFS direttamente su gitlab-runner e poi montarla nei pod.
Nota
Forse ti starai chiedendo perché complicare tutto creando un Job se puoi semplicemente eseguire uno script con test direttamente sullo Shell Runner? La risposta è abbastanza banale...
Alcuni test richiedono l'accesso all'infrastruttura (MongoDB, RabbitMQ, PostgreSQL, ecc.) per verificare che funzionino correttamente. Rendiamo i test unificati: con questo approccio diventa facile includere tali entità aggiuntive. Oltre a questo, otteniamo standard approccio di distribuzione (anche se si utilizza NFS, montaggio aggiuntivo di directory).
risultato
Cosa vedremo quando applicheremo la configurazione preparata?
La richiesta di unione mostrerà le statistiche di riepilogo per i test eseguiti nella sua pipeline più recente:
È possibile fare clic su ciascun errore qui per i dettagli:
NB: Il lettore attento noterà che stiamo testando un'applicazione NodeJS e negli screenshot - .NET... Non stupitevi: è solo che durante la preparazione dell'articolo non sono stati trovati errori nel testare la prima applicazione, ma sono sono stati trovati in un altro.
conclusione
Come puoi vedere, niente di complicato!
In linea di principio, se hai già uno Shell Collector e funziona, ma non hai bisogno di Kubernetes, allegarvi dei test sarà un compito ancora più semplice di quello descritto qui. E dentro Documentazione CI di GitLab troverai esempi per Ruby, Go, Gradle, Maven e alcuni altri.