JUnit i GitLab CI med Kubernetes

Trots att alla vet att det Àr viktigt och nödvÀndigt att testa sin mjukvara och att mÄnga redan gör detta automatiskt, har det inte funnits nÄgot recept pÄ Habr för att konfigurera kopplingen mellan sÄ populÀra produkter i denna nisch som (vÄr favorit) GitLab och JUnit. LÄt oss fylla detta tomrum!

JUnit i GitLab CI med Kubernetes

Inledning

För att börja med, lÄt mig definiera kontexten:

  • Eftersom alla vĂ„ra applikationer körs i Kubernetes kommer vi att se pĂ„ hur tester körs i den aktuella infrastrukturen.
  • För byggande och distribution anvĂ€nder vi werf (vilket ocksĂ„ automatiskt innebĂ€r att Helm anvĂ€nds i meningen infrastrukturkomponenter).
  • Jag kommer inte att gĂ„ in pĂ„ detaljer om sjĂ€lva skapandet av tester: i vĂ„rt fall skriver kunden testerna sjĂ€lva, och vi sĂ€kerstĂ€ller bara deras körning (och att det finns en motsvarande rapport i merge requesten).


Hur kommer den allmÀnna sekvensen av ÄtgÀrder att se ut?

  1. Byggande av applikationen – vi kommer att hoppa över beskrivningen av detta steg.
  2. Distribution av applikationen till ett separat namespace i Kubernetes-klustret och uppstart av testning.
  3. Sökning av artefakter och parsering av JUnit-rapporten av GitLab.
  4. Borttagning av det tidigare skapade namespace.

Nu – till genomförandet!

InstÀllning

GitLab CI

LÄt oss börja med fragmentet .gitlab-ci.yaml, som beskriver distributionen av applikationen och körningen av tester. Listan blev ganska omfattande, sÄ den har noggrant kompletterats med kommentarer:

variabler:
# deklarera versionen av werf som vi ska anvÀnda
  WERF_VERSION: "1.0 beta"

.base_deploy: &base_deploy
  skript:
# skapa namespace i K8s om det inte finns
    - kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# ladda ner werf och deploya — lĂ€s mer om detta i dokumentationen
# (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:-''}"
# överför variabeln `run_tests`
# den kommer att anvÀndas i renderingen av Helm-releasen
      --set "global.run_tests=${RUN_TESTS:-no}"
      --set "global.env=${CI_ENVIRONMENT_SLUG}"
# Àndra timeout (det kan ta tid med tester) och överför den till releasen
      --set "global.ci_timeout=${CI_TIMEOUT:-900}"
     --timeout ${CI_TIMEOUT:-900}
  beroenden:
    - Bygg

.test-base: &test-base
  extends: .base_deploy
  before_script:
# skapa en mapp för den framtida rapporten baserat pÄ $CI_COMMIT_REF_SLUG
    - mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# tvingad workaround eftersom GitLab vill ha artefakter i sin build-mapp
    - mkdir ./tests || true
    - ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
  after_script:
# efter testerna avslutas radera releasen tillsammans med jobben
# (och eventuellt dess infrastruktur)
    - 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
# vi tillÄter misslyckanden, men du kan göra annorlunda
  allow_failure: true
  variabler:
    RUN_TESTS: 'ja'
# stÀll in kontexten i werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
    WERF_KUBE_CONTEXT: 'admin@stage-cluster'
  tags:
# anvÀnd en runner med taggen `werf-runner`
    - werf-runner
  artefakter:
# det Àr nödvÀndigt att samla artefakten sÄ den kan ses
# i pipelinen och laddas ner — till exempel för mer noggrant granskande
    paths:
      - ./tests/${CI_COMMIT_REF_SLUG}/*
# artefakter Àldre Àn en vecka kommer att raderas
    expire_in: 7 dagar
# viktigt: dessa rader ansvarar för parsing av rapporten av GitLab
    reports:
      junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml

# för att förenkla visas hÀr bara tvÄ steg
# i verkligheten har du fler — Ă„tminstone pĂ„ grund av deployment
steg:
  - bygg
  - tester

bygg:
  steg: bygg
  skript:
# bygg — Ă„terigen enligt dokumentationen för 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
  förutom:
    - scheman

kör tester:
  <<: *test-base
  miljö:
# "sjÀlva hemligheten" i namngivningen av namespace
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
    name: tester-${CI_COMMIT_REF_SLUG}
  steg: tester
  förutom:
    - scheman

Kubernetes

Nu i katalogen .helm/templates skapar vi en YAML med Job' som tests-job.yaml för att köra tester och dess nödvÀndiga resurser för Kubernetes. Se förklaringar efter listningen:

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

Vilka resurser beskrivs i denna konfiguration? Vid deployment skapar vi ett unikt namespace för projektet (detta anges ocksĂ„ i .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) och vi distribuerar till det:

  1. ConfigMap med testscriptet;
  2. Job med beskrivning av podden och angiven direktiv command, som just startar testerna;
  3. PV och PVC, vilket gör det möjligt att lagra testdata.

Observera det inledande villkoret med if i början av manifestet — dĂ€rmed mĂ„ste andra YAML-filer frĂ„n Helm-diagrammet med applikationen omslutas i omvĂ€nd struktur sĂ„ att de inte distribueras vid testning. Det vill sĂ€ga:

{{- if ne .Values.global.run_tests "yes" }}
---
jag annan yaml
{{- end }}

Men om testerna krĂ€ver viss infrastruktur (till exempel Redis, RabbitMQ, Mongo, PostgreSQL
) — deras YAML-filer kan inte stĂ€nga av. Utveckla och testa dem i en testmiljö  sjĂ€lvklart justera efter eget tycke.

Det sista strecket

Eftersom byggnation och distribution med werf för nÀrvarande fungerar bara pÄ byggservern (med gitlab-runner), och podden med tester startas pÄ master, behöver vi skapa en katalog /mnt/tests pÄ master och ge den till runnern, till exempel via NFS. Ett utökat exempel med förklaringar kan hittas i K8s-dokumentationen.

Resultatet blir:

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

Ingen hindrar dig frÄn att skapa en NFS-delning direkt pÄ gitlab-runner, som sedan kan monteras i poddar.

AnmÀrkning

Du kanske undrar varför man ska komplicera det hela genom att skapa en Job, nÀr man kan köra testskript direkt pÄ shell-runnern? Svaret Àr ganska trivialt...

Vissa tester krĂ€ver tillgĂ„ng till infrastrukturer (MongoDB, RabbitMQ, PostgreSQL m.m.) för att kontrollera korrektheten i arbetet med dem. Vi gör testningen enhetlig — med denna metod blir det enkelt att inkludera sĂ„dana tillĂ€ggsenheter. Dessutom fĂ„r vi standard metod för distribution (Ă€ven om det gĂ€ller att anvĂ€nda NFS och ytterligare montering av kataloger).

Resultat

Vad kommer vi att se nÀr vi tillÀmpa den förberedda konfigurationen?

I merge-förfrÄgan kommer en sammanfattande statistik över de tester som kördes i den senaste pipelinen att visas:

JUnit i GitLab CI med Kubernetes

För varje fel kan du klicka för att fÄ detaljer:

JUnit i GitLab CI med Kubernetes

NB: En uppmĂ€rksam lĂ€sare kommer att notera att vi testar en NodeJS-applikation, medan skĂ€rmbilderna visar .NET
 Oroa dig inte: helt enkelt för att det under artikelns förberedelse inte fanns nĂ„gra fel i testningen av den första applikationen, men vi hittade dem i den andra.

Slutsats

Som ni ser, inget komplicerat!

I princip, om du redan har en shell-builder och den fungerar, och Kubernetes inte Ă€r nödvĂ€ndigt för dig — att koppla till testningen blir en Ă€nnu enklare uppgift Ă€n den som beskrivs hĂ€r. Och i GitLab CI-dokumentationen finns exempel för Ruby, Go, Gradle, Maven och nĂ„gra andra.

P.S.

LÀs Àven i vÄr blogg:

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster