JUnit sa GitLab CI kasama ang Kubernetes

Sa kabila ng katotohanan na lubos na alam ng lahat na ang pagsubok sa iyong software ay mahalaga at kinakailangan, at marami ang awtomatikong ginagawa ito sa loob ng mahabang panahon, sa kalakhan ng Habr ay walang isang recipe para sa pag-set up ng kumbinasyon ng mga sikat na produkto sa niche na ito bilang (aming paborito) GitLab at JUnit . Punan natin ang puwang na ito!

JUnit sa GitLab CI kasama ang Kubernetes

Panimula

Una, hayaan mo akong magbigay ng ilang konteksto:

  • Dahil tumatakbo ang lahat ng aming application sa Kubernetes, isasaalang-alang namin ang pagpapatakbo ng mga pagsubok sa naaangkop na imprastraktura.
  • Para sa assembly at deployment ginagamit namin werf (sa mga tuntunin ng mga bahagi ng imprastraktura, awtomatiko rin itong nangangahulugan na kasama si Helm).
  • Hindi ako pupunta sa mga detalye ng aktwal na paglikha ng mga pagsubok: sa aming kaso, isinulat mismo ng kliyente ang mga pagsubok, at tinitiyak lamang namin ang kanilang paglulunsad (at ang pagkakaroon ng kaukulang ulat sa kahilingan ng pagsasama).


Ano ang magiging hitsura ng pangkalahatang pagkakasunud-sunod ng mga aksyon?

  1. Pagbuo ng application - aalisin namin ang paglalarawan ng yugtong ito.
  2. I-deploy ang application sa isang hiwalay na namespace ng Kubernetes cluster at simulan ang pagsubok.
  3. Paghahanap ng mga artifact at pag-parse ng mga ulat ng JUnit sa GitLab.
  4. Pagtanggal ng dating ginawang namespace.

Ngayon - sa pagpapatupad!

pag-aayos

GitLab CI

Magsimula tayo sa isang fragment .gitlab-ci.yaml, na naglalarawan sa pag-deploy ng application at pagpapatakbo ng mga pagsubok. Ang listahan ay naging napakalaki, kaya lubusan itong dinagdagan ng mga komento:

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

Ngayon sa direktoryo .helm/templates gumawa tayo ng YAML kasama si Job - tests-job.yaml — upang magpatakbo ng mga pagsubok at ang mga mapagkukunan ng Kubernetes na kailangan nito. Tingnan ang mga paliwanag pagkatapos ng listahan:

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

Anong uri ng mga mapagkukunan inilarawan sa pagsasaayos na ito? Kapag nagde-deploy, gumagawa kami ng natatanging namespace para sa proyekto (ito ay ipinahiwatig sa .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) at ilunsad ito:

  1. ConfigMap may test script;
  2. Trabaho na may isang paglalarawan ng pod at ang tinukoy na direktiba command, na nagpapatakbo lamang ng mga pagsubok;
  3. PV at PVC, na nagbibigay-daan sa iyong mag-imbak ng data ng pagsubok.

Bigyang-pansin ang panimulang kondisyon na may if sa simula ng manifest - nang naaayon, ang iba pang mga YAML file ng Helm chart na may application ay dapat na nakabalot sa baligtarin disenyo upang hindi sila ma-deploy sa panahon ng pagsubok. Yan ay:

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

Gayunpaman, kung ang mga pagsubok nangangailangan ng ilang imprastraktura (halimbawa, Redis, RabbitMQ, Mongo, PostgreSQL...) - ang kanilang mga YAML ay maaaring hindi patayin. I-deploy ang mga ito sa isang pagsubok na kapaligiran pati na rin... pagsasaayos sa kanila ayon sa nakikita mong akma, siyempre.

Huling ugnay

kasi assembly at deployment gamit ang werf works sa ngayon lamang sa build server (na may gitlab-runner), at ang pod na may mga pagsubok ay inilunsad sa master, kakailanganin mong lumikha ng isang direktoryo /mnt/tests sa panginoon at ibigay ito sa mananakbo, halimbawa, sa pamamagitan ng NFS. Ang isang detalyadong halimbawa na may mga paliwanag ay matatagpuan sa Dokumentasyon ng K8.

Ang magiging resulta ay:

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

Walang sinuman ang nagbabawal sa direktang pagbabahagi ng NFS sa gitlab-runner, at pagkatapos ay i-mount ito sa mga pod.

Nota

Maaaring nagtatanong ka kung bakit ginagawang kumplikado ang lahat sa pamamagitan ng paglikha ng isang Trabaho kung maaari ka lamang magpatakbo ng isang script na may mga pagsubok nang direkta sa shell runner? Ang sagot ay medyo walang kuwenta...

Ang ilang mga pagsubok ay nangangailangan ng access sa imprastraktura (MongoDB, RabbitMQ, PostgreSQL, atbp.) upang ma-verify na gumagana ang mga ito nang tama. Ginagawa naming pinag-isa ang pagsubok - sa diskarteng ito, nagiging madaling isama ang mga naturang karagdagang entity. Bilang karagdagan dito, nakukuha namin pamantayan diskarte sa pag-deploy (kahit na gumagamit ng NFS, karagdagang pag-mount ng mga direktoryo).

Resulta

Ano ang makikita natin kapag inilapat natin ang inihandang pagsasaayos?

Ang kahilingan sa pagsasama ay magpapakita ng buod na istatistika para sa mga pagsubok na tumatakbo sa pinakabagong pipeline nito:

JUnit sa GitLab CI kasama ang Kubernetes

Ang bawat error ay maaaring i-click dito para sa mga detalye:

JUnit sa GitLab CI kasama ang Kubernetes

NB: Mapapansin ng matulungin na mambabasa na sinusubok namin ang isang NodeJS application, at sa mga screenshot -. ay natagpuan sa iba.

Konklusyon

Tulad ng nakikita mo, walang kumplikado!

Sa prinsipyo, kung mayroon ka nang isang shell collector at ito ay gumagana, ngunit hindi mo kailangan ng Kubernetes, ang pag-attach ng pagsubok dito ay magiging isang mas simpleng gawain kaysa inilarawan dito. At sa Dokumentasyon ng GitLab CI makakahanap ka ng mga halimbawa para kay Ruby, Go, Gradle, Maven at ilang iba pa.

PS

Basahin din sa aming blog:

Pinagmulan: www.habr.com

Magdagdag ng komento