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 με το Job — 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

Αγοράστε αξιόπιστη φιλοξενία για ιστότοπους με προστασία DDoS, διακομιστές VPS VDS 🔥 Αγοράστε αξιόπιστη φιλοξενία ιστοσελίδων με προστασία DDoS, διακομιστές VPS VDS | ProHoster