JUnit GitLab CI ar Kubernetes

Neskatoties uz to, ka visi labi zina, cik svarīgi ir testēt savu programmatūru, un daudzi to jau sen dara automātiski, Habrā neatradās neviens receptes par šo populāro produktu, kā (mūsu mīļotais) GitLab un JUnit, savienošanu. Aizpildīsim šo tukšumu!

JUnit GitLab CI ar Kubernetes

Ievada

Vispirms iezīmēšu kontekstu:

  • Tā kā visas mūsu lietojumprogrammas darbojas Kubernetes, tiks apskatīta testu palaišana atbilstošajā infrastruktūrā.
  • Mēs izmantojam werf (infrastruktūras komponentu ziņā tas automātiski nozīmē, ka tiek izmantots arī Helm).
  • Nedziļināšos detaļās par testu izveidi: mūsu gadījumā klients raksta testus pats, un mēs tikai nodrošinām to palaišanu (un atbilstoša ziņojuma esamību merge request’ā).


Kāda būs vispārējā darbību secība?

  1. Lietojumprogrammas izveide — šī posma aprakstu mēs izlaidīsim.
  2. Lietojumprogrammas izvietošana atsevišķā Kubernetes klastera namespace un testēšanas uzsākšana.
  3. Artefaktu meklēšana un JUnit ziņojuma parsēšana ar GitLab.
  4. Iepriekš izveidotā namespace dzēšana.

Tagad — pie īstenošanas!

Iestatīšana

GitLab CI

Sāksim ar fragmentu .gitlab-ci.yaml, kas apraksta lietojumprogrammas izvietošanu un testu palaišanu. Saraksts izrādījās diezgan apjomīgs, tāpēc būtiski papildināts ar komentāriem:

mainītāji:
# nosakām werf versiju, ko plānojam izmantot
  WERF_VERSION: "1.0 beta"

.base_deploy: &base_deploy
  skripts:
# izveidojam namespace K8s, ja tāda nav
    - kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# lejupielādējam werf un veicam izvietošanu — sīkāk par to skatiet dokumentācijā
# (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:-''}"
# nododam mainīgo `run_tests`
# tas tiks izmantots Helm izlaiduma sakārtošanā
      --set "global.run_tests=${RUN_TESTS:-nē}"
      --set "global.env=${CI_ENVIRONMENT_SLUG}"
# mainām laika ierobežojumu (dažreiz testi ir gari) un nododam to izlaidumam
      --set "global.ci_timeout=${CI_TIMEOUT:-900}"
     --timeout ${CI_TIMEOUT:-900}
  atkarības:
    - Build

.test-base: &test-base
  pagarinājums: .base_deploy
  before_script:
# izveidojam direktoriju nākotnes atskaitēm, atkarībā no $CI_COMMIT_REF_SLUG
    - mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# piespiedu risinājums, jo GitLab vēlas saņemt artefaktus savā build-dir’ā
    - mkdir ./tests || true
    - ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
  after_script:
# pēc testu pabeigšanas izdzēšam izlaidumu kopā ar Job’iem
# (un, iespējams, tā infrastruktūru)
    - 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
# mēs atļaujam kļūdas, bet jūs varat darīt pretēji
  atļaut_kļūdas: true
  mainītāji:
    RUN_TESTS: 'jā'
# nosakām kontekstu werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
    WERF_KUBE_CONTEXT: 'admin@stage-cluster'
  etiķetes:
# izmantojam runneri ar etiķeti `werf-runner`
    - werf-runner
  artefakti:
# nepieciešams savākt artefaktu, lai to varētu redzēt
# pipeline un lejupielādēt — piemēram, dziļākai izpētei
    ceļi:
      - ./tests/${CI_COMMIT_REF_SLUG}/*
# artefakti, kas ir vecāki par nedēļu, tiks izdzēsti
    expire_in: 7 dienas
# svarīgi: šīs rindas atbild par atskaites parsēšanu GitLab’ā
    ziņojumi:
      junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml

# vienkāršošanai šeit parādītas tikai divas fāzes
# patiesībā jums tās būs vairāk — vismaz dēļ izvietošanas
fāzes:
  - build
  - tests

build:
  fāze: build
  skripts:
# būvēšana — vēlreiz pēc dokumentācijas par 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
  etiķetes:
    - werf-runner
  izņēmumi:
    - grafiki

veikt testus:
  <<: *test-base
  vide:
# "pašas sāls" namespace nosaukumā
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
    nosaukums: tests-${CI_COMMIT_REF_SLUG}
  fāze: tests
  izņēmumi:
    - grafiki

Kubernetes

Tagad direktorijā .helm/templates izveidosim YAML ar Job’iem — tests-job.yaml — testu palaišanai un nepieciešamajiem Kubernetes resursiem. Skatiet paskaidrojumus pēc saraksta:

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

Kādi resursi ir aprakstīti šajā konfigurācijā? Izvietojot, izveidojam unikālu projekta namespace (tas norādīts vēl .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) un izplatām to:

  1. ConfigMap ar testa skriptu;
  2. Job ar pod’a aprakstu un norādīto direktīvu command, kas tieši palaiž testus;
  3. PV un PVC, kas ļauj glabāt testu datus.

Lūdzu, ņemiet vērā ievada nosacījumu ar if sākumā manifesta — attiecīgi, citiem YAML failiem Helm chartā ar lietojumu jābūt iekļautiem pretējā struktūrā, lai tie netiktu izvietoti testēšanas laikā. Tātad:

{{- if ne .Values.global.run_tests "yes" }}
---
viens cits yaml fails
{{- end }}

Tomēr, ja testi prasa zināmu infrastruktūru (piemēram, Redis, RabbitMQ, Mongo, PostgreSQL…) — to YAML failus var nees izslēgt. Izvērst tos testēšanas vidē… protams, pielāgojot pēc saviem ieskatiem.

Pēdējais akords

Jo būvēšana un izvietošana ar werf pagaidām darbojas tikai uz build-servera (ar gitlab-runner), bet pods ar testiem tiek palaists uz meistara, būs jāizveido katalogs /mnt/tests uz meistara un jānodod to runnerim, piemēram, pa NFS. Izvērsts piemērs ar paskaidrojumiem atrodamies K8s dokumentācijā.

Rezultāts būs:

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

Neviens ne aizliedz izveidot NFS-share tieši uz gitlab-runner, pēc tam to montēt pod’os.

Piezīme

Iespējams, jūs jautājat, kāpēc vispār sarežģīt ar Job’iem, ja var vienkārši palaist skriptu ar testiem tieši uz shell-runnera? Atbilde ir diezgan vienkārša…

Daži testi prasa piekļuvi infrastruktūrai (MongoDB, RabbitMQ, PostgreSQL u.c.) lai pārbaudītu pareizu darbību ar tām. Mēs veicam testēšanu standartizētā veidā — šādā pieejā papildus entitātes iekļaut ir viegli. Turklāt mēs iegūstam standarta piekļuvi izvietošanai (lai arī ar NFS, papildu katalogu montēšanu).

Rezultāts

Ko mēs redzēsim, kad izmantosim sagatavoto konfigurāciju?

Merging requestā tiks parādīta kopsavilkuma statistika par testiem, kas palaisti tā pēdējā pipeline:

JUnit GitLab CI ar Kubernetes

Uz katru kļūdu šeit var noklikšķināt, lai iegūtu detaļas:

JUnit GitLab CI ar Kubernetes

P.S.: Rūpīgs lasītājs pamanīs, ka mēs testējam NodeJS lietojumprogrammu, bet ekrānšāviņos ir .NET… Neuztraucieties: vienkārši rakstīšanas procesa ietvaros netika atrastas kļūdas pirmajā lietojumprogrammā, bet atradām tās otrajā.

Secinājums

Kā redzams, nekas sarežģīts!

Principā, ja jums jau ir shell-būvējs un tas darbojas, bet Kubernetes nav nepieciešams — pievienot tam testēšanu būs vēl vieglāka uzdevuma nekā šeit aprakstīts. Savukārt GitLab CI dokumentācijā atradīsiet piemērus Ruby, Go, Gradle, Maven un citām valodām.

P.S.

Lasiet arī mūsu emuāra rakstos:

Avots: habr.com

Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem 🔥 Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem | ProHoster