JUnit in GitLab CI met Kubernetes

Ten spyte van die feit dat almal baie goed weet dat die toets van u sagteware belangrik en noodsaaklik is, en baie doen dit al lankal outomaties, was daar in die uitgestrekte Habr nie 'n enkele resep vir die opstel van 'n kombinasie van sulke gewilde produkte in hierdie nis as (ons gunsteling) GitLab en JUnit. Kom ons vul hierdie gaping!

JUnit in GitLab CI met Kubernetes

Inleidend

Laat ek eers 'n bietjie konteks gee:

  • Aangesien al ons toepassings op Kubernetes loop, sal ons dit oorweeg om toetse op die toepaslike infrastruktuur uit te voer.
  • Vir samestelling en ontplooiing gebruik ons werf (wat infrastruktuurkomponente betref, beteken dit ook outomaties dat Helm betrokke is).
  • Ek gaan nie in op die besonderhede van die werklike skepping van toetse nie: in ons geval skryf die kliënt die toetse self, en ons verseker slegs die bekendstelling daarvan (en die teenwoordigheid van 'n ooreenstemmende verslag in die samesmeltingsversoek).


Hoe sal die algemene volgorde van aksies lyk?

  1. Die bou van die toepassing - ons sal die beskrywing van hierdie stadium weglaat.
  2. Ontplooi die toepassing na 'n aparte naamruimte van die Kubernetes-kluster en begin toets.
  3. Soek na artefakte en ontleed JUnit-verslae met GitLab.
  4. Vee 'n voorheen geskepte naamspasie uit.

Nou - tot implementering!

aanpassing

GitLab CI

Kom ons begin met 'n fragment .gitlab-ci.yaml, wat die ontplooiing van die toepassing en die uitvoer van toetse beskryf. Die lys blyk taamlik lywig te wees, so dit is deeglik aangevul met opmerkings:

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

Nou in die gids .helm/templates kom ons skep YAML met Job - tests-job.yaml - om toetse uit te voer en die Kubernetes-hulpbronne wat dit benodig. Sien verduidelikings na lys:

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

Watter soort hulpbronne beskryf in hierdie opset? Wanneer ons ontplooi, skep ons 'n unieke naamruimte vir die projek (dit word aangedui in .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) en rol dit uit:

  1. ConfigMap met toetsskrif;
  2. Job met 'n beskrywing van die peul en die gespesifiseerde richtlijn command, wat net die toetse laat loop;
  3. PV en PVC, wat jou toelaat om toetsdata te stoor.

Gee aandag aan die inleidende voorwaarde met if aan die begin van die manifes - dienooreenkomstig moet ander YAML-lêers van die Helm-kaart met die aansoek toegedraai word omgekeerde ontwerp sodat hulle nie tydens toetsing ontplooi word nie. Dit is:

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

As die toetse egter vereis sekere infrastruktuur (byvoorbeeld Redis, RabbitMQ, Mongo, PostgreSQL...) - hul YAML'e kan wees geen skakel af. Ontplooi hulle ook in 'n toetsomgewing ... pas hulle natuurlik aan soos jy goeddink.

Finale aanraking

Omdat samestelling en ontplooiing met behulp van werf werke vir nou slegs op die boubediener (met gitlab-runner), en die pod met toetse word op die meester geloods, sal jy 'n gids moet skep /mnt/tests op die meester en gee dit aan die hardloper, byvoorbeeld, via NFS. 'n Gedetailleerde voorbeeld met verduidelikings kan gevind word in K8s dokumentasie.

Die resultaat sal wees:

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

Niemand verbied om 'n NFS-deel direk op gitlab-runner te maak en dit dan in peule te monteer nie.

Let daarop

Jy vra dalk hoekom alles bemoeilik deur 'n werk te skep as jy bloot 'n skrif met toetse direk op die doploper kan laat loop? Die antwoord is nogal triviaal ...

Sommige toetse vereis toegang tot die infrastruktuur (MongoDB, RabbitMQ, PostgreSQL, ens.) om te verifieer dat hulle reg werk. Ons maak toetsing verenig - met hierdie benadering word dit maklik om sulke bykomende entiteite in te sluit. Benewens hierdie, kry ons standaard ontplooiingsbenadering (selfs as NFS gebruik word, bykomende montering van gidse).

Gevolg

Wat sal ons sien wanneer ons die voorbereide konfigurasie toepas?

Die samesmeltingsversoek sal opsommende statistieke wys vir toetse wat in sy jongste pyplyn uitgevoer is:

JUnit in GitLab CI met Kubernetes

Elke fout kan hier geklik word vir besonderhede:

JUnit in GitLab CI met Kubernetes

NB: Die aandagtige leser sal agterkom dat ons besig is om 'n NodeJS-toepassing te toets, en in die skermkiekies - .NET... Moenie verbaas wees nie: net as deel van die voorbereiding van die artikel, is geen foute gevind in die toets van die eerste toepassing nie, maar hulle is in 'n ander gevind.

Gevolgtrekking

Soos jy kan sien, niks ingewikkeld nie!

In beginsel, as jy reeds 'n dopversamelaar het en dit werk, maar jy het nie Kubernetes nodig nie, sal dit 'n selfs eenvoudiger taak wees om toetsing daaraan te koppel as wat hier beskryf word. En in GitLab CI dokumentasie jy sal voorbeelde vind vir Ruby, Go, Gradle, Maven en 'n paar ander.

PS

Lees ook op ons blog:

Bron: will.com

Voeg 'n opmerking