JUnit a GitLab CI amb Kubernetes

Malgrat que tothom sap perfectament que provar el vostre programari és important i necessari, i molts ho fan automàticament des de fa molt de temps, a la immensitat d'Habr no hi havia ni una recepta per configurar una combinació de productes tan populars a aquest nínxol com (el nostre favorit) GitLab i JUnit . Omplim aquest buit!

JUnit a GitLab CI amb Kubernetes

Introducció

En primer lloc, permeteu-me donar una mica de context:

  • Com que totes les nostres aplicacions s'executen a Kubernetes, considerarem fer proves a la infraestructura adequada.
  • Per al muntatge i el desplegament fem servir werf (en termes de components d'infraestructura, això també significa automàticament que Helm està implicat).
  • No entraré en els detalls de la creació real de les proves: en el nostre cas, el client escriu les proves ell mateix i només ens assegurem el llançament (i la presència d'un informe corresponent a la sol·licitud de fusió).


Com serà la seqüència general d'accions?

  1. Creació de l'aplicació: ometrem la descripció d'aquesta etapa.
  2. Desplegueu l'aplicació en un espai de noms independent del clúster de Kubernetes i comenceu a provar.
  3. Cercar artefactes i analitzar informes JUnit amb GitLab.
  4. Eliminació d'un espai de noms creat anteriorment.

Ara, a la implementació!

ajust

GitLab CI

Comencem amb un fragment .gitlab-ci.yaml, que descriu el desplegament de l'aplicació i l'execució de proves. La llista va resultar ser força voluminosa, per la qual cosa es va complementar a fons amb comentaris:

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

Ara al directori .helm/templates creem YAML amb Job - tests-job.yaml — per executar proves i els recursos de Kubernetes que necessita. Vegeu les explicacions després de la llista:

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

Quin tipus de recursos descrit en aquesta configuració? Quan es desplega, creem un espai de noms únic per al projecte (això s'indica a .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) i desplega-ho:

  1. ConfigMap amb guió de prova;
  2. treball amb una descripció del pod i la directiva especificada command, que només executa les proves;
  3. PV i PVC, que us permeten emmagatzemar dades de prova.

Preste atenció a la condició introductòria amb if al principi del manifest; en conseqüència, altres fitxers YAML del gràfic Helm amb l'aplicació s'han d'embolicar a al revés dissenyar de manera que no es despleguin durant les proves. Això és:

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

Tanmateix, si les proves requereixen d'alguna infraestructura (per exemple, Redis, RabbitMQ, Mongo, PostgreSQL...) - els seus YAML poden ser no tanca. Desplegueu-los també en un entorn de prova... ajustant-los com cregueu, és clar.

Tacte final

Perquè el muntatge i el desplegament amb werf funciona ara per ara només al servidor de compilació (amb gitlab-runner) i el pod amb proves es llança al mestre, haureu de crear un directori /mnt/tests sobre el mestre i donar-lo al corredor, per exemple, mitjançant NFS. Podeu trobar un exemple detallat amb explicacions a Documentació K8s.

El resultat serà:

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

Ningú prohibeix fer una compartició NFS directament a gitlab-runner i després muntar-la en pods.

Nota

Potser us preguntareu per què complicar-ho tot creant un treball si simplement podeu executar un script amb proves directament al shell runner? La resposta és bastant trivial...

Algunes proves requereixen accés a la infraestructura (MongoDB, RabbitMQ, PostgreSQL, etc.) per comprovar que funcionen correctament. Fem les proves unificades: amb aquest enfocament, és fàcil incloure aquestes entitats addicionals. A més d'això, aconseguim estàndard enfocament de desplegament (encara que utilitzeu NFS, muntatge addicional de directoris).

Resultat

Què veurem quan apliquem la configuració preparada?

La sol·licitud de combinació mostrarà estadístiques de resum de les proves executades a la seva última canalització:

JUnit a GitLab CI amb Kubernetes

Cada error es pot fer clic aquí per obtenir més informació:

JUnit a GitLab CI amb Kubernetes

NB: El lector atent s'adonarà que estem provant una aplicació NodeJS, i a les captures de pantalla - .NET... No us estranyeu: és que durant la preparació de l'article no s'han trobat errors en provar la primera aplicació, però sí es van trobar en un altre.

Conclusió

Com podeu veure, res complicat!

En principi, si ja teniu un col·lector d'intèrprets d'ordres i funciona, però no necessiteu Kubernetes, adjuntar-hi proves serà una tasca encara més senzilla que la que es descriu aquí. I en Documentació de GitLab CI trobareu exemples per a Ruby, Go, Gradle, Maven i alguns altres.

PS

Llegeix també al nostre blog:

Font: www.habr.com

Afegeix comentari