JUnit en GitLab CI con Kubernetes

A pesar de que todos saben que es importante y necesario probar su software, y muchos lo han estado haciendo automáticamente durante mucho tiempo, no hay una sola receta para configurar un montón de productos tan populares en este nicho como (querido) GitLab y JUnit . ¡Llena este vacío!



Introductorio


Primero, describiré el contexto:

  • Dado que todas nuestras aplicaciones funcionan en Kubernetes, consideraremos ejecutar pruebas en la infraestructura adecuada.
  • Para el ensamblaje y la implementación usamos werf (en el sentido de componentes de infraestructura, esto también significa automáticamente que Helm está involucrado).
  • No entraré en detalles para crear pruebas directamente: en nuestro caso, el cliente escribe las pruebas por sí mismo y solo nos aseguramos de que se ejecuten (y el informe correspondiente está disponible en la solicitud de fusión).

¿Cómo será la secuencia general de acciones?

  1. Ensamblaje de la aplicación: omitiremos la descripción de esta etapa.
  2. Implemente la aplicación en un espacio de nombres de clúster Kubernetes separado y ejecute las pruebas.
  3. Busque artefactos y analice un informe JUnit de GitLab.
  4. Eliminar el espacio de nombres creado anteriormente.

Ahora - a la implementación!

Personalización


Gitlab ci


Comencemos con el fragmento .gitlab-ci.yaml que describe la implementación de la aplicación y ejecuta las pruebas. La lista resultó ser bastante voluminosa, por lo tanto, se completa 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


Ahora en el directorio .helm/templates , cree un YAML con Job - tests-job.yaml - para ejecutar las pruebas y los recursos de Kubernetes que necesita. Explicaciones ver después de la 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 }} 

¿Qué recursos se describen en esta configuración? Al implementar, cree un espacio de nombres único para el proyecto (esto también se indica en .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG} ) y enróllelo en él:

  1. ConfigMap con un script de prueba;
  2. Trabajo con una descripción de pod y la directiva de command especificada, que solo ejecuta las pruebas;
  3. PV y PVC , que le permite almacenar datos de prueba.

Preste atención a la condición de introducción con if al comienzo del manifiesto; en consecuencia, otros archivos YAML del gráfico Helm con la aplicación deben envolverse en la construcción inversa para que no se implementen durante la prueba. Eso es:

 {{- if ne .Values.global.run_tests "yes" }} ---    {{- end }} 

Sin embargo, si las pruebas requieren alguna infraestructura (por ejemplo, Redis, RabbitMQ, Mongo, PostgreSQL ...), sus YAML pueden desactivarse. Impleméntelos en un entorno de prueba ... por supuesto, retoque a su gusto.

Toque final


Porque el ensamblaje y la implementación usando werf hasta ahora solo funciona en el servidor de compilación (con gitlab-runner), y el pod con pruebas se ejecuta en el asistente, deberá crear el directorio /mnt/tests en el asistente y dárselo al corredor, por ejemplo, a través de NFS . Un ejemplo detallado con explicaciones se puede encontrar en la documentación de K8s .

El 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 

Nadie prohíbe hacer una bola NFS directamente en el gitlab-runner y luego montarla en vainas.

Nota


Puede preguntar, ¿por qué complicar todo con la creación de Job, si solo puede ejecutar el script de prueba directamente en el shell runner? La respuesta es bastante trivial ...

Algunas pruebas requieren acceso a la infraestructura (MongoDB, RabbitMQ, PostgreSQL, etc.) para verificar la exactitud de trabajar con ellas. Hacemos que las pruebas sean unificadas: con este enfoque, es fácil incluir tales entidades adicionales. Además de esto, obtenemos un enfoque estándar en la implementación (incluso si se usa NFS, montaje de directorio adicional).

Resultado


¿Qué veremos cuando apliquemos la configuración preparada?

La solicitud de combinación mostrará estadísticas resumidas sobre las pruebas lanzadas en su última canalización:



Puede hacer clic en cada error aquí para obtener detalles:



NB : Un lector atento notará que estamos probando una aplicación NodeJS, y en las capturas de pantalla - .NET ... No se sorprenda: solo como parte de la preparación del artículo, no hubo errores al probar la primera aplicación, pero se encontraron en otra.

Conclusión


Al parecer, nada complicado!

En principio, si ya tiene un constructor de shell y funciona, y no necesita Kubernetes, atornillar las pruebas será una tarea aún más simple que la descrita aquí. Y en la documentación de GitLab CI encontrará ejemplos para Ruby, Go, Gradle, Maven y algunos otros.

PS


Lea también en nuestro blog:

Source: https://habr.com/ru/post/460897/


All Articles