JUnit v GitLab CI z Kubernetesom

Kljub temu da vsi vedo, da je testiranje programske opreme pomembno in nujno, ter da mnogi to že dolgo počnejo avtomatsko, na Habrju ni bilo najti nobenega recepta za nastavitev povezave med tako priljubljenima produktoma na tem področju, kot sta (naša ljubljena) GitLab in JUnit. Zapolnimo to vrzel!

JUnit v GitLab CI z Kubernetesom

Uvodne informacije

Najprej bom določil kontekst:

  • Ker vse naše aplikacije delujejo v Kubernetesu, bomo obravnavali zagon testov v ustrezni infrastrukturi.
  • Za gradnjo in namestitev uporabljamo werf (kar pomeni, da to infrastrukturne komponente avtomatsko vključujejo tudi Helm).
  • Ne bom se poglabljal v podrobnosti neposrednega ustvarjanja testov: v našem primeru testov ne pišejo naši inženirji, temveč jih stranka sama, mi pa zgolj omogočimo njihov zagon (in zagotovimo, da je ustrezen poročilo v merge requestu).


Kako bo izgledal celoten postopek?

  1. Gradnja aplikacije — opis tega koraka bomo izpustili.
  2. Namestitev aplikacije v ločen namespace Kubernetes klasterja in zagon testiranja.
  3. Iskanje artefaktov in razčlenitev JUnit poročila s strani GitLaba.
  4. Izbris prej ustvarjenega namespacea.

Sedaj pa k implementaciji!

Setup

GitLab CI

Začnimo s fragmentom .gitlab-ci.yaml, ki opisuje namestitev aplikacije in zagon testov. Seznam je precej obsežen, zato je obsežno dopolnjen s komentarji:

variables:
# zatvárame verziu werf, ktorú plánujeme použiť
  WERF_VERSION: "1.0 beta"

.base_deploy: &base_deploy
  script:
# vytvoríme namespace v K8s, ak neexistuje
    - kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# nahrávame werf a deploy - viac o tom nájdete v dokumentácii
# (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:-''}"
# odovzdávame premennú `run_tests`
# bude sa používať pri renderovaní Helm releasu
      --set "global.run_tests=${RUN_TESTS:-no}"
      --set "global.env=${CI_ENVIRONMENT_SLUG}"
# meníme timeout (môžu byť dlhé testy) a odovzdávame ho do releasu
      --set "global.ci_timeout=${CI_TIMEOUT:-900}"
     --timeout ${CI_TIMEOUT:-900}
  dependencies:
    - Build

.test-base: &test-base
  extends: .base_deploy
  before_script:
# vytvoríme adresár pre budúcu správu, podľa $CI_COMMIT_REF_SLUG
    - mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# nútený workaround, pretože GitLab chce získať artefakty vo svojom build-dir
    - mkdir ./tests || true
    - ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
  after_script:
# po skončení testov zmažeme release spolu s Jobom
# (a možno aj jeho infraštruktúrou)
    - 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
# povolíme zlyhania, ale môžete to urobiť inak
  allow_failure: true
  variables:
    RUN_TESTS: 'yes'
# určujeme kontext v werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
    WERF_KUBE_CONTEXT: 'admin@stage-cluster'
  tags:
# používame runner s tagom `werf-runner`
    - werf-runner
  artifacts:
# vyžaduje sa zostaviť artefakt, aby ho bolo možné vidieť
# v pipeline a stiahnuť - napríklad na podrobnejšie preskúmanie
    paths:
      - ./tests/${CI_COMMIT_REF_SLUG}/*
# artefakty staršie ako týždeň budú odstránené
    expire_in: 7 day
# dôležité: tieto riadky sú zodpovedné za parsovanie správy GitLabom
    reports:
      junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml

# pre zjednodušenie sú tu zobrazené len dve fázy
# v skutočnosti ich budete mať viac - minimálne kvôli deploy
stages:
  - build
  - tests

build:
  stage: build
  script:
# zostavovanie - opäť podľa dokumentácie 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:
# "sama soľ" pomenovávanie namespace
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
    name: tests-${CI_COMMIT_REF_SLUG}
  stage: tests
  except:
    - schedules

Kubernetes

Sedaj v direktoriju .helm/templates ustvarimo YAML z Jobom — tests-job.yaml — za spuščanje testov in potrebnimi viri Kubernetes. Razlago glejte po seznamu:

{{- 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 }}

Kateri viri so opisani v tej konfiguraciji? Pri uvedbi ustvarjamo edinstven namespace za projekt (to je navedeno še v .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) in vanj izvajamo:

  1. ConfigMap s skriptom testiranja;
  2. Job s opisom pod-a in določeno direktivo ukaz, ki pravzaprav запускает teste;
  3. PV in PVC, ki omogočata shranjevanje podatkov testov.

Upoštevajte uvodno stanje z if na začetku manifesta — ustrezno, druge YAML datoteke Helm grafičnega prikaza aplikacije je treba zaviti v obratno konstruiranje, da se ne uvedejo med testiranjem. Torej:

{{- if ne .Values.global.run_tests "yes" }}
---
sem druga yml datoteka
{{- end }}

Vendar pa, če testi zahtevajo določeno infrastrukturo (na primer, Redis, RabbitMQ, Mongo, PostgreSQL...) — njihove YAML datoteke je mogoče ne izklopiti. Razširite in jih v testnem okolju… seveda, z nekaj prilagoditvami po lastni izbiri.

Zaključni dotik

Ker gradnja in implementacija s pomočjo werf trenutno deluje le na gradbenem strežniku (s gitlab-runner), testni pod se zažene na masterju, bo treba ustvariti imenik /mnt/tests na masterju in ga predati runnerju, na primer, preko NFS. Razširjen primer s pojasnili lahko najdete v dokumentaciji K8s.

Rezultat bo:

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

Nikdo ne prepoveduje, da bi NFS-share ustvarili neposredno na gitlab-runnerju, nato pa jo priklopili v pode.

Napomena

Morda se sprašujete, zakaj zapletati z ustvarjanjem Job-a, če lahko preprosto zaženete skript s testi prav na shell-runnerju? Odgovor je precej preprost…

Nekateri testi zahtevajo dostop do infrastrukture (MongoDB, RabbitMQ, PostgreSQL in podobno) za preverjanje pravilnosti delovanja z njimi. Testiranje izvajamo na poenoten način – pri tem pristopu je vključitev takih dodatnih entitet enostavna. Poleg tega pridobimo standardno pristop pri implementaciji (tudi z uporabo NFS, dodatnim montiranjem imenikov).

Результат.

Kaj bomo videli, ko uporabimo pripravljeno konfiguracijo?

V merge request-u bo prikazana povzetek statistik o testih, ki so bili izvedeni v njegovem zadnjem pipeline-u:

JUnit v GitLab CI z Kubernetesom

Na vsako napako tukaj lahko kliknete za pridobitev podrobnosti:

JUnit v GitLab CI z Kubernetesom

NB: Pozorni bralec bo opazil, da testiramo NodeJS-aplikacijo, na zaslonih pa je .NET… Ne presenečajte se: preprosto med pripravo članka ni bilo napak v testiranju prve aplikacije, smo pa jih našli pri drugi.

Zaključek

Kot lahko vidite, nič zapletenega!

Načeloma, če imate že shell-build in deluje, Kubernetes pa vam ni potreben – povezovanje testiranja bo še enostavnejša naloga kot tisto, opisano tukaj. In v dokumentaciji GitLab CI boste našli primere za Ruby, Go, Gradle, Maven in nekaj drugih.

P.S.

Preberite tudi na našem blogu:

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster