JUnit GitLab CI:ssä Kubernetesin kanssa

Huolimatta siitä, että kaikki tietävät täydellisesti, että ohjelmistojen testaus on tärkeää ja tarpeellista, ja monet ovat tehneet sitä automaattisesti jo pitkään, Habrin laajuudessa ei ollut yhtä reseptiä niin suosittujen tuotteiden yhdistelmän luomiseen. tämä markkinarako kuten (suosikkimme) GitLab ja JUnit . Täytetään tämä aukko!

JUnit GitLab CI:ssä Kubernetesin kanssa

johdannossa

Aluksi kerron hieman kontekstista:

  • Koska kaikki sovelluksemme toimivat Kubernetesissa, harkitsemme testien suorittamista sopivassa infrastruktuurissa.
  • Käytämme kokoonpanoon ja käyttöönottoon werf (infrastruktuurikomponenttien kannalta tämä tarkoittaa myös automaattisesti, että Helm on mukana).
  • En mene yksityiskohtiin varsinaiseen testien luomiseen: meidän tapauksessamme asiakas kirjoittaa testit itse, ja varmistamme vain niiden käynnistämisen (ja vastaavan raportin läsnäolon yhdistämispyynnössä).


Miltä toimintojen yleinen järjestys näyttää?

  1. Sovelluksen rakentaminen - jätämme pois tämän vaiheen kuvauksen.
  2. Ota sovellus käyttöön Kubernetes-klusterin erilliseen nimiavaruuteen ja aloita testaus.
  3. Artefaktien etsiminen ja JUnit-raporttien jäsentäminen GitLabin avulla.
  4. Aiemmin luodun nimitilan poistaminen.

Nyt - toteutukseen!

säätö

GitLab CI

Aloitetaan fragmentilla .gitlab-ci.yaml, joka kuvaa sovelluksen käyttöönottoa ja testien suorittamista. Listaus osoittautui melko laajaksi, joten sitä täydennettiin perusteellisesti kommenteilla:

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

Nyt hakemistossa .helm/templates luodaan YAML Jobin kanssa - tests-job.yaml — suorittaa testejä ja sen tarvitsemia Kubernetes-resursseja. Katso selitykset listauksen jälkeen:

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

Millaisia ​​resursseja kuvattu tässä kokoonpanossa? Käyttöönoton yhteydessä luomme projektille ainutlaatuisen nimiavaruuden (tämä on ilmoitettu kohdassa .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) ja rullaa se ulos:

  1. ConfigMap testikirjoituksen kanssa;
  2. Job podin kuvauksen ja määritetyn direktiivin kanssa command, joka vain suorittaa testit;
  3. PV ja PVC, joiden avulla voit tallentaa testitietoja.

Kiinnitä huomiota johdantoehtoon if manifestin alussa - vastaavasti muut ruorikaavion YAML-tiedostot sovelluksen kanssa on käärittävä päinvastoin suunnitella niin, että ne eivät pääse leviämään testauksen aikana. Tuo on:

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

Kuitenkin, jos testit vaativat jonkin verran infrastruktuuria (esimerkiksi Redis, RabbitMQ, Mongo, PostgreSQL...) - niiden YAML:t voivat olla ei sammuttaa. Ota ne käyttöön myös testiympäristössä... säätämällä niitä tietysti parhaaksi katsomallasi tavalla.

Lopullinen kosketus

Koska kokoonpano ja käyttöönotto werf-teoksilla toistaiseksi vain rakennuspalvelimella (gitlab-runnerilla) ja testejä sisältävä pod käynnistetään isäntäkoneessa, sinun on luotava hakemisto /mnt/tests isännille ja anna se juoksijalle, esimerkiksi NFS:n kautta. Yksityiskohtainen esimerkki selityksineen löytyy osoitteesta K8:n dokumentaatio.

Tuloksena on:

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

Kukaan ei kiellä tekemästä NFS-jakoa suoraan gitlab-runneriin ja sitten asentamasta sitä podeihin.

Huomata

Saatat kysyä, miksi monimutkaistaa kaikkea luomalla työ, jos voit yksinkertaisesti ajaa komentosarjan testeillä suoraan shell runnerissa? Vastaus on aika triviaali...

Jotkut testit vaativat pääsyn infrastruktuuriin (MongoDB, RabbitMQ, PostgreSQL jne.) varmistaakseen, että ne toimivat oikein. Teemme testauksesta yhtenäisen – tällä lähestymistavalla on helppoa sisällyttää tällaisia ​​lisäkokonaisuuksia. Tämän lisäksi saamme standardi käyttöönottotapa (vaikka käytettäisiin NFS:ää, hakemistojen lisäasennus).

Tulos

Mitä näemme, kun käytämme valmisteltua kokoonpanoa?

Yhdistämispyyntö näyttää yhteenvetotilastot viimeisimmässä putkistossaan suoritetuista testeistä:

JUnit GitLab CI:ssä Kubernetesin kanssa

Jokainen virhe voidaan napsauttaa tästä saadaksesi lisätietoja:

JUnit GitLab CI:ssä Kubernetesin kanssa

NB: Huomaavainen lukija huomaa, että testaamme NodeJS-sovellusta, ja kuvakaappauksissa - .NET... Älä ihmettele: artikkelia valmisteltaessa ei vain löytynyt virheitä ensimmäisen sovelluksen testauksessa, mutta ne löytyivät toisesta.

Johtopäätös

Kuten näette, ei mitään monimutkaista!

Periaatteessa, jos sinulla on jo shell-keräilijä ja se toimii, mutta et tarvitse Kubernetesia, testauksen liittäminen siihen on vielä yksinkertaisempi tehtävä kuin tässä kuvattu. Ja sisään GitLab CI -dokumentaatio löydät esimerkkejä Rubysta, Gosta, Gradlesta, Mavenista ja joistakin muista.

PS.

Lue myös blogistamme:

Lähde: will.com

Lisää kommentti