JUnit en GitLab CI kun Kubernetes

Malgraŭ tio, ke ĉiuj scias perfekte, ke provi vian programaron estas grava kaj necesa, kaj multaj faras ĝin aŭtomate delonge, en la vasteco de Habr ne ekzistis eĉ unu recepto por starigi kombinaĵon de tiaj popularaj produktoj en ĉi tiu niĉo kiel (nia plej ŝatata) GitLab kaj JUnit . Ni plenigu ĉi tiun mankon!

JUnit en GitLab CI kun Kubernetes

Enkonduka

Unue, lasu min doni iom da kunteksto:

  • Ĉar ĉiuj niaj aplikaĵoj funkcias per Kubernetes, ni konsideros fari provojn sur la taŭga infrastrukturo.
  • Por muntado kaj deplojo ni uzas werf (laŭ infrastrukturaj komponantoj, tio ankaŭ aŭtomate signifas, ke Helm estas implikita).
  • Mi ne eniros la detalojn pri la efektiva kreado de testoj: en nia kazo, la kliento mem skribas la testojn, kaj ni nur certigas ilian lanĉon (kaj la ĉeeston de responda raporto en la kunfanda peto).


Kiel aspektos la ĝenerala sinsekvo de agoj?

  1. Konstruante la aplikaĵon - ni preterlasos la priskribon de ĉi tiu etapo.
  2. Deploji la aplikaĵon al aparta nomspaco de la Kubernetes-areo kaj komencu testi.
  3. Serĉante artefaktojn kaj analizante JUnit-raportojn kun GitLab.
  4. Forigante antaŭe kreitan nomspacon.

Nun - al efektivigo!

alĝustigo

GitLab CI

Ni komencu per fragmento .gitlab-ci.yaml, kiu priskribas deploji la aplikaĵon kaj ruli testojn. La listo montriĝis sufiĉe granda, do ĝi estis plene kompletigita per komentoj:

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

Kubernetoj

Nun en la dosierujo .helm/templates ni kreu YAML kun Job - tests-job.yaml — por ruli testojn kaj la rimedojn de Kubernetes, kiujn ĝi bezonas. Vidu klarigojn post listigo:

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

Kiaj rimedoj priskribita en ĉi tiu agordo? Dum deplojiĝo, ni kreas unikan nomspacon por la projekto (ĉi tio estas indikita en .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) kaj rulu ĝin:

  1. ConfigMap kun prova skripto;
  2. job kun priskribo de la pod kaj la specifita direktivo command, kiu nur kuras la testojn;
  3. PV kaj PVC, kiuj permesas vin stoki testajn datumojn.

Atentu la enkondukan kondiĉon kun if komence de la manifesto - sekve, aliaj YAML-dosieroj de la Helm-diagramo kun la aplikaĵo devas esti envolvitaj en reverso desegni por ke ili ne estu deplojitaj dum testado. Tio estas:

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

Tamen, se la provoj postulas iom da infrastrukturo (ekzemple, Redis, RabbitMQ, Mongo, PostgreSQL...) - iliaj YAMLoj povas esti ne Malŝalti. Deploji ilin ankaŭ en testan medion... ĝustigante ilin laŭplaĉe, kompreneble.

fina tuŝo

Ĉar muntado kaj deplojo uzante werf funkcias nuntempe nur sur la konstruservilo (kun gitlab-runner), kaj la pod kun testoj estas lanĉita sur la majstro, vi devos krei dosierujon /mnt/tests sur la mastro kaj donu ĝin al la kuristo, ekzemple, per NFS. Detala ekzemplo kun klarigoj troviĝas en K8s-dokumentado.

La rezulto estos:

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

Neniu malpermesas fari NFS-dividadon rekte sur gitlab-runner, kaj poste munti ĝin en podoj.

Примечание

Vi eble demandas, kial kompliki ĉion kreante Laborpostenon, se vi povas simple ruli skripton kun testoj rekte sur la ŝelkurilo? La respondo estas sufiĉe bagatela...

Iuj provoj postulas aliron al la infrastrukturo (MongoDB, RabbitMQ, PostgreSQL, ktp.) por kontroli, ke ili funkcias ĝuste. Ni igas testadon unuigita - kun ĉi tiu aliro, fariĝas facile inkluzivi tiajn kromajn entojn. Krom ĉi tio, ni ricevas normo deplojo aliro (eĉ se uzante NFS, kroma muntado de dosierujoj).

rezulto

Kion ni vidos kiam ni aplikas la pretan agordon?

La kunfanda peto montros resumajn statistikojn por testoj rulitaj en sia plej nova dukto:

JUnit en GitLab CI kun Kubernetes

Ĉiu eraro povas esti klakita ĉi tie por detaloj:

JUnit en GitLab CI kun Kubernetes

NB: La atentema leganto rimarkos, ke ni testas aplikaĵon de NodeJS, kaj en la ekrankopioj - .NET... Ne miru: nur, ke dum la preparado de la artikolo, oni ne trovis erarojn en la testado de la unua aplikaĵo, sed ili estis trovitaj en alia.

konkludo

Kiel vi povas vidi, nenio komplika!

Principe, se vi jam havas ŝelkolektilon kaj ĝi funkcias, sed vi ne bezonas Kubernetes, alligi testadon al ĝi estos eĉ pli simpla tasko ol priskribita ĉi tie. Kaj en GitLab CI-dokumentado vi trovos ekzemplojn por Ruby, Go, Gradle, Maven kaj iuj aliaj.

PS

Legu ankaŭ en nia blogo:

fonto: www.habr.com

Aldoni komenton