JUnit in GitLab CI cù Kubernetes

Malgradu u fattu chì ognunu sapi perfettamente chì a prova di u vostru software hè impurtante è necessariu, è parechji l'anu fattu automaticamente per un bellu pezzu, in l'immensità di Habr ùn ci era micca una sola ricetta per a creazione di una cumminazione di tali prudutti populari. questu nichu cum'è (u nostru favuritu) GitLab è JUnit . Rimpiemu sta lacuna !

JUnit in GitLab CI cù Kubernetes

Intruduzioni

Prima, lasciami dà un pocu di cuntestu:

  • Siccomu tutte e nostre applicazioni funzionanu nantu à Kubernetes, cunsideremu l'esecuzione di teste nantu à l'infrastruttura adatta.
  • Per assemblea è implementazione avemu aduprà werf (in termini di cumpunenti infrastrutturali, questu significa ancu automaticamente chì Helm hè implicatu).
  • Ùn andaraghju micca in i dettagli di a creazione attuale di teste: in u nostru casu, u cliente scrive i testi stessu, è avemu assicuratu solu u so lanciu (è a prisenza di un rapportu currispundente in a dumanda di fusione).


Chì serà a sequenza generale di l'azzioni cum'è?

  1. Custruisce l'applicazione - omettemu a descrizzione di sta tappa.
  2. Implementa l'applicazione in un spaziu di nomi separatu di u cluster Kubernetes è cumincianu a prova.
  3. Ricerca di artefatti è analizà i rapporti JUnit cù GitLab.
  4. Eliminazione di un spaziu di nome creatu prima.

Avà - à l'implementazione!

cutter

GitLab CI

Cuminciamu cù un fragmentu .gitlab-ci.yaml, chì descrive l'implementazione di l'applicazione è l'esecuzione di teste. A lista hè stata abbastanza voluminosa, cusì hè stata cumplementata cù cumenti:

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

Avà in u cartulare .helm/templates criemu YAML cù Job - tests-job.yaml - per eseguisce e teste è e risorse Kubernetes chì hà bisognu. Vede spiegazioni dopu a lista:

{{- if eq .Values.global.run_tests "yes" }}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: tests-script
data:
  tests.sh: |
    echo "======================"
    echo "${APP_NAME} TESTS"
    echo "======================"

    cd /app
    npm run test:ci
    cp report.xml /app/test_results/${CI_COMMIT_REF_SLUG}/

    echo ""
    echo ""
    echo ""

    chown -R 999:999 /app/test_results/${CI_COMMIT_REF_SLUG}
---
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Chart.Name }}-test
  annotations:
    "helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook-weight": "2"
    "werf/watch-logs": "true"
spec:
  activeDeadlineSeconds: {{ .Values.global.ci_timeout }}
  backoffLimit: 1
  template:
    metadata:
      name: {{ .Chart.Name }}-test
    spec:
      containers:
      - name: test
        command: ['bash', '-c', '/app/tests.sh']
{{ tuple "application" . | include "werf_container_image" | indent 8 }}
        env:
        - name: env
          value: {{ .Values.global.env }}
        - name: CI_COMMIT_REF_SLUG
          value: {{ .Values.global.commit_ref_slug }}
       - name: APP_NAME
          value: {{ .Chart.Name }}
{{ tuple "application" . | include "werf_container_env" | indent 8 }}
        volumeMounts:
        - mountPath: /app/test_results/
          name: data
        - mountPath: /app/tests.sh
          name: tests-script
          subPath: tests.sh
      tolerations:
      - key: dedicated
        operator: Exists
      - key: node-role.kubernetes.io/master
        operator: Exists
      restartPolicy: OnFailure
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: {{ .Chart.Name }}-pvc
      - name: tests-script
        configMap:
          name: tests-script
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ .Chart.Name }}-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Mi
  storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
  volumeName: {{ .Values.global.commit_ref_slug }}

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: {{ .Values.global.commit_ref_slug }}
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 10Mi
  local:
    path: /mnt/tests/
  nodeAffinity:
   required:
     nodeSelectorTerms:
     - matchExpressions:
       - key: kubernetes.io/hostname
         operator: In
         values:
         - kube-master
  persistentVolumeReclaimPolicy: Delete
  storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
{{- end }}

Chì tipu di risorse descrittu in sta cunfigurazione? Quandu implementate, creemu un spaziu di nome unicu per u prugettu (questu hè indicatu in .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) è stende:

  1. ConfigMap cù script di prova;
  2. Job cù una descrizzione di u pod è a direttiva specifica command, chì solu corre i testi;
  3. PV è PVC, chì permettenu di almacenà dati di prova.

Prestate attenzione à a cundizione introduttiva cù if à l'iniziu di u manifestu - per quessa, altri schedarii YAML di u graficu Helm cù l'applicazione deve esse impannillati in inverse cuncepimentu in modu chì ùn sò micca implementati durante a prova. Hè:

{{- if ne .Values.global.run_tests "yes" }}
---
я другой ямлик
{{- end }}

Tuttavia, se i testi richiede una certa infrastruttura (per esempiu, Redis, RabbitMQ, Mongo, PostgreSQL ...) - i so YAML ponu esse ùn chjode. Impulsate ancu in un ambiente di prova ... aghjustendu cum'è vede bè, sicuru.

Toccu finale

Perchè assemblea è implementazione cù werf travaglia per avà solu nant'à u servitore di custruzzione (cù gitlab-runner), è u pod cù testi hè lanciatu nantu à u maestru, vi tuccherà à creà un cartulare /mnt/tests nantu à u maestru è dà à u corridore, per esempiu, via NFS. Un esempiu detallatu cù spiegazioni pò esse truvatu in K8s documentazione.

U risultatu serà:

user@kube-master:~$ cat /etc/exports | grep tests
/mnt/tests    IP_gitlab-builder/32(rw,nohide,insecure,no_subtree_check,sync,all_squash,anonuid=999,anongid=998)

user@gitlab-runner:~$ cat /etc/fstab | grep tests
IP_kube-master:/mnt/tests    /mnt/tests   nfs4    _netdev,auto  0       0

Nimu ùn pruibisce di fà una spartera NFS direttamente nantu à gitlab-runner, è dopu muntallu in pods.

Vita

Puderete esse dumandate perchè complicate tuttu creendu un Job se pudete simpricimenti eseguisce un script cù testi direttamente nantu à u shell runner? A risposta hè abbastanza triviale ...

Certi testi necessitanu accessu à l'infrastruttura (MongoDB, RabbitMQ, PostgreSQL, etc.) per verificà chì travaglianu bè. Facemu a prova unificata - cù questu approcciu, diventa faciule d'include tali entità supplementari. In più di questu, avemu standard approcciu di implementazione (ancu s'ellu si usa NFS, muntazione supplementu di cartulari).

risultatu

Chì vedemu quandu applichemu a cunfigurazione preparata?

A dumanda di fusione mostrarà statistiche riassuntu per e teste eseguite in u so ultimu pipeline:

JUnit in GitLab CI cù Kubernetes

Ogni errore pò esse clicatu quì per i dettagli:

JUnit in GitLab CI cù Kubernetes

NB: U lettore attentu s'avvisarà chì avemu pruvatu una applicazione NodeJS, è in i screenshots - .NET... Ùn vi maravigliate : hè solu chì durante a preparazione di l'articulu, ùn ci hè statu trovu errori in a prova di a prima applicazione, ma sò sò stati truvati in un altru.

cunchiusioni

Comu pudete vede, nunda di complicatu!

In principiu, s'è vo avete digià un cullettore di cunchiglia è funziona, ma ùn avete micca bisognu di Kubernetes, attaccà a prova à questu serà un compitu ancu più simplice di ciò chì hè descrittu quì. È in Documentazione GitLab CI truverete esempi per Ruby, Go, Gradle, Maven è altri.

PS

Leghjite puru nant'à u nostru blog:

Source: www.habr.com

Add a comment