JUnit trong GitLab CI với Kubernetes

Mặc dù thực tế là mọi người đều biết rõ rằng việc kiểm tra phần mềm của bạn là quan trọng và cần thiết, và nhiều người đã thực hiện việc đó một cách tự động trong một thời gian dài, nhưng trong sự rộng lớn của Habr, không có một công thức duy nhất nào để thiết lập sự kết hợp của các sản phẩm phổ biến như vậy trong vị trí thích hợp này là GitLab và JUnit (yêu thích của chúng tôi). Hãy lấp đầy khoảng trống này!

JUnit trong GitLab CI với Kubernetes

giới thiệu

Đầu tiên, hãy để tôi đưa ra một số bối cảnh:

  • Vì tất cả các ứng dụng của chúng tôi đều chạy trên Kubernetes nên chúng tôi sẽ xem xét việc chạy thử nghiệm trên cơ sở hạ tầng phù hợp.
  • Để lắp ráp và triển khai, chúng tôi sử dụng người sói (về các thành phần cơ sở hạ tầng, điều này đương nhiên cũng có nghĩa là Helm có liên quan).
  • Tôi sẽ không đi sâu vào chi tiết về việc tạo các thử nghiệm thực tế: trong trường hợp của chúng tôi, khách hàng tự viết các thử nghiệm và chúng tôi chỉ đảm bảo việc khởi chạy chúng (và sự hiện diện của báo cáo tương ứng trong yêu cầu hợp nhất).


Trình tự hành động chung sẽ như thế nào?

  1. Xây dựng ứng dụng - chúng tôi sẽ bỏ qua phần mô tả về giai đoạn này.
  2. Triển khai ứng dụng vào một không gian tên riêng của cụm Kubernetes và bắt đầu thử nghiệm.
  3. Tìm kiếm các thành phần lạ và phân tích cú pháp các báo cáo JUnit bằng GitLab.
  4. Xóa không gian tên đã tạo trước đó.

Bây giờ - để thực hiện!

điều chỉnh

CI GitLab

Hãy bắt đầu với một đoạn .gitlab-ci.yaml, mô tả việc triển khai ứng dụng và chạy thử nghiệm. Danh sách hóa ra khá đồ sộ nên đã được bổ sung kỹ lưỡng các nhận xét:

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

Bây giờ trong thư mục .helm/templates hãy tạo YAML với Job - tests-job.yaml — để chạy thử nghiệm và tài nguyên Kubernetes mà nó cần. Xem giải thích sau khi liệt kê:

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

Những loại tài nguyên được mô tả trong cấu hình này? Khi triển khai, chúng tôi tạo một namespace duy nhất cho dự án (điều này được chỉ ra trong .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) và triển khai nó:

  1. Bản đồ cấu hình với kịch bản thử nghiệm;
  2. Việc làm với mô tả về nhóm và chỉ thị được chỉ định command, chỉ chạy thử nghiệm;
  3. PV và PVC, cho phép bạn lưu trữ dữ liệu thử nghiệm.

Hãy chú ý đến điều kiện mở đầu với if ở đầu tệp kê khai - theo đó, các tệp YAML khác của biểu đồ Helm có ứng dụng phải được gói trong đảo ngược thiết kế để chúng không bị triển khai trong quá trình thử nghiệm. Đó là:

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

Tuy nhiên, nếu các thử nghiệm yêu cầu một số cơ sở hạ tầng (ví dụ: Redis, RabbitMQ, Mongo, PostgreSQL...) - YAML của họ có thể không tắt. Đồng thời triển khai chúng vào môi trường thử nghiệm... tất nhiên là điều chỉnh chúng khi bạn thấy phù hợp.

Liên lạc cuối cùng

Bởi vì hiện tại đã lắp ráp và triển khai bằng cách sử dụng werf chỉ trên máy chủ xây dựng (với gitlab-runner) và nhóm có các bài kiểm tra được khởi chạy trên máy chủ chính, bạn sẽ cần tạo một thư mục /mnt/tests trên người chủ và đưa nó cho người chạy, ví dụ: thông qua NFS. Một ví dụ chi tiết với lời giải thích có thể được tìm thấy trong Tài liệu K8s.

Kết quả sẽ là:

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

Không ai cấm việc chia sẻ NFS trực tiếp trên gitlab-runner, sau đó gắn nó vào nhóm.

Ghi

Bạn có thể hỏi tại sao lại phức tạp hóa mọi thứ bằng cách tạo một Công việc nếu bạn có thể chỉ cần chạy một tập lệnh với các thử nghiệm trực tiếp trên trình chạy shell? Câu trả lời khá tầm thường...

Một số thử nghiệm yêu cầu quyền truy cập vào cơ sở hạ tầng (MongoDB, RabbitMQ, PostgreSQL, v.v.) để xác minh rằng chúng hoạt động chính xác. Chúng tôi làm cho việc thử nghiệm trở nên thống nhất - với phương pháp này, việc bao gồm các thực thể bổ sung như vậy trở nên dễ dàng. Ngoài ra, chúng tôi nhận được tiêu chuẩn phương pháp triển khai (ngay cả khi sử dụng NFS, việc gắn thêm các thư mục).

Kết quả

Chúng ta sẽ thấy gì khi áp dụng cấu hình đã chuẩn bị?

Yêu cầu hợp nhất sẽ hiển thị số liệu thống kê tóm tắt cho các thử nghiệm chạy trong quy trình mới nhất của nó:

JUnit trong GitLab CI với Kubernetes

Mỗi lỗi có thể bấm vào đây để biết chi tiết:

JUnit trong GitLab CI với Kubernetes

NB: Người đọc chú ý sẽ nhận thấy rằng chúng tôi đang thử nghiệm một ứng dụng NodeJS và trong ảnh chụp màn hình - .NET... Đừng ngạc nhiên: chỉ là trong khi chuẩn bị bài viết, không tìm thấy lỗi nào khi thử nghiệm ứng dụng đầu tiên, nhưng chúng được tìm thấy ở nơi khác.

Kết luận

Như bạn có thể thấy, không có gì phức tạp!

Về nguyên tắc, nếu bạn đã có trình thu thập shell và nó hoạt động, nhưng bạn không cần Kubernetes, thì việc gắn thử nghiệm vào nó sẽ là một nhiệm vụ thậm chí còn đơn giản hơn mô tả ở đây. Và trong Tài liệu CI của GitLab bạn sẽ tìm thấy các ví dụ về Ruby, Go, Gradle, Maven và một số ví dụ khác.

PS

Đọc thêm trên blog của chúng tôi:

Nguồn: www.habr.com

Thêm một lời nhận xét