JUnit u GitLab CI sa Kubernetesom

Unatoč tome što svi savršeno znaju da je testiranje vašeg softvera važno i neophodno, a mnogi to već duže vrijeme rade automatski, na prostranstvima Habra nije postojao niti jedan recept za postavljanje kombinacije tako popularnih proizvoda u ovu nišu kao (naš omiljeni) GitLab i JUnit . Hajde da popunimo ovu prazninu!

JUnit u GitLab CI sa Kubernetesom

Uvodni

Prvo da dam neki kontekst:

  • Budući da sve naše aplikacije rade na Kubernetesu, razmotrićemo pokretanje testova na odgovarajućoj infrastrukturi.
  • Za montažu i postavljanje koristimo werf (u smislu infrastrukturnih komponenti, to takođe automatski znači da je Helm uključen).
  • Neću ulaziti u detalje stvarnog kreiranja testova: u našem slučaju, klijent sam piše testove, a mi samo osiguravamo njihovo pokretanje (i prisustvo odgovarajućeg izvještaja u zahtjevu za spajanje).


Kako će izgledati opći slijed radnji?

  1. Izrada aplikacije - izostavićemo opis ove faze.
  2. Postavite aplikaciju u poseban prostor imena Kubernetes klastera i počnite testirati.
  3. Traženje artefakata i raščlanjivanje JUnit izvještaja pomoću GitLaba.
  4. Brisanje prethodno kreiranog imenskog prostora.

Sada - implementaciji!

podešavanje

GitLab CI

Počnimo s fragmentom .gitlab-ci.yaml, koji opisuje implementaciju aplikacije i pokretanje testova. Popis se pokazao prilično obimnim, pa je temeljito dopunjen komentarima:

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

Kubernet

Sada u imeniku .helm/templates kreirajmo YAML sa Jobom - tests-job.yaml — za pokretanje testova i potrebnih Kubernetes resursa. Pogledajte objašnjenja nakon navođenja:

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

Kakva sredstva opisano u ovoj konfiguraciji? Prilikom implementacije kreiramo jedinstveni prostor imena za projekat (ovo je naznačeno u .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) i razvaljajte ga:

  1. ConfigMap sa test skriptom;
  2. posao sa opisom pod i navedenom direktivom command, koji samo pokreće testove;
  3. PV i PVC, koji vam omogućavaju pohranjivanje testnih podataka.

Obratite pažnju na uvodni uslov sa if na početku manifesta - prema tome, ostale YAML datoteke Helm karte sa aplikacijom moraju biti umotane u obrnuto dizajnirati tako da se ne raspoređuju tokom testiranja. To je:

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

Međutim, ako su testovi zahtijevaju određenu infrastrukturu (na primjer, Redis, RabbitMQ, Mongo, PostgreSQL...) - njihovi YAML-ovi mogu biti ne ugasiti. Postavite ih iu testno okruženje... prilagođavajući ih kako vam odgovara, naravno.

Final touch

Jer montaža i postavljanje koristeći werf za sada radi samo na build serveru (sa gitlab-runnerom), a pod sa testovima je pokrenut na masteru, morat ćete kreirati direktorij /mnt/tests na majstora i daj ga trkaču, na primjer, preko NFS-a. Detaljan primjer s objašnjenjima možete pronaći u K8s dokumentacija.

Rezultat će biti:

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

Niko ne zabranjuje pravljenje NFS deljenja direktno na gitlab-runner-u, a zatim ga montiranje u podove.

primjedba

Možda se pitate zašto sve komplikovati kreiranjem Job-a ako jednostavno možete pokrenuti skriptu sa testovima direktno na shell runneru? Odgovor je sasvim trivijalan...

Neki testovi zahtevaju pristup infrastrukturi (MongoDB, RabbitMQ, PostgreSQL, itd.) da bi se potvrdilo da rade ispravno. Testiranje činimo ujedinjenim - ovim pristupom postaje lako uključiti takve dodatne entitete. Pored ovoga, dobijamo standard pristup implementaciji (čak i ako se koristi NFS, dodatno montiranje direktorija).

rezultat

Šta ćemo vidjeti kada primijenimo pripremljenu konfiguraciju?

Zahtjev za spajanje će prikazati zbirnu statistiku za testove koji se izvode u svom najnovijem cevovodu:

JUnit u GitLab CI sa Kubernetesom

Svaku grešku možete kliknuti ovdje za detalje:

JUnit u GitLab CI sa Kubernetesom

NB: Pažljivi čitalac će primijetiti da testiramo NodeJS aplikaciju, a na snimcima ekrana - .NET... Nemojte se iznenaditi: samo u pripremi članka nisu pronađene greške u testiranju prve aplikacije, ali one pronađeni su u drugom.

zaključak

Kao što vidite, ništa komplikovano!

U principu, ako već imate kolektor ljuski i on radi, ali vam ne treba Kubernetes, priključivanje testiranja na njega bit će još jednostavniji zadatak nego što je ovdje opisano. I unutra GitLab CI dokumentacija naći ćete primjere za Ruby, Go, Gradle, Maven i neke druge.

PS

Pročitajte i na našem blogu:

izvor: www.habr.com

Dodajte komentar