JUnit në GitLab CI me Kubernetes

Përkundër faktit se të gjithë e dinë shumë mirë se testimi i softuerit tuaj është i rëndësishëm dhe i nevojshëm, dhe shumë prej tyre e kanë bërë automatikisht për një kohë të gjatë, në pafundësinë e Habrit nuk kishte asnjë recetë të vetme për vendosjen e një kombinimi të produkteve kaq të njohura në këtë vend si (i preferuari ynë) GitLab dhe JUnit. Le ta mbushim këtë boshllëk!

JUnit në GitLab CI me Kubernetes

hyrëse

Së pari, më lejoni të jap një kontekst:

  • Meqenëse të gjitha aplikacionet tona funksionojnë në Kubernetes, ne do të shqyrtojmë ekzekutimin e testeve në infrastrukturën e duhur.
  • Për montim dhe vendosje ne përdorim werf (përsa i përket komponentëve të infrastrukturës, kjo gjithashtu automatikisht do të thotë që Helm është i përfshirë).
  • Unë nuk do të hyj në detajet e krijimit aktual të testeve: në rastin tonë, klienti i shkruan vetë testet, dhe ne vetëm sigurojmë nisjen e tyre (dhe praninë e një raporti përkatës në kërkesën për bashkim).


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

  1. Ndërtimi i aplikacionit - ne do të heqim përshkrimin e kësaj faze.
  2. Vendoseni aplikacionin në një hapësirë ​​emri të veçantë të grupit Kubernetes dhe filloni testimin.
  3. Kërkimi i objekteve dhe analizimi i raporteve të JUnit me GitLab.
  4. Fshirja e një hapësire emri të krijuar më parë.

Tani - në zbatim!

rregullim

GitLab CI

Le të fillojmë me një fragment .gitlab-ci.yaml, i cili përshkruan vendosjen e aplikacionit dhe ekzekutimin e testeve. Lista doli të ishte mjaft voluminoze, kështu që u plotësua plotësisht me komente:

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

Tani në drejtori .helm/templates le të krijojmë YAML me Job - tests-job.yaml — për të ekzekutuar testet dhe burimet e Kubernetes që i nevojiten. Shihni shpjegimet pas renditjes:

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

Çfarë lloj burimesh përshkruar në këtë konfigurim? Gjatë vendosjes, ne krijojmë një hapësirë ​​unike emri për projektin (kjo tregohet në .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) dhe shpërndajeni:

  1. ConfigMap me skenar testimi;
  2. punë me një përshkrim të pod dhe direktivën e specifikuar command, i cili thjesht kryen testet;
  3. PV dhe PVC, të cilat ju lejojnë të ruani të dhënat e provës.

Kushtojini vëmendje gjendjes hyrëse me if në fillim të manifestit - në përputhje me rrethanat, skedarët e tjerë YAML të tabelës Helm me aplikacionin duhet të mbështillen në anasjelltas dizajnoni në mënyrë që ato të mos vendosen gjatë testimit. Kjo eshte:

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

Megjithatë, nëse testet kërkojnë pak infrastrukturë (për shembull, Redis, RabbitMQ, Mongo, PostgreSQL...) - YAML-të e tyre mund të jenë jo fiket. Vendosini ato edhe në një mjedis testimi... duke i rregulluar siç e shihni të arsyeshme, natyrisht.

Prekja e fundit

Sepse montimi dhe vendosja duke përdorur punimet werf tani për tani vetëm në serverin e ndërtimit (me gitlab-runner), dhe pod me teste lëshohet në master, do t'ju duhet të krijoni një drejtori /mnt/tests mbi mjeshtrin dhe ia jepni vrapuesit, për shembull, nëpërmjet NFS. Një shembull i detajuar me shpjegime mund të gjendet në Dokumentacioni K8s.

Rezultati do të jetë:

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 e ndalon krijimin e një share NFS direkt në gitlab-runner, dhe më pas montimin e tij në pods.

Shënim

Ju mund të pyesni pse të komplikoni gjithçka duke krijuar një punë nëse thjesht mund të ekzekutoni një skript me teste direkt në shell runner? Përgjigja është mjaft e parëndësishme...

Disa teste kërkojnë qasje në infrastrukturë (MongoDB, RabbitMQ, PostgreSQL, etj.) për të verifikuar që ato funksionojnë siç duhet. Ne e bëjmë testimin të unifikuar - me këtë qasje, bëhet e lehtë të përfshihen entitete të tilla shtesë. Përveç kësaj, ne marrim standard qasja e vendosjes (edhe nëse përdorni NFS, montim shtesë të drejtorive).

Result

Çfarë do të shohim kur të aplikojmë konfigurimin e përgatitur?

Kërkesa për bashkim do të tregojë statistika përmbledhëse për testet e kryera në tubacionin e saj të fundit:

JUnit në GitLab CI me Kubernetes

Çdo gabim mund të klikohet këtu për detaje:

JUnit në GitLab CI me Kubernetes

NB: Lexuesi i vëmendshëm do të vërejë se ne po testojmë një aplikacion NodeJS, dhe në pamjet e ekranit - .NET... Mos u çuditni: thjesht gjatë përgatitjes së artikullit nuk u gjetën gabime në testimin e aplikacionit të parë, por ato janë gjetur në një tjetër.

Përfundim

Siç mund ta shihni, asgjë e komplikuar!

Në parim, nëse tashmë keni një koleksionist guaskë dhe funksionon, por nuk keni nevojë për Kubernetes, bashkëngjitja e testimit me të do të jetë një detyrë edhe më e thjeshtë sesa përshkruhet këtu. Dhe ne Dokumentacioni GitLab CI do të gjeni shembuj për Ruby, Go, Gradle, Maven dhe disa të tjerë.

PS

Lexoni edhe në blogun tonë:

Burimi: www.habr.com

Shto një koment