JUnit در GitLab CI با Kubernetes

علیرغم این واقعیت که همه به خوبی می‌دانند که تست نرم‌افزار شما مهم و ضروری است و بسیاری مدت‌هاست که این کار را به صورت خودکار انجام می‌دهند، اما در وسعت هابر حتی یک دستورالعمل برای راه‌اندازی ترکیبی از چنین محصولات محبوبی وجود نداشت. این طاقچه به عنوان (مورد علاقه ما) GitLab و JUnit. بیایید این شکاف را پر کنیم!

JUnit در GitLab CI با Kubernetes

مقدماتی

اول، اجازه دهید یک زمینه را بیان کنم:

  • از آنجایی که همه برنامه‌های ما روی Kubernetes اجرا می‌شوند، آزمایش‌هایی را روی زیرساخت مناسب اجرا می‌کنیم.
  • برای مونتاژ و استقرار ما استفاده می کنیم ورف (از نظر اجزای زیرساخت، این نیز به طور خودکار به این معنی است که 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

کوبرنیتس

اکنون در دایرکتوری .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. کار با شرح غلاف و بخشنامه مشخص شده command، که فقط تست ها را اجرا می کند.
  3. PV و PVC، که به شما امکان می دهد داده های آزمایش را ذخیره کنید.

به شرط مقدماتی با توجه کنید if در ابتدای مانیفست - بر این اساس، سایر فایل های YAML نمودار Helm با برنامه باید در بسته بندی شوند معکوس طوری طراحی کنید که در طول آزمایش مستقر نشوند. به این معنا که:

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

با این حال، اگر آزمایشات نیاز به زیرساخت (به عنوان مثال، Redis، RabbitMQ، Mongo، PostgreSQL...) - YAML های آنها می تواند باشد هیچ خاموش کردن آنها را در یک محیط آزمایشی نیز مستقر کنید... البته آنها را به دلخواه تنظیم کنید.

لمس نهایی

زیرا مونتاژ و استقرار با استفاده از werf در حال حاضر کار می کند تنها در سرور بیلد (با gitlab-runner)، و پاد با تست‌ها روی Master راه‌اندازی می‌شود، باید یک دایرکتوری ایجاد کنید. /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 و سپس نصب آن در پادها را ممنوع نمی کند.

یادداشت

ممکن است بپرسید که چرا همه چیز را با ایجاد یک Job پیچیده می‌کنید، اگر به سادگی می‌توانید یک اسکریپت را با آزمایش‌هایی مستقیماً روی شل‌رانر اجرا کنید؟ پاسخ کاملاً پیش پا افتاده است ...

برخی از تست‌ها نیاز به دسترسی به زیرساخت (MongoDB، RabbitMQ، PostgreSQL، و غیره) دارند تا بررسی شود که درست کار می‌کنند. ما آزمایش را یکپارچه می کنیم - با این رویکرد، گنجاندن چنین موجودیت های اضافی آسان می شود. علاوه بر این، ما دریافت می کنیم استاندارد رویکرد استقرار (حتی در صورت استفاده از NFS، نصب اضافی دایرکتوری ها).

نتیجه

وقتی پیکربندی آماده شده را اعمال می کنیم چه خواهیم دید؟

درخواست ادغام، آمار خلاصه ای را برای آزمایش های انجام شده در آخرین خط لوله خود نشان می دهد:

JUnit در GitLab CI با Kubernetes

هر خطا را می توان برای جزئیات اینجا کلیک کرد:

JUnit در GitLab CI با Kubernetes

NB: خواننده با دقت متوجه می شود که ما در حال آزمایش یک برنامه NodeJS هستیم و در تصاویر - .NET... تعجب نکنید: فقط در هنگام آماده سازی مقاله، هیچ خطایی در آزمایش برنامه اول مشاهده نشد، اما آنها در دیگری یافت شدند.

نتیجه

همانطور که می بینید، هیچ چیز پیچیده ای نیست!

در اصل، اگر قبلاً یک کلکتور پوسته دارید و کار می کند، اما به Kubernetes نیاز ندارید، اتصال آزمایش به آن ساده تر از آنچه در اینجا توضیح داده شده است، خواهد بود. و در مستندات GitLab CI نمونه هایی برای Ruby، Go، Gradle، Maven و برخی دیگر را خواهید یافت.

PS

در وبلاگ ما نیز بخوانید:

منبع: www.habr.com

اضافه کردن نظر