JUnit v GitLab CI s Kubernetes

Napriek tomu, že každý veľmi dobre vie, že testovanie vášho softvéru je dôležité a potrebné a mnohí to už dávno robia automaticky, v rozľahlosti Habr neexistoval jediný recept na nastavenie kombinácie takýchto obľúbených produktov v r. tento výklenok ako (náš obľúbený) GitLab a JUnit . Vyplňte túto medzeru!

JUnit v GitLab CI s Kubernetes

Úvodná

Najprv mi dovoľte uviesť kontext:

  • Keďže všetky naše aplikácie bežia na Kubernetes, zvážime spustenie testov na príslušnej infraštruktúre.
  • Na montáž a nasadenie používame werf (z hľadiska komponentov infraštruktúry to automaticky znamená aj účasť Helm).
  • Nebudem zachádzať do detailov samotnej tvorby testov: v našom prípade si klient píše testy sám a my zabezpečujeme len ich spustenie (a prítomnosť zodpovedajúceho reportu v žiadosti o zlúčenie).


Ako bude vyzerať všeobecná postupnosť akcií?

  1. Budovanie aplikácie – popis tejto fázy vynecháme.
  2. Nasaďte aplikáciu do samostatného menného priestoru klastra Kubernetes a začnite testovať.
  3. Hľadanie artefaktov a analýza správ JUnit pomocou GitLab.
  4. Odstránenie predtým vytvoreného priestoru názvov.

Teraz - k implementácii!

nastavenie

GitLab CI

Začnime fragmentom .gitlab-ci.yaml, ktorá popisuje nasadenie aplikácie a spustenie testov. Výpis sa ukázal byť dosť objemný, takže bol dôkladne doplnený komentármi:

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

Teraz v adresári .helm/templates poďme vytvoriť YAML s Jobom - tests-job.yaml — na spustenie testov a zdrojov Kubernetes, ktoré potrebuje. Pozrite si vysvetlenia po uvedení:

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

Aké zdroje popísané v tejto konfigurácii? Pri nasadzovaní vytvoríme pre projekt jedinečný menný priestor (toto je uvedené v .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) a rozviňte ho:

  1. ConfigMap s testovacím skriptom;
  2. zamestnania s popisom pod a zadanej smernice command, ktorý práve spúšťa testy;
  3. PV a PVC, ktoré vám umožňujú ukladať testovacie údaje.

Venujte pozornosť úvodnej podmienke s if na začiatku manifestu - podľa toho musia byť zabalené ďalšie YAML súbory Helmovej tabuľky s aplikáciou obrátene navrhnúť tak, aby nedošlo k ich nasadeniu počas testovania. To je:

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

Ak však testy vyžadujú určitú infraštruktúru (napríklad Redis, RabbitMQ, Mongo, PostgreSQL...) – ich YAML môžu byť nie vypnúť. Nasaďte ich aj do testovacieho prostredia... samozrejme upravte ich podľa vlastného uváženia.

Konečný dotyk

Pretože montáž a nasadenie pomocou werf zatiaľ funguje iba na zostavovacom serveri (s gitlab-runner) a modul s testami je spustený na hlavnom serveri, budete musieť vytvoriť adresár /mnt/tests na pána a dať to bežcovi, napríklad cez NFS. Podrobný príklad s vysvetlivkami nájdete v Dokumentácia K8s.

Výsledkom bude:

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

Nikto nezakazuje vytvoriť zdieľanie NFS priamo na gitlab-runner a potom ho namontovať do modulov.

Poznámka

Možno sa pýtate, prečo všetko komplikovať vytváraním úlohy, ak môžete jednoducho spustiť skript s testami priamo na shell runner? Odpoveď je dosť triviálna...

Niektoré testy vyžadujú prístup k infraštruktúre (MongoDB, RabbitMQ, PostgreSQL atď.), aby sa overilo, či fungujú správne. Testovanie je zjednotené – vďaka tomuto prístupu je jednoduché zahrnúť aj takéto ďalšie entity. Okrem toho dostaneme standard nasadzovací prístup (aj pri použití NFS, dodatočné pripojenie adresárov).

Výsledok

Čo uvidíme, keď aplikujeme pripravenú konfiguráciu?

Žiadosť o zlúčenie zobrazí súhrnnú štatistiku testov spustených v najnovšom kanáli:

JUnit v GitLab CI s Kubernetes

Podrobnosti o každej chybe získate kliknutím sem:

JUnit v GitLab CI s Kubernetes

NB: Pozorný čitateľ si všimne, že testujeme NodeJS aplikáciu a na screenshotoch - .NET... Nečudujte sa: pri príprave článku sa pri testovaní prvej aplikácie nenašli žiadne chyby, ale sa našli v inom.

Záver

Ako vidíte, nič zložité!

V zásade, ak už máte zberač shellu a funguje, ale nepotrebujete Kubernetes, pripojenie testovania k nemu bude ešte jednoduchšia úloha, ako je tu opísané. A v Dokumentácia GitLab CI nájdete príklady pre Ruby, Go, Gradle, Maven a niektoré ďalšie.

PS

Prečítajte si aj na našom blogu:

Zdroj: hab.com

Pridať komentár