JUnit en GitLab CI con Kubernetes

A pesar de que todo o mundo sabe perfectamente que probar o teu software é importante e necesario, e moitos levan facendo de forma automática durante moito tempo, na inmensidade de Habr non había nin unha soa receita para configurar unha combinación de produtos tan populares. este nicho como (o noso favorito) GitLab e JUnit . Imos cubrir este oco!

JUnit en GitLab CI con Kubernetes

Introdución

En primeiro lugar, permíteme dar un pouco de contexto:

  • Dado que todas as nosas aplicacións se executan en Kubernetes, consideraremos realizar probas na infraestrutura adecuada.
  • Para montaxe e despregamento utilizamos werf (en termos de compoñentes de infraestrutura, isto tamén significa automaticamente que Helm está implicado).
  • Non entrarei nos detalles da creación real de probas: no noso caso, o cliente escribe el mesmo as probas e só aseguramos o seu lanzamento (e a presenza do informe correspondente na solicitude de fusión).


Como será a secuencia xeral de accións?

  1. Creación da aplicación: omitiremos a descrición desta fase.
  2. Implementa a aplicación nun espazo de nomes separado do clúster de Kubernetes e comeza a probar.
  3. Buscando artefactos e analizando informes JUnit con GitLab.
  4. Eliminando un espazo de nomes creado previamente.

Agora - á implementación!

axuste

GitLab CI

Comecemos cun fragmento .gitlab-ci.yaml, que describe a implantación da aplicación e a execución de probas. A lista resultou ser bastante voluminosa, polo que se complementou completamente con comentarios:

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

Agora no directorio .helm/templates imos crear YAML con Job - tests-job.yaml — para executar probas e os recursos de Kubernetes que necesita. Vexa as explicacións despois da lista:

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

Que tipo de recursos descrito nesta configuración? Ao implementar, creamos un espazo de nomes único para o proxecto (isto indícase en .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) e desenrola:

  1. ConfigMap con guión de proba;
  2. Traballo cunha descrición do pod e a directiva especificada command, que só executa as probas;
  3. PV e PVC, que che permiten almacenar datos de proba.

Preste atención á condición introdutoria con if ao comezo do manifesto; en consecuencia, outros ficheiros YAML do gráfico Helm coa aplicación deben estar envoltos en á inversa deseñar para que non se despreguen durante as probas. É dicir:

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

Con todo, se as probas requiren algunha infraestrutura (por exemplo, Redis, RabbitMQ, Mongo, PostgreSQL...) - os seus YAML poden ser non apagar. Imprógaos tamén nun ambiente de proba... axustándoos como creas oportuno, por suposto.

Preme final

Porque montaxe e despregamento usando werf works por agora no servidor de compilación (con gitlab-runner) e o pod con probas lánzase no mestre, terás que crear un directorio /mnt/tests sobre o mestre e dálle ao corredor, por exemplo, a través de NFS. Pódese atopar un exemplo detallado con explicacións en Documentación K8s.

O resultado 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

Ninguén prohibe facer un recurso compartido NFS directamente en gitlab-runner, e despois montalo en pods.

Nota

Podes estar preguntando por que complica todo creando un traballo se pode simplemente executar un script con probas directamente no shell runner? A resposta é bastante trivial...

Algunhas probas requiren acceso á infraestrutura (MongoDB, RabbitMQ, PostgreSQL, etc.) para verificar que funcionan correctamente. Facemos as probas unificadas; con este enfoque, faise doado incluír esas entidades adicionais. Ademais disto, conseguimos estándar enfoque de implantación (aínda que use NFS, montaxe adicional de directorios).

Resultado

Que veremos cando apliquemos a configuración preparada?

A solicitude de combinación mostrará estatísticas de resumo para as probas realizadas na súa última canalización:

JUnit en GitLab CI con Kubernetes

Cada erro pódese facer clic aquí para obter máis información:

JUnit en GitLab CI con Kubernetes

NB: O lector atento notará que estamos a probar unha aplicación NodeJS, e nas capturas de pantalla - .NET... Non te estrañes: é que ao preparar o artigo non se atoparon erros ao probar a primeira aplicación, pero si atopáronse noutro.

Conclusión

Como vedes, nada complicado!

En principio, se xa tes un colector de shell e funciona, pero non necesitas Kubernetes, engadirlle probas será unha tarefa aínda máis sinxela que a descrita aquí. E en Documentación de GitLab CI atoparás exemplos para Ruby, Go, Gradle, Maven e algúns outros.

PS

Lea tamén no noso blog:

Fonte: www.habr.com

Engadir un comentario