JUnit во GitLab CI со Kubernetes

И покрај фактот дека секој совршено добро знае дека тестирањето на вашиот софтвер е важно и неопходно, а многумина тоа го прават автоматски долго време, во пространоста на Хабр немаше ниту еден рецепт за поставување комбинација од такви популарни производи во оваа ниша како (нашиот омилен) GitLab и JUnit . Да ја пополниме оваа празнина!

JUnit во GitLab CI со Kubernetes

Воведен

Прво, дозволете ми да дадам некој контекст:

  • Бидејќи сите наши апликации работат на Kubernetes, ќе размислиме да извршиме тестови на соодветната инфраструктура.
  • За склопување и распоредување користиме верф (во однос на инфраструктурните компоненти, ова исто така автоматски значи дека е вклучен и Хелм).
  • Нема да навлегувам во деталите за вистинското креирање тестови: во нашиот случај, клиентот сам ги пишува тестовите, а ние само го обезбедуваме нивното стартување (и присуството на соодветен извештај во барањето за спојување).


Како ќе изгледа генералниот редослед на дејства?

  1. Градење на апликацијата - ќе го изоставиме описот на оваа фаза.
  2. Распоредете ја апликацијата во посебен именски простор од кластерот Kubernetes и започнете со тестирање.
  3. Пребарување артефакти и парсирање на извештаите на JUnit со GitLab.
  4. Бришење на претходно креиран именски простор.

Сега - до имплементација!

прилагодување

GitLab CI

Да почнеме со фрагмент .gitlab-ci.yaml, кој го опишува распоредувањето на апликацијата и извршувањето на тестовите. Написот се покажа доста обемен, па затоа беше темелно дополнет со коментари:

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

Кубернети

Сега во директориумот .helm/templates ајде да создадеме YAML со Job - tests-job.yaml — да изврши тестови и ресурсите на Кубернетес што му се потребни. Погледнете ги објаснувањата по набројувањето:

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

Какви ресурси опишано во оваа конфигурација? При распоредување, создаваме единствен именски простор за проектот (ова е означено во .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) и развлечете го:

  1. ConfigMap со тест скрипта;
  2. работа со опис на под и наведената директива command, кој само ги извршува тестовите;
  3. PV и PVC, кои ви дозволуваат да складирате податоци од тестот.

Обрнете внимание на воведната состојба со if на почетокот на манифестот - соодветно, другите YAML-датотеки од табелата на Helm со апликацијата мора да бидат завиткани во обратна дизајнирајте така што тие да не се распоредат за време на тестирањето. Тоа е:

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

Меѓутоа, доколку тестовите бараат одредена инфраструктура (на пример, Redis, RabbitMQ, Mongo, PostgreSQL...) - нивните YAML може да бидат Нема Исклучи. Распоредете ги и во тест опкружување... прилагодувајќи ги како што ви одговара, се разбира.

Конечен допир

Бидејќи склопување и распоредување со користење на werf работи засега само на серверот за изградба (со gitlab-runner), и подлогата со тестови е лансирана на главниот, ќе треба да креирате директориум /mnt/tests на господарот и дај му го на тркачот, на пример, преку NFS. Детален пример со објаснувања може да се најде во K8s документација.

Резултатот ќе биде:

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

Никој не забранува правење NFS споделување директно на gitlab-runner, а потоа негово монтирање во pods.

Имајте на ум

Можеби се прашувате зошто да се комплицира сè со креирање на Job, ако едноставно можете да извршите скрипта со тестови директно на школката? Одговорот е прилично тривијален...

Некои тестови бараат пристап до инфраструктурата (MongoDB, RabbitMQ, PostgreSQL, итн.) за да се потврди дека тие работат правилно. Ние го правиме тестирањето унифицирано - со овој пристап, станува лесно да се вклучат такви дополнителни ентитети. Во прилог на ова, добиваме стандард пристап на распоредување (дури и ако користите NFS, дополнително монтирање на директориуми).

Резултира

Што ќе видиме кога ќе ја примениме подготвената конфигурација?

Барањето за спојување ќе прикаже збирна статистика за тестовите што се извршуваат во најновата линија:

JUnit во GitLab CI со Kubernetes

Секоја грешка може да се кликне овде за детали:

JUnit во GitLab CI со Kubernetes

NB: Внимателниот читател ќе забележи дека тестираме апликација NodeJS, а на сликите од екранот - .NET... Немојте да се чудите: исто како дел од подготовката на статијата, не беа пронајдени грешки при тестирањето на првата апликација, но биле пронајдени во друга.

Заклучок

Како што можете да видите, ништо комплицирано!

Во принцип, ако веќе имате колектор на школки и тој работи, но не ви треба Kubernetes, прикачувањето на тестирањето на него ќе биде уште поедноставна задача отколку што е опишано овде. И во GitLab CI документација ќе најдете примери за Ruby, Go, Gradle, Maven и некои други.

PS

Прочитајте и на нашиот блог:

Извор: www.habr.com

Додадете коментар