JUnit në GitLab CI me Kubernetes

Megjithëse të gjithë e dinë se është e rëndësishme dhe e nevojshme të testosh softuerin tënd, dhe shumë prej nesh e bëjnë këtë automatikisht, në hapësirat e Habrat nuk ka asnjë recetë për konfigurimin e lidhjes së produkteve kaq popullore në këtë fushë si (i preferuari ynë) GitLab dhe JUnit. Le të plotësojmë këtë boshllëk!

JUnit në GitLab CI me Kubernetes

Hyrëse

Për të filluar, do të citoj kontekstin:

  • Duke qenĂ« se tĂ« gjitha aplikacionet tona funksionojnĂ« nĂ« Kubernetes, do tĂ« shqyrtojmĂ« ekzekutimin e testeve nĂ« infrastrukturĂ«n pĂ«rkatĂ«se.
  • PĂ«r ndĂ«rtimin dhe shpĂ«rndarjen pĂ«rdorim werf (nĂ« kuptimin e komponenteve infrastrukturore, kjo gjithashtu automatikisht nĂ«nkupton se Ă«shtĂ« pĂ«rdorur Helm).
  • Nuk do tĂ« hyj nĂ« detaje tĂ« krijimit tĂ« testeve: nĂ« rastin tonĂ«, klienti i shkruan testet vetĂ«, dhe ne vetĂ«m sigurojmĂ« ekzekutim tĂ« tyre (dhe praninĂ« e raportit pĂ«rkatĂ«s nĂ« merge request).


Si do të duket sekuenca e përgjithshme e veprimeve?

  1. NdĂ«rtimi i aplikacionit — pĂ«rshkrimi i kĂ«tij faze do ta anashkalojmĂ«.
  2. Shpërndarja e aplikacionit në një namespace të veçantë të klasterit Kubernetes dhe fillimi i testimit.
  3. Gjetja e artefakteve dhe analizimi i raportit JUnit nga GitLab.
  4. Fshirja e namespace-it të krijuar më parë.

Tani, le të kalojmë te implementimi!

Konfigurimi

GitLab CI

Të fillojmë me fragmentin .gitlab-ci.yaml, që përshkruan shpërndarjen e aplikacionit dhe ekzekutimin e testeve. Lista doli mjaft e gjerë, prandaj është plotësuar qëllimisht me komentet:

variables:
# shpallim versionin e werf që do të përdorim
  WERF_VERSION: "1.0 beta"

.base_deploy: &base_deploy
  script:
# krijojmë namespace në K8s, nëse nuk ekziston
    - kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# ngarkojmĂ« werf dhe e shpĂ«rndajmĂ« — mĂ« shumĂ« rreth kĂ«saj shihni nĂ« dokumentacion
# (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:-''}"
# kalojmë variablin `run_tests`
# ai do të përdoret në renderimin e helm-release-it
      --set "global.run_tests=${RUN_TESTS:-no}"
      --set "global.env=${CI_ENVIRONMENT_SLUG}"
# ndryshojmë timeout (disa teste mund të zgjasin) dhe e kalojmë atë në release
      --set "global.ci_timeout=${CI_TIMEOUT:-900}"
     --timeout ${CI_TIMEOUT:-900}
  dependencies:
    - Build

.test-base: &test-base
  extends: .base_deploy
  before_script:
# krijojmë një dosje për raportin e ardhshëm, duke u bazuar në $CI_COMMIT_REF_SLUG
    - mkdir /mnt/tests/${CI_COMMIT_REF_SL_SLUG} || true
# një zgjidhje e nevojshme, sepse GitLab dëshiron të marrë artefaktet në build-dir-in e tij
    - mkdir ./tests || true
    - ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
  after_script:
# pas përfundimit të testeve fshijmë release-in, së bashku me Job-in
# (dhe, ndoshta, infrastrukturen e tij)
    - 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
# lejojmë rëniet, por ju mund ta bëni ndryshe
  allow_failure: true
  variables:
    RUN_TESTS: 'yes'
# caktojmë kontekst në werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
    WERF_KUBE_CONTEXT: 'admin@stage-cluster'
  tags:
# përdorim runner-in me etiketën `werf-runner`
    - werf-runner
  artifacts:
# kërkohet të krijohet një artefakt që të mund të shfaqet
# nĂ« pipeline dhe tĂ« shkarkohet — p.sh., pĂ«r studime mĂ« tĂ« thella
    paths:
      - ./tests/${CI_COMMIT_REF_SLUG}/*
# artefaktet më të vjetra se një javë do të fshihen
    expire_in: 7 day
# e rëndësishme: këto rreshta përgjigjen për analizen e raportit nga GitLab
    reports:
      junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml

# për thjeshtim, këtu tregohet vetëm dy faza
# nĂ« tĂ« vĂ«rtetĂ« do tĂ« keni mĂ« shumĂ« — sĂ« paku pĂ«r shkak tĂ« shpĂ«rndarjes
stages:
  - build
  - tests

build:
  stage: build
  script:
# ndĂ«rtimi — pĂ«rsĂ«ri sipas dokumentacionit tĂ« 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:
# "vetë kripa" e emërtimit të namespace-it
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
    name: tests-${CI_COMMIT_REF_SLUG}
  stage: tests
  except:
    - schedules

Kubernetes

Tani nĂ« direktorinĂ« .helm/templates do tĂ« krijojmĂ« YAML me Job-in — tests-job.yaml — pĂ«r ekzekutimin e testeve dhe burimet e nevojshme pĂ«r tĂ« nĂ« Kubernetes. PĂ«rshtypjet shihni pas listĂ«s:

{{- 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}\/\n
    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\/\n          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\/\n  nodeAffinity:
   required:
     nodeSelectorTerms:
     - matchExpressions:
       - key: kubernetes.io\/hostname
         operator: In
         values:
         - kube-master
  persistentVolumeReclaimPolicy: Delete
  storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
{{- end }}

ÇfarĂ« burimesh janĂ« pĂ«rshkruar nĂ« kĂ«tĂ« konfigurim? GjatĂ« implementimit krijojmĂ« njĂ« hapĂ«sirĂ« unike pĂ«r projektin (kjo Ă«shtĂ« e specifikuar edhe nĂ« .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) dhe nĂ« tĂ« nxjerrim:

  1. ConfigMap me skriptin e testit;
  2. Job me përshkrimin e pod-it dhe direktivën e specifikuar command, e cila pikërisht aktivizon testet;
  3. PV dhe PVC, që lejojnë ruajtjen e të dhënave të testeve.

Kujdesi ndaj kushtit tĂ« hyrjes me nĂ«se nĂ« fillim tĂ« manifestit — pĂ«rkatĂ«sisht, skedarĂ«t e tjerĂ« YAML tĂ« Helm-chart-it tĂ« aplikacionit duhet tĂ« mbyllen nĂ« ndihmĂ«sin e pĂ«r tĂ« mos u implementuar gjatĂ« testimit. Pra:

{{- if ne .Values.global.run_tests "yes" }}
---
jam ndihmë tjetër
{{- end }}

MegjithatĂ«, nĂ«se testet kĂ«rkojnĂ« njĂ« infrastrukturĂ« tĂ« caktuar (p.sh., Redis, RabbitMQ, Mongo, PostgreSQL
) — YAML-Ă«t e tyre mund tĂ« nuk çaktivizohen. Aktivizoni edhe ato nĂ« mjedisin e testimit
 sigurisht, duke i modifikuar sipas dĂ«shirĂ«s tuaj.

Detaji përfundimtar

Duke qenë se ndërtimi dhe implementimi me ndihmën e werf aktualisht funksionon vetëm në serverin e ndërtimit (me gitlab-runner), dhe pod-i me testet aktivizohet në master, do të nevojitet të krijoni një direktori /mnt/tests në master dhe t'ia jepni atij në runner, p.sh., përmes NFS. Një shembull i përfunduar me shpjegime mund të gjendet në dokumenjtimin K8s.

Si rezultat do të kemi:

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

Askush nuk ndalon që të bëni një NFS-share direkt në gitlab-runner, pas së cilës ta montoni atë në pod-e.

Shënim

Ndoshta do tĂ« pyesni, pse ta komplikohet gjithĂ« kjo krijimi i Job-it, nĂ«se mund tĂ« aktivizoni thjesht skriptin me testet direkt nĂ« shell-runner? PĂ«gjigjja Ă«shtĂ« mjaft e thjeshtë 

Disa teste kĂ«rkojnĂ« qasje nĂ« infrastrukturĂ«n (MongoDB, RabbitMQ, PostgreSQL etj.) pĂ«r tĂ« verifikuar funksionimin e duhur me to. Ne e bĂ«jmĂ« testimin tĂ« unifikuar — me kĂ«tĂ« qasje, pĂ«rfshirja e entiteteve tĂ« tilla tĂ« tjera bĂ«het e lehtĂ«. PĂ«r mĂ« tepĂ«r, ne pĂ«rfitojmĂ« qasje standarde nĂ« implementim (ndonĂ«se me pĂ«rdorimin e NFS, montimi tĂ« katalogĂ«ve tĂ« tjerĂ«).

Rezultati

ÇfarĂ« do tĂ« shohim, kur tĂ« aplikojmĂ« konfigurimin e pĂ«rgatitur?

Në kërkesën për bashkimin do të tregohet statistika përmbledhëse për testet, të cilat janë aktivizuar në pipeline-in e fundit të saj:

JUnit në GitLab CI me Kubernetes

Për çdo gabim këtu mund të klikoni për të marrë detaje:

JUnit në GitLab CI me Kubernetes

NB: Lexuesi i kujdesshĂ«m do tĂ« vĂ«rejĂ«, se po testojmĂ« aplikacionin NodeJS, ndĂ«rsa nĂ« screenshot janĂ« .NET
 Mos u çuditni: thjesht nĂ« kuadĂ«r tĂ« pĂ«rgatitjes sĂ« artikullit nuk u gjetĂ«n gabime nĂ« testimin e aplikacionit tĂ« parĂ«, por u gjetĂ«n nĂ« njĂ« tjetĂ«r.

Përfundimi

Siç duket, nuk është asgjë e komplikuar!

NĂ« parim, nĂ«se ju tashmĂ« keni njĂ« ndĂ«rtues shell dhe ai funksionon, dhe Kubernetes nuk ju nevojitet — integrimi i testimit nĂ« tĂ« do tĂ« jetĂ« njĂ« detyrĂ« akoma mĂ« e thjeshtĂ« se ajo qĂ« Ă«shtĂ« pĂ«rshkruar kĂ«tu. Dhe nĂ« dokumenjtimin e GitLab CI do tĂ« gjeni shembuj pĂ«r Ruby, Go, Gradle, Maven dhe disa tĂ« tjerĂ«.

P.S.

Lexoni gjithashtu në blogun tonë:

Burimi: habr.com

Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster