JUnit di GitLab CI dengan Kubernetes

Terlepas dari kenyataan bahwa semua orang tahu betul bahwa pengujian perangkat lunak Anda penting dan perlu, dan banyak yang telah melakukannya secara otomatis sejak lama, di Habr yang luas, tidak ada satu resep pun untuk menyiapkan kombinasi produk populer seperti itu di ceruk ini sebagai (favorit kami) GitLab dan JUnit . Mari kita isi kesenjangan ini!

JUnit di GitLab CI dengan Kubernetes

pengantar

Pertama, izinkan saya memberikan beberapa konteks:

  • Karena semua aplikasi kami berjalan di Kubernetes, kami akan mempertimbangkan untuk menjalankan pengujian pada infrastruktur yang sesuai.
  • Untuk perakitan dan penerapan kami menggunakan wer (dari segi komponen infrastruktur, otomatis Helm juga ikut terlibat).
  • Saya tidak akan membahas detail pembuatan pengujian yang sebenarnya: dalam kasus kami, klien menulis pengujian sendiri, dan kami hanya memastikan peluncurannya (dan adanya laporan terkait dalam permintaan penggabungan).


Seperti apa urutan tindakan secara umum?

  1. Membangun aplikasi - kami akan menghilangkan deskripsi tahap ini.
  2. Terapkan aplikasi ke namespace terpisah dari cluster Kubernetes dan mulai pengujian.
  3. Mencari artefak dan menguraikan laporan JUnit dengan GitLab.
  4. Menghapus namespace yang dibuat sebelumnya.

Sekarang - untuk implementasi!

pengaturan

GitLab CI

Mari kita mulai dengan sebuah fragmen .gitlab-ci.yaml, yang menjelaskan penerapan aplikasi dan menjalankan pengujian. Daftarnya ternyata cukup banyak, jadi dilengkapi dengan komentar:

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 di direktori .helm/templates mari buat YAML dengan Job - tests-job.yaml — untuk menjalankan pengujian dan sumber daya Kubernetes yang diperlukan. Lihat penjelasan setelah daftar:

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

Sumber daya apa dijelaskan dalam konfigurasi ini? Saat menerapkan, kami membuat namespace unik untuk proyek tersebut (ini ditunjukkan dalam .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) dan luncurkan:

  1. Peta Konfigurasi dengan skrip tes;
  2. Pekerjaan dengan deskripsi pod dan arahan yang ditentukan command, yang baru saja menjalankan pengujian;
  3. PV dan PVC, yang memungkinkan Anda menyimpan data pengujian.

Perhatikan kondisi pengantar dengan if di awal manifes - oleh karena itu, file YAML lain dari bagan Helm dengan aplikasi harus digabungkan balik desain sehingga mereka tidak dikerahkan selama pengujian. Itu adalah:

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

Namun jika tes memerlukan beberapa infrastruktur (misalnya, Redis, RabbitMQ, Mongo, PostgreSQL...) - YAML-nya dapat berupa tidak matikan. Terapkan juga ke dalam lingkungan pengujian... sesuaikan sesuai keinginan Anda, tentu saja.

Sentuhan terakhir

Karena perakitan dan penerapan menggunakan werf berfungsi untuk saat ini hanya di server build (dengan gitlab-runner), dan pod dengan pengujian diluncurkan di master, Anda perlu membuat direktori /mnt/tests pada tuannya dan memberikannya kepada pelari, misalnya melalui NFS. Contoh detail beserta penjelasannya dapat ditemukan di Dokumentasi K8.

Hasilnya adalah:

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

Tidak ada yang melarang membuat share NFS langsung di gitlab-runner, lalu memasangnya di pod.

Catatan

Anda mungkin bertanya mengapa mempersulit segalanya dengan membuat Pekerjaan jika Anda bisa menjalankan skrip dengan tes langsung di shell runner? Jawabannya cukup sepele...

Beberapa pengujian memerlukan akses ke infrastruktur (MongoDB, RabbitMQ, PostgreSQL, dll.) untuk memverifikasi bahwa pengujian tersebut berfungsi dengan benar. Kami menjadikan pengujian terpadu - dengan pendekatan ini, memasukkan entitas tambahan tersebut menjadi mudah. Selain itu, kami mendapatkan standar pendekatan penerapan (bahkan jika menggunakan NFS, pemasangan direktori tambahan).

Hasil

Apa yang akan kita lihat ketika kita menerapkan konfigurasi yang telah disiapkan?

Permintaan penggabungan akan menampilkan statistik ringkasan untuk pengujian yang dijalankan di pipeline terbarunya:

JUnit di GitLab CI dengan Kubernetes

Setiap kesalahan dapat diklik di sini untuk detailnya:

JUnit di GitLab CI dengan Kubernetes

NB: Pembaca yang penuh perhatian akan melihat bahwa kami sedang menguji aplikasi NodeJS, dan di tangkapan layar - .NET... Jangan kaget: hanya saja saat menyiapkan artikel, tidak ditemukan kesalahan dalam pengujian aplikasi pertama, tetapi mereka ditemukan di tempat lain.

Kesimpulan

Seperti yang Anda lihat, tidak ada yang rumit!

Pada prinsipnya, jika Anda sudah memiliki shell collector dan berfungsi, tetapi Anda tidak memerlukan Kubernetes, melampirkan pengujian ke dalamnya akan menjadi tugas yang lebih sederhana daripada yang dijelaskan di sini. Dan masuk Dokumentasi GitLab CI Anda akan menemukan contoh untuk Ruby, Go, Gradle, Maven dan beberapa lainnya.

PS

Baca juga di blog kami:

Sumber: www.habr.com

Tambah komentar