JUnit in GitLab CI mit Kubernetes

Obwohl jeder weiß, dass es wichtig und notwendig ist, Ihre Software zu testen, und viele dies schon lange automatisch tun, gibt es in den offenen Räumen von Habr kein einziges Rezept, um in dieser Nische eine Reihe so beliebter Produkte wie (unser Lieblings-) GitLab und JUnit einzurichten . Fülle diese Lücke!



Einführung


Zunächst werde ich den Kontext skizzieren:

  • Da alle unsere Anwendungen in Kubernetes funktionieren, werden wir in Betracht ziehen, Tests in der entsprechenden Infrastruktur durchzuführen.
  • Für die Montage und Bereitstellung verwenden wir werf (im Sinne von Infrastrukturkomponenten bedeutet dies auch automatisch, dass Helm beteiligt ist).
  • Ich werde nicht auf die Details der direkten Erstellung von Tests eingehen: In unserem Fall schreibt der Client die Tests selbst und wir stellen nur sicher, dass sie ausgeführt werden (und der entsprechende Bericht in der Zusammenführungsanforderung verfügbar ist).

Wie sieht die gesamte Abfolge der Aktionen aus?

  1. Anwendungsassemblierung - Wir werden die Beschreibung dieser Phase weglassen.
  2. Stellen Sie die Anwendung in einem separaten Kubernetes-Cluster-Namespace bereit und starten Sie den Test.
  3. Suchen Sie nach Artefakten und analysieren Sie einen JUnit-Bericht von GitLab.
  4. Löschen Sie den zuvor erstellten Namespace.

Nun zur Implementierung!

Anpassung


Gitlab ci


Beginnen wir mit dem Fragment .gitlab-ci.yaml , das die Bereitstellung der Anwendung beschreibt und die Tests .gitlab-ci.yaml . Die Auflistung erwies sich als ziemlich umfangreich, daher wird sie gründlich durch Kommentare ergänzt:

 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


.helm/templates nun im .helm/templates YAML mit Job - tests-job.yaml -, um die Tests und die benötigten Kubernetes-Ressourcen auszuführen. Erläuterungen siehe nach Auflistung:

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

Welche Ressourcen werden in dieser Konfiguration beschrieben? Erstellen Sie bei der Bereitstellung einen eindeutigen Namespace für das Projekt (dies wird auch in .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG} ) und rollen Sie ihn hinein:

  1. ConfigMap mit einem Testskript ;
  2. Job mit einer Beschreibung des Pods und der angegebenen command , die nur die Tests ausführt;
  3. PV und PVC , mit denen Sie Testdaten speichern können.

Beachten Sie die Einführungsbedingung, if zu Beginn des Manifests - dementsprechend müssen andere YAML-Dateien des Helm-Diagramms mit der Anwendung in die umgekehrte Konstruktion eingeschlossen werden, damit sie während des Tests nicht bereitgestellt werden. Also:

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

Wenn für die Tests jedoch eine Infrastruktur erforderlich ist (z. B. Redis, RabbitMQ, Mongo, PostgreSQL ...), können die YAMLs deaktiviert werden . Stellen Sie sie in einer Testumgebung bereit ... natürlich nach Ihren Wünschen.

Letzte Berührung


Weil Die Montage und Bereitstellung mit werf funktioniert bisher nur auf dem Build-Server (mit gitlab-Runner). Der Pod mit den Tests wird im Assistenten ausgeführt. Sie müssen das Verzeichnis /mnt/tests im Assistenten erstellen und es beispielsweise über NFS an Runner weitergeben . Ein detailliertes Beispiel mit Erläuterungen finden Sie in der K8-Dokumentation .

Das Ergebnis wird sein:

 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 

Niemand verbietet es, einen NFS-Ball direkt auf dem Gitlab-Läufer herzustellen und ihn dann in Pods zu montieren.

Hinweis


Sie fragen sich vielleicht, warum alles mit der Erstellung von Job kompliziert wird, wenn Sie das Testskript einfach direkt auf dem Shell Runner ausführen können. Die Antwort ist ziemlich trivial ...

Einige Tests erfordern Zugriff auf die Infrastruktur (MongoDB, RabbitMQ, PostgreSQL usw.), um die Richtigkeit der Arbeit mit ihnen zu überprüfen. Wir vereinheitlichen das Testen - mit diesem Ansatz wird es einfach, solche zusätzlichen Entitäten einzubeziehen. Darüber hinaus erhalten wir einen Standardansatz für die Bereitstellung (auch bei Verwendung von NFS, zusätzliche Verzeichnisbereitstellung).

Ergebnis


Was werden wir sehen, wenn wir die vorbereitete Konfiguration anwenden?

In der Zusammenführungsanforderung werden zusammenfassende Statistiken zu den in der letzten Pipeline gestarteten Tests angezeigt:



Sie können hier auf jeden Fehler klicken, um Details zu erhalten:



NB : Ein aufmerksamer Leser wird feststellen, dass wir eine NodeJS-Anwendung testen, und in den Screenshots - .NET ... Seien Sie nicht überrascht: Nur im Rahmen der Vorbereitung des Artikels gab es keine Fehler beim Testen der ersten Anwendung, aber sie wurden in einer anderen gefunden.

Fazit


Anscheinend nichts kompliziertes!

Wenn Sie bereits einen Shell-Builder haben und dieser funktioniert und Sie keine Kubernetes benötigen, ist das Schrauben von Tests im Prinzip eine noch einfachere Aufgabe als hier beschrieben. In der GitLab CI-Dokumentation finden Sie Beispiele für Ruby, Go, Gradle, Maven und einige andere.

PS


Lesen Sie auch in unserem Blog:

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


All Articles