JUnit i GitLab CI med Kubernetes

På trods af at alle godt ved, at det er vigtigt og nødvendigt at teste din software, og mange har gjort det automatisk i lang tid, var der i Habrs vidder ikke en eneste opskrift på at opsætte en kombination af så populære produkter i denne niche som (vores favorit) GitLab og JUnit . Lad os udfylde dette hul!

JUnit i GitLab CI med Kubernetes

Indledende

Lad mig først give lidt kontekst:

  • Da alle vores applikationer kører på Kubernetes, vil vi overveje at køre test på den relevante infrastruktur.
  • Til montering og udrulning bruger vi werf (i forhold til infrastrukturkomponenter betyder det også automatisk, at Helm er involveret).
  • Jeg vil ikke gå ind i detaljerne i den faktiske oprettelse af tests: I vores tilfælde skriver klienten testene selv, og vi sikrer kun deres lancering (og tilstedeværelsen af ​​en tilsvarende rapport i fletteanmodningen).


Hvordan vil den generelle rækkefølge af handlinger se ud?

  1. Opbygning af applikationen - vi udelader beskrivelsen af ​​denne fase.
  2. Implementer applikationen til et separat navneområde i Kubernetes-klyngen, og begynd at teste.
  3. Søger efter artefakter og parser JUnit-rapporter med GitLab.
  4. Sletning af et tidligere oprettet navneområde.

Nu - til implementering!

justering

GitLab CI

Lad os starte med et fragment .gitlab-ci.yaml, som beskriver implementering af applikationen og afvikling af tests. Listen viste sig at være ret omfangsrig, så den blev grundigt suppleret med kommentarer:

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

Nu i mappen .helm/templates lad os skabe YAML med Job - tests-job.yaml — at køre test og de Kubernetes-ressourcer, det har brug for. Se forklaringer efter liste:

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

Hvilken slags ressourcer beskrevet i denne konfiguration? Ved implementering opretter vi et unikt navneområde for projektet (dette er angivet i .gitlab-ci.yamltests-${CI_COMMIT_REF_SLUG}) og rul den ud:

  1. ConfigMap med testscript;
  2. Job med en beskrivelse af poden og det specificerede direktiv command, som netop kører testene;
  3. PV og PVC, som giver dig mulighed for at gemme testdata.

Vær opmærksom på den indledende betingelse med if i begyndelsen af ​​manifestet - derfor skal andre YAML-filer i Helm-diagrammet med applikationen pakkes ind i baglæns design, så de ikke bliver implementeret under test. Det er:

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

Men hvis testene kræver noget infrastruktur (for eksempel Redis, RabbitMQ, Mongo, PostgreSQL...) - deres YAML'er kan være nej sluk. Implementer dem også i et testmiljø... juster dem, som du finder passende, selvfølgelig.

Endelig berøring

Fordi montering og udrulning ved hjælp af werf-værker for nu kun på build-serveren (med gitlab-runner), og poden med test lanceres på masteren, skal du oprette en mappe /mnt/tests på mesteren og giv den til løberen, for eksempel via NFS. Et detaljeret eksempel med forklaringer kan findes i K8s dokumentation.

Resultatet bliver:

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

Ingen forbyder at lave en NFS-share direkte på gitlab-runner og derefter montere den i pods.

Bemærk

Du spørger måske, hvorfor komplicere alt ved at oprette et job, hvis du blot kan køre et script med test direkte på shell-løberen? Svaret er ret trivielt...

Nogle test kræver adgang til infrastrukturen (MongoDB, RabbitMQ, PostgreSQL osv.) for at verificere, at de fungerer korrekt. Vi gør testning samlet - med denne tilgang bliver det nemt at inkludere sådanne yderligere enheder. Ud over dette får vi standard implementeringstilgang (selv hvis du bruger NFS, yderligere montering af mapper).

Outcome

Hvad vil vi se, når vi anvender den forberedte konfiguration?

Sammenfletningsanmodningen vil vise oversigtsstatistikker for test, der er kørt i dens seneste pipeline:

JUnit i GitLab CI med Kubernetes

Hver fejl kan klikkes her for detaljer:

JUnit i GitLab CI med Kubernetes

NB: Den opmærksomme læser vil bemærke, at vi tester en NodeJS-applikation, og i skærmbillederne - .NET... Bliv ikke overrasket: det er bare, at der under udarbejdelsen af ​​artiklen ikke blev fundet fejl i test af den første applikation, men de blev fundet i en anden.

Konklusion

Som du kan se, intet kompliceret!

I princippet, hvis du allerede har en skalsamler, og den virker, men du ikke har brug for Kubernetes, vil det være en endnu enklere opgave at vedhæfte test til den end beskrevet her. Og i GitLab CI dokumentation du finder eksempler på Ruby, Go, Gradle, Maven og nogle andre.

PS

Læs også på vores blog:

Kilde: www.habr.com

Tilføj en kommentar