Kubernetes ile GitLab CI'da JUnit

Yazılımınızı test etmenin önemli ve gerekli olduğunu herkesin çok iyi bilmesine ve birçoğunun bunu uzun süredir otomatik olarak yapmasına rağmen, Habr'ın geniş topraklarında bu kadar popüler ürünlerin bir kombinasyonunu oluşturmak için tek bir tarif yoktu. Bu niş (en sevdiğimiz) GitLab ve JUnit olarak. Bu boşluğu dolduralım!

Kubernetes ile GitLab CI'da JUnit

Giriş

Öncelikle biraz bağlam aktarayım:

  • Tüm uygulamalarımız Kubernetes üzerinde çalıştığından testleri uygun altyapı üzerinde çalıştırmayı değerlendireceğiz.
  • Montaj ve dağıtım için kullanıyoruz Werf (altyapı bileşenleri açısından bu aynı zamanda otomatik olarak Helm'in de dahil olduğu anlamına gelir).
  • Gerçek test oluşturma ayrıntılarına girmeyeceğim: bizim durumumuzda müşteri testleri kendisi yazar ve biz yalnızca bunların başlatılmasını (ve birleştirme talebinde ilgili bir raporun varlığını) sağlarız.


Genel eylem sırası nasıl görünecek?

  1. Uygulamanın oluşturulması - bu aşamanın açıklamasını atlayacağız.
  2. Uygulamayı Kubernetes kümesinin ayrı bir ad alanına dağıtın ve test etmeye başlayın.
  3. Yapıları arama ve JUnit raporlarını GitLab ile ayrıştırma.
  4. Daha önce oluşturulmuş bir ad alanını silme.

Şimdi - uygulamaya!

Ayar

GitLab CI

Bir parçayla başlayalım .gitlab-ci.yaml, uygulamanın dağıtılmasını ve testlerin çalıştırılmasını açıklar. Listenin oldukça hacimli olduğu ortaya çıktı, bu nedenle yorumlarla tamamen desteklendi:

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

Şimdi dizinde .helm/templates hadi Job ile YAML oluşturalım - tests-job.yaml — testleri ve ihtiyaç duyduğu Kubernetes kaynaklarını çalıştırmak için. Listelemeden sonra açıklamalara bakın:

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

Ne tür kaynaklar Bu konfigürasyonda tanımlandı mı? Dağıtım sırasında proje için benzersiz bir ad alanı oluştururuz (bu, .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) ve yuvarlayın:

  1. Yapılandırma haritası test komut dosyasıyla;
  2. İş bölmenin açıklaması ve belirtilen direktifle birlikte commandsadece testleri çalıştıran;
  3. PV ve PVCtest verilerini saklamanıza olanak tanır.

Giriş durumuna dikkat edin if bildirimin başında - buna göre, Helm grafiğinin uygulamayla birlikte diğer YAML dosyaları sarılmalıdır обратную test sırasında konuşlandırılmayacak şekilde tasarlayın. Yani:

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

Ancak eğer testler biraz altyapı gerektiriyor (örneğin, Redis, RabbitMQ, Mongo, PostgreSQL...) - YAML'leri hayır kapamak. Bunları bir test ortamına da dağıtın... Elbette uygun gördüğünüz şekilde ayarlayın.

Son dokunuş

Çünkü werf kullanarak montaj ve dağıtım şimdilik çalışıyor sadece derleme sunucusunda (gitlab-runner ile) ve testlerin bulunduğu bölme ana bilgisayarda başlatıldığında, bir dizin oluşturmanız gerekecektir. /mnt/tests ustaya ve koşucuya ver, örneğin, NFS yoluyla. Açıklamalarla birlikte ayrıntılı bir örneği şu adreste bulabilirsiniz: K8'in belgeleri.

Sonuç şöyle olacaktır:

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

Hiç kimse doğrudan gitlab-runner'da bir NFS paylaşımı yapmayı ve ardından bunu bölmelere monte etmeyi yasaklamaz.

Dikkat

Doğrudan kabuk çalıştırıcısında testlerle bir komut dosyası çalıştırabiliyorsanız, bir İş oluşturarak neden her şeyi karmaşık hale getirdiğinizi soruyor olabilirsiniz? Cevap oldukça önemsiz...

Bazı testlerin doğru çalıştığını doğrulamak için altyapıya (MongoDB, RabbitMQ, PostgreSQL vb.) erişim gerekir. Testleri birleştirilmiş hale getiriyoruz; bu yaklaşımla bu tür ek birimlerin dahil edilmesi kolaylaşıyor. Buna ek olarak şunu alıyoruz: standart dağıtım yaklaşımı (NFS kullanılıyor olsa bile, dizinlerin ek montajı).

sonuç

Hazırlanan konfigürasyonu uyguladığımızda ne göreceğiz?

Birleştirme isteği, en son ardışık düzende çalıştırılan testlere ilişkin özet istatistikleri gösterecektir:

Kubernetes ile GitLab CI'da JUnit

Ayrıntılar için her hataya buraya tıklanabilir:

Kubernetes ile GitLab CI'da JUnit

NB: Dikkatli okuyucu, bir NodeJS uygulamasını test ettiğimizi ve ekran görüntülerinde - .NET... Şaşırmayın: makaleyi hazırlarken ilk uygulamanın testinde hiçbir hata bulunamadı, ancak onlar başka birinde bulundu.

Sonuç

Gördüğünüz gibi karmaşık bir şey yok!

Prensip olarak, zaten bir kabuk toplayıcınız varsa ve çalışıyorsa ancak Kubernetes'e ihtiyacınız yoksa, buna test eklemek burada açıklanandan daha basit bir görev olacaktır. Ve GitLab CI belgeleri Ruby, Go, Gradle, Maven ve diğerleri için örnekler bulacaksınız.

PS

Blogumuzda da okuyun:

Kaynak: habr.com

Yorum ekle