JUnit yn GitLab CI mei Kubernetes

Nettsjinsteande it feit dat elkenien goed wit dat it testen fan jo software wichtich en needsaaklik is, en in protte hawwe it al in lange tiid automatysk dien, yn 'e grutte Habr wie d'r gjin ien resept foar it opsetten fan in kombinaasje fan sokke populêre produkten yn dizze niche as (ús favorite) GitLab en JUnit. Litte wy dizze gat folje!

JUnit yn GitLab CI mei Kubernetes

Ynliedend

Lit my earst wat kontekst jaan:

  • Sûnt al ús applikaasjes rinne op Kubernetes, sille wy beskôgje it útfieren fan testen op de passende ynfrastruktuer.
  • Foar montage en ynset brûke wy werf (wat ynfrastruktuerkomponinten oanbelanget betsjut dit ek automatysk dat Helm belutsen is).
  • Ik sil net yngean op 'e details fan' e eigentlike skepping fan tests: yn ús gefal skriuwt de kliïnt de tests sels, en wy soargje allinich foar har lansearring (en de oanwêzigens fan in oerienkommende rapport yn 'e fúzjefersyk).


Hoe sil de algemiene folchoarder fan aksjes der útsjen?

  1. It bouwen fan de applikaasje - wy sille de beskriuwing fan dit poadium weglitte.
  2. Implementearje de applikaasje nei in aparte nammeromte fan it Kubernetes-kluster en begjin te testen.
  3. Sykje nei artefakten en parsearjen fan JUnit-rapporten mei GitLab.
  4. In earder oanmakke nammeromte wiskje.

No - nei ymplemintaasje!

oanpassing

GitLab CI

Litte wy begjinne mei in fragmint .gitlab-ci.yaml, dy't beskriuwt it ynsetten fan de applikaasje en it útfieren fan tests. De fermelding blykte frijwat voluminous te wêzen, dus it waard yngeand oanfolle mei 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

No yn 'e map .helm/templates lit ús YAML meitsje mei Job - tests-job.yaml - om tests út te fieren en de Kubernetes-boarnen dy't it nedich is. Sjoch útlis nei list:

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

Wat soarte fan middels beskreaun yn dizze konfiguraasje? By it ynsetten meitsje wy in unike nammeromte foar it projekt (dit wurdt oanjûn yn .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) en rôlje it út:

  1. ConfigMap mei testskript;
  2. Taak mei in beskriuwing fan 'e pod en de oantsjutte rjochtline command, dy't krekt de tests rint;
  3. PV en PVC, wêrmei jo testgegevens opslaan kinne.

Jou omtinken oan de ynliedende betingst mei if oan it begjin fan it manifest - dêrom moatte oare YAML-bestannen fan it Helm-diagram mei de applikaasje wurde ferpakt yn omkeare ûntwerp sadat se net wurde ynset by testen. Dat is:

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

Lykwols, as de tests nedich wat ynfrastruktuer (bygelyks Redis, RabbitMQ, Mongo, PostgreSQL ...) - har YAML's kinne wêze net útsette. Ynsette se ek yn in testomjouwing ... oanpasse se as jo goed fine, fansels.

Finale berekkening

Omdat montage en ynset mei werfwurken foar no allinnich op 'e build-tsjinner (mei gitlab-runner), en de pod mei tests wurdt lansearre op' e master, moatte jo in map meitsje /mnt/tests op 'e master en jou it oan 'e rinner, bygelyks, fia NFS. In detaillearre foarbyld mei útlis is te finen yn K8s dokumintaasje.

It resultaat sil wêze:

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

Nimmen ferbiedt it meitsjen fan in NFS-diel direkt op gitlab-runner, en dan it montearjen yn pods.

remark

Jo kinne freegje wêrom alles komplisearje troch it meitsjen fan in baan as jo gewoan in skript kinne útfiere mei tests direkt op 'e shell-runner? It antwurd is frij triviaal ...

Guon tests fereaskje tagong ta de ynfrastruktuer (MongoDB, RabbitMQ, PostgreSQL, ensfh.) Om te kontrolearjen dat se goed wurkje. Wy meitsje testen ferienige - mei dizze oanpak wurdt it maklik om sokke ekstra entiteiten op te nimmen. Neist dit, wy krije standert ynset oanpak (sels as it brûken fan NFS, ekstra mounting fan mappen).

resultaat

Wat sille wy sjen as wy de tariede konfiguraasje tapasse?

It fúzjefersyk sil gearfettingsstatistiken sjen litte foar testen útfierd yn syn lêste pipeline:

JUnit yn GitLab CI mei Kubernetes

Elke flater kin hjir klikke foar details:

JUnit yn GitLab CI mei Kubernetes

NB: De oandachtige lêzer sil fernimme dat wy in NodeJS-applikaasje testen, en yn 'e skermôfbyldings - .NET ... Wês net fernuvere: it is gewoan dat by it tarieden fan it artikel gjin flaters fûn binne by it testen fan 'e earste applikaasje, mar se waarden fûn yn in oar.

konklúzje

Sa't jo sjen kinne, neat yngewikkeld!

Yn prinsipe, as jo al in shell-samler hawwe en it wurket, mar jo hawwe gjin Kubernetes nedich, dan sil it taheakjen fan testen in noch ienfâldiger taak wêze as hjir beskreaun. En yn GitLab CI dokumintaasje jo sille foarbylden fine foar Ruby, Go, Gradle, Maven en guon oaren.

PS

Lês ek op ús blog:

Boarne: www.habr.com

Add a comment