JUnit dalam GitLab CI dengan Kubernetes

Walaupun fakta bahawa semua orang tahu betul-betul bahawa menguji perisian anda adalah penting dan perlu, dan ramai yang telah melakukannya secara automatik untuk masa yang lama, dalam keluasan Habr tidak ada satu pun resipi untuk menyediakan gabungan produk popular sedemikian dalam niche ini sebagai (kegemaran kami) GitLab dan JUnit . Mari kita penuhi jurang ini!

JUnit dalam GitLab CI dengan Kubernetes

pengenalan

Pertama, izinkan saya memberikan beberapa konteks:

  • Memandangkan semua aplikasi kami dijalankan pada Kubernetes, kami akan mempertimbangkan untuk menjalankan ujian pada infrastruktur yang sesuai.
  • Untuk pemasangan dan penggunaan yang kami gunakan werf (dari segi komponen infrastruktur, ini juga secara automatik bermakna Helm terlibat).
  • Saya tidak akan pergi ke butiran penciptaan sebenar ujian: dalam kes kami, pelanggan menulis ujian itu sendiri, dan kami hanya memastikan pelancarannya (dan kehadiran laporan yang sepadan dalam permintaan gabungan).


Apakah rupa urutan umum tindakan itu?

  1. Membina aplikasi - kami akan meninggalkan penerangan peringkat ini.
  2. Sebarkan aplikasi ke ruang nama yang berasingan bagi kelompok Kubernetes dan mulakan ujian.
  3. Mencari artifak dan menghuraikan laporan JUnit dengan GitLab.
  4. Memadam ruang nama yang dibuat sebelum ini.

Sekarang - untuk pelaksanaan!

pelarasan

GitLab CI

Mari kita mulakan dengan serpihan .gitlab-ci.yaml, yang menerangkan penggunaan aplikasi dan menjalankan ujian. Penyenaraian itu ternyata agak besar, jadi ia telah ditambah dengan ulasan:

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

Sekarang dalam direktori .helm/templates mari buat YAML dengan Job - tests-job.yaml — untuk menjalankan ujian dan sumber Kubernetes yang diperlukannya. Lihat penjelasan selepas penyenaraian:

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

Apakah jenis sumber diterangkan dalam konfigurasi ini? Apabila menggunakan, kami mencipta ruang nama unik untuk projek (ini ditunjukkan dalam .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) dan gulungkannya:

  1. Peta Konfigurasi dengan skrip ujian;
  2. Kerja dengan perihalan pod dan arahan yang ditentukan command, yang hanya menjalankan ujian;
  3. PV dan PVC, yang membolehkan anda menyimpan data ujian.

Beri perhatian kepada syarat pengenalan dengan if pada permulaan manifes - oleh itu, fail YAML lain carta Helm dengan aplikasi mesti dibungkus terbalik reka bentuk supaya mereka tidak digunakan semasa ujian. Itu dia:

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

Walau bagaimanapun, jika ujian memerlukan beberapa infrastruktur (contohnya, Redis, RabbitMQ, Mongo, PostgreSQL...) - YAML mereka boleh tiada matikan. Sebarkan mereka ke dalam persekitaran ujian juga... laraskannya mengikut kesesuaian, sudah tentu.

Sentuhan akhir

Kerana pemasangan dan penempatan menggunakan kerja-kerja werf buat masa ini sahaja pada pelayan binaan (dengan gitlab-runner), dan pod dengan ujian dilancarkan pada induk, anda perlu membuat direktori /mnt/tests pada tuan dan berikan kepada pelari, contohnya, melalui NFS. Contoh terperinci dengan penjelasan boleh didapati di dokumentasi K8s.

Hasilnya akan menjadi:

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

Tiada siapa yang melarang membuat bahagian NFS secara langsung pada gitlab-runner, dan kemudian memasangnya dalam pod.

Nota

Anda mungkin bertanya mengapa merumitkan segala-galanya dengan membuat Kerja jika anda boleh menjalankan skrip dengan ujian terus pada pelari shell? Jawapannya agak remeh...

Sesetengah ujian memerlukan akses kepada infrastruktur (MongoDB, RabbitMQ, PostgreSQL, dll.) untuk mengesahkan bahawa ia berfungsi dengan betul. Kami menjadikan ujian bersatu - dengan pendekatan ini, menjadi mudah untuk memasukkan entiti tambahan tersebut. Di samping itu, kami mendapat standard pendekatan penggunaan (walaupun menggunakan NFS, pemasangan tambahan direktori).

Keputusan

Apakah yang akan kita lihat apabila kita menggunakan konfigurasi yang disediakan?

Permintaan gabungan akan menunjukkan statistik ringkasan untuk ujian yang dijalankan dalam saluran paip terbaharunya:

JUnit dalam GitLab CI dengan Kubernetes

Setiap ralat boleh diklik di sini untuk butiran:

JUnit dalam GitLab CI dengan Kubernetes

NB: Pembaca yang penuh perhatian akan melihat bahawa kami sedang menguji aplikasi NodeJS, dan dalam tangkapan skrin - .NET... Jangan terkejut: cuma semasa menyediakan artikel, tiada ralat ditemui dalam menguji aplikasi pertama, tetapi mereka ditemui di tempat lain.

Kesimpulan

Seperti yang anda lihat, tiada yang rumit!

Pada dasarnya, jika anda sudah mempunyai pengumpul shell dan ia berfungsi, tetapi anda tidak memerlukan Kubernetes, melampirkan ujian padanya akan menjadi tugas yang lebih mudah daripada yang diterangkan di sini. Dan dalam dokumentasi GitLab CI anda akan menemui contoh untuk Ruby, Go, Gradle, Maven dan beberapa yang lain.

PS

Baca juga di blog kami:

Sumber: www.habr.com

Tambah komen