JUnit στο GitLab CI με Kubernetes

Παρά το γεγονός ότι όλοι γνωρίζουν πολύ καλά ότι η δοκιμή του λογισμικού σας είναι σημαντική και απαραίτητη, και πολλοί το κάνουν αυτόματα εδώ και πολύ καιρό, στην απεραντοσύνη του Habr δεν υπήρχε ούτε μία συνταγή για τη δημιουργία ενός συνδυασμού τέτοιων δημοφιλών προϊόντων στο αυτή η θέση ως (το αγαπημένο μας) GitLab και JUnit . Ας καλύψουμε αυτό το κενό!

JUnit στο GitLab CI με Kubernetes

εισαγωγικός

Αρχικά, επιτρέψτε μου να δώσω κάποιο πλαίσιο:

  • Δεδομένου ότι όλες οι εφαρμογές μας εκτελούνται στο Kubernetes, θα εξετάσουμε το ενδεχόμενο εκτέλεσης δοκιμών στην κατάλληλη υποδομή.
  • Για συναρμολόγηση και ανάπτυξη χρησιμοποιούμε werf (όσον αφορά τα στοιχεία υποδομής, αυτό σημαίνει αυτόματα ότι εμπλέκεται και η Helm).
  • Δεν θα υπεισέλθω στις λεπτομέρειες της πραγματικής δημιουργίας δοκιμών: στην περίπτωσή μας, ο πελάτης γράφει ο ίδιος τις δοκιμές και διασφαλίζουμε μόνο την εκκίνησή τους (και την παρουσία αντίστοιχης αναφοράς στο αίτημα συγχώνευσης).


Πώς θα μοιάζει η γενική ακολουθία των ενεργειών;

  1. Κατασκευή της εφαρμογής - θα παραλείψουμε την περιγραφή αυτού του σταδίου.
  2. Αναπτύξτε την εφαρμογή σε έναν ξεχωριστό χώρο ονομάτων του συμπλέγματος Kubernetes και ξεκινήστε τη δοκιμή.
  3. Αναζήτηση τεχνουργημάτων και ανάλυση αναφορών JUnit με το GitLab.
  4. Διαγραφή χώρου ονομάτων που δημιουργήθηκε προηγουμένως.

Τώρα - στην υλοποίηση!

προσαρμογή

GitLab CI

Ας ξεκινήσουμε με ένα απόσπασμα .gitlab-ci.yaml, το οποίο περιγράφει την ανάπτυξη της εφαρμογής και την εκτέλεση δοκιμών. Η λίστα αποδείχθηκε αρκετά ογκώδης, επομένως συμπληρώθηκε διεξοδικά με σχόλια:

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

Τώρα στον κατάλογο .helm/templates ας δημιουργήσουμε YAML με την εργασία - tests-job.yaml — για την εκτέλεση δοκιμών και των πόρων Kubernetes που χρειάζεται. Δείτε τις εξηγήσεις μετά την καταχώριση:

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

Τι είδους πόροι περιγράφεται σε αυτή τη διαμόρφωση; Κατά την ανάπτυξη, δημιουργούμε έναν μοναδικό χώρο ονομάτων για το έργο (αυτό υποδεικνύεται στο .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) και ξεδιπλώστε το:

  1. ConfigMap με σενάριο δοκιμής?
  2. Δουλειά με περιγραφή του pod και της καθορισμένης οδηγίας command, το οποίο απλώς εκτελεί τις δοκιμές.
  3. Φ/Β και PVC, που σας επιτρέπουν να αποθηκεύετε δεδομένα δοκιμής.

Δώστε προσοχή στην εισαγωγική συνθήκη με if στην αρχή του δηλωτικού - κατά συνέπεια, άλλα αρχεία YAML του διαγράμματος Helm με την εφαρμογή πρέπει να είναι τυλιγμένα σε ΑΝΤΙΣΤΡΟΦΗ σχεδιάστε έτσι ώστε να μην αναπτύσσονται κατά τη διάρκεια της δοκιμής. Αυτό είναι:

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

Ωστόσο, εάν οι δοκιμές απαιτούν κάποια υποδομή (για παράδειγμα, Redis, RabbitMQ, Mongo, PostgreSQL...) - τα YAML τους μπορούν να είναι όχι σβήνω. Αναπτύξτε τα και σε ένα δοκιμαστικό περιβάλλον... προσαρμόζοντάς τα όπως σας ταιριάζει, φυσικά.

Τελική αφή

Επειδή συναρμολόγηση και ανάπτυξη με χρήση εργασιών werf προς το παρόν μόνο στον διακομιστή κατασκευής (με το gitlab-runner) και το pod με δοκιμές εκκινείται στον κύριο, θα χρειαστεί να δημιουργήσετε έναν κατάλογο /mnt/tests στον κύριο και δώσε το στον δρομέα, για παράδειγμα, μέσω NFS. Ένα λεπτομερές παράδειγμα με επεξηγήσεις μπορείτε να βρείτε στο τεκμηρίωση K8s.

Το αποτέλεσμα θα είναι:

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

Κανείς δεν απαγορεύει να δημιουργήσετε ένα κοινόχρηστο στοιχείο NFS απευθείας στο gitlab-runner και στη συνέχεια να το τοποθετήσετε σε pods.

Σημείωση

Μπορεί να ρωτάτε γιατί να περιπλέκετε τα πάντα δημιουργώντας μια Εργασία, εάν μπορείτε απλώς να εκτελέσετε ένα σενάριο με δοκιμές απευθείας στο πρόγραμμα εκτέλεσης του κελύφους; Η απάντηση είναι πολύ ασήμαντη...

Ορισμένες δοκιμές απαιτούν πρόσβαση στην υποδομή (MongoDB, RabbitMQ, PostgreSQL, κ.λπ.) για να επαληθευτεί ότι λειτουργούν σωστά. Κάνουμε τις δοκιμές ενοποιημένες - με αυτήν την προσέγγιση, γίνεται εύκολο να συμπεριληφθούν τέτοιες πρόσθετες οντότητες. Εκτός από αυτό, παίρνουμε πρότυπο προσέγγιση ανάπτυξης (ακόμα και αν χρησιμοποιείτε NFS, πρόσθετη προσάρτηση καταλόγων).

Αποτέλεσμα

Τι θα δούμε όταν εφαρμόσουμε την προετοιμασμένη διαμόρφωση;

Το αίτημα συγχώνευσης θα εμφανίζει συνοπτικά στατιστικά στοιχεία για δοκιμές που εκτελούνται στην τελευταία του σειρά:

JUnit στο GitLab CI με Kubernetes

Κάθε σφάλμα μπορεί να γίνει κλικ εδώ για λεπτομέρειες:

JUnit στο GitLab CI με Kubernetes

NB: Ο προσεκτικός αναγνώστης θα παρατηρήσει ότι δοκιμάζουμε μια εφαρμογή NodeJS και στα στιγμιότυπα οθόνης - .NET... Μην εκπλαγείτε: απλώς κατά την προετοιμασία του άρθρου, δεν βρέθηκαν σφάλματα στη δοκιμή της πρώτης εφαρμογής, αλλά βρέθηκαν σε άλλο.

Συμπέρασμα

Όπως μπορείτε να δείτε, τίποτα περίπλοκο!

Κατ 'αρχήν, εάν έχετε ήδη έναν συλλέκτη κελύφους και λειτουργεί, αλλά δεν χρειάζεστε το Kubernetes, η προσάρτηση της δοκιμής σε αυτόν θα είναι ακόμη πιο απλή από ό,τι περιγράφεται εδώ. Και στο Τεκμηρίωση GitLab CI θα βρείτε παραδείγματα για Ruby, Go, Gradle, Maven και μερικά άλλα.

PS

Διαβάστε επίσης στο blog μας:

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο