GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

Тази статия ще бъде от интерес както за тестери, така и за разработчици, но е предназначена повече за автоматизатори, които са изправени пред проблема с настройването на GitLab CI / CD за интеграционно тестване в лицето на недостатъчни инфраструктурни ресурси и / или липсата на оркестрация на контейнери платформа. Ще ви кажа как да конфигурирате разгръщането на тестови среди с помощта на docker compose на един шел пълнеж на GitLab и така, че когато разгръщате няколко среди, стартираните услуги да не си пречат.


Съдържание

Предпосылки

  1. В моята практика често се случваше да „третирам“ интеграционното тестване на проекти. И често първият и най-важен проблем е CI тръбопроводът, в който интеграционното тестване развити услугата(ите) се хоства в среда за разработка/етап. Това доведе до доста проблеми:

    • Поради дефекти в една или друга услуга по време на интеграционното тестване, тестовата верига може да бъде повредена от повредени данни. Имаше случаи, когато изпращането на заявка със счупен JSON формат затвори услугата, което направи щанда напълно неработещ.
    • Забавянето на тестовата верига с нарастването на тестовите данни. Мисля, че няма смисъл да описвам пример с почистване / връщане на базата данни. В моята практика не съм срещал проект, при който тази процедура да върви гладко.
    • Рискът от нарушаване на работата на тестовата верига при тестване на общи системни настройки. Например политика за потребител/група/парола/приложение.
    • Тестовите данни от автоматичните тестове правят живота труден за ръчните тестери.

    Някой ще каже, че добрите автотестове трябва да изчистят данните след себе си. Имам аргументи против:

    • Динамичните стойки са много удобни за използване.
    • Не всеки обект може да бъде премахнат от системата чрез API. Например, извикване за изтриване на обект не е реализирано, тъй като противоречи на бизнес логиката.
    • Когато създавате обект чрез API, може да се създаде огромно количество метаданни, което е проблематично за премахване.
    • Ако тестовете са зависими един от друг, тогава процесът на почистване на данните след изпълнението на тестовете се превръща в главоболие.
    • Допълнителни (и според мен неоправдани) извиквания към API.
    • И основният аргумент: когато тестовите данни започнат да се почистват директно от базата данни. Това се превръща в истински PK/FK цирк! Чуваме от разработчиците: „Току-що добавих/изтрих/преименувах знака, защо 100500 теста за интеграция се провалиха?“

    Според мен най-оптималното решение е динамична среда.

  2. Много хора използват docker-compose, за да стартират тестова среда, но малко хора използват docker-compose, когато правят интеграционно тестване в CI/CD. И тук не вземам предвид kubernetes, swarm и други платформи за оркестриране на контейнери. Не всяка фирма ги има. Би било хубаво, ако docker-compose.yml беше универсален.
  3. Дори ако имаме собствен QA runner, как можем да сме сигурни, че услугите, стартирани чрез docker-compose, не си пречат?
  4. Как да събирам регистрационни файлове на тествани услуги?
  5. Как да почистите бегача?

Имам собствен GitLab runner за моите проекти и се натъкнах на тези проблеми, докато разработвах Java клиент за тестова релса. По-конкретно, когато изпълнявате интеграционни тестове. Тук ще продължим да решаваме тези проблеми с примери от този проект.

Към съдържанието

GitLab Shell Runner

За бегач препоръчвам Linux виртуална машина с 4 vCPU, 4 GB RAM, 50 GB HDD.
Има много информация за настройка на gitlab-runner в интернет, така че накратко:

  • Отиваме до машината чрез SSH
  • Ако имате по-малко от 8 GB RAM, препоръчвам направи суап 10 GBза да не дойде убиецът на OOM и да ни убие задачите поради липса на RAM. Това може да се случи, когато повече от 5 задачи се изпълняват едновременно. Задачите ще бъдат по-бавни, но стабилни.

    Пример с OOM killer

    Ако в регистрационните файлове на задачите виждате bash: line 82: 26474 Killed, след това просто изпълнете на бегача sudo dmesg | grep 26474

    [26474]  1002 26474  1061935   123806     339        0             0 java
    Out of memory: Kill process 26474 (java) score 127 or sacrifice child
    Killed process 26474 (java) total-vm:4247740kB, anon-rss:495224kB, file-rss:0kB, shmem-rss:0kB

    И ако снимката изглежда нещо подобно, тогава или добавете суап, или добавете RAM.

  • инсталирам gitlab-бегач, докер, докер-ново съобщение, направи.
  • Добавяне на потребител gitlab-runner към групата docker
    sudo groupadd docker
    sudo usermod -aG docker gitlab-runner
  • Регистрация gitlab-бегач.
  • Отворено за редактиране /etc/gitlab-runner/config.toml и добавете

    concurrent=20
    [[runners]]
      request_concurrency = 10

    Това ще ви позволи да изпълнявате паралелни задачи на един и същи бегач. Прочетете още тук.
    Ако имате по-мощна машина, например 8 vCPU, 16 GB RAM, тогава тези числа могат да бъдат направени поне 2 пъти по-големи. Но всичко зависи от това какво точно ще бъде пуснато на този бегач и в какво количество.

Това е достатъчно.

Към съдържанието

Подготовка на docker-compose.yml

Основната задача е универсален docker-compose.yml, който разработчиците/тестерите могат да използват както локално, така и в CI конвейера.

На първо място, ние създаваме уникални имена на услуги за CI. Една от уникалните променливи в GitLab CI е променливата CI_JOB_ID. Ако посочите container_name със стойност "service-${CI_JOB_ID:-local}", тогава в случая:

  • ако CI_JOB_ID не е дефиниран в променливите на средата,
    тогава името на услугата ще бъде service-local
  • ако CI_JOB_ID дефинирани в променливи на средата (напр. 123),
    тогава името на услугата ще бъде service-123

Второ, правим обща мрежа за изпълняване на услуги. Това ни дава изолация на ниво мрежа, когато изпълняваме множество тестови среди.

networks:
  default:
    external:
      name: service-network-${CI_JOB_ID:-local}

Всъщност това е първата стъпка към успеха =)

Пример за моя docker-compose.yml с коментари

version: "3"

# Для корректной работы web (php) и fmt нужно, 
# чтобы контейнеры имели общий исполняемый контент.
# В нашем случае, это директория /var/www/testrail
volumes:
  static-content:

# Изолируем окружение на сетевом уровне
networks:
  default:
    external:
      name: testrail-network-${CI_JOB_ID:-local}

services:
  db:
    image: mysql:5.7.22
    # Каждый container_name содержит ${CI_JOB_ID:-local}
    container_name: "testrail-mysql-${CI_JOB_ID:-local}"
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: mydb
      MYSQL_ROOT_PASSWORD: 1234
      SKIP_GRANT_TABLES: 1
      SKIP_NETWORKING: 1
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    networks:
    - default

  migration:
    image: registry.gitlab.com/touchbit/image/testrail/migration:latest
    container_name: "testrail-migration-${CI_JOB_ID:-local}"
    links:
    - db
    depends_on:
    - db
    networks:
    - default

  fpm:
    image: registry.gitlab.com/touchbit/image/testrail/fpm:latest
    container_name: "testrail-fpm-${CI_JOB_ID:-local}"
    volumes:
    - static-content:/var/www/testrail
    links:
    - db
    networks:
    - default

  web:
    image: registry.gitlab.com/touchbit/image/testrail/web:latest
    container_name: "testrail-web-${CI_JOB_ID:-local}"
    # Если переменные TR_HTTP_PORT или TR_HTTPS_PORTS не определены,
    # то сервис поднимается на 80 и 443 порту соответственно.
    ports:
      - ${TR_HTTP_PORT:-80}:80
      - ${TR_HTTPS_PORT:-443}:443
    volumes:
      - static-content:/var/www/testrail
    links:
      - db
      - fpm
    networks:
      - default

Пример за локално изпълнение

docker-compose -f docker-compose.yml up -d
Starting   testrail-mysql-local     ... done
Starting   testrail-migration-local ... done
Starting   testrail-fpm-local       ... done
Recreating testrail-web-local       ... done

Но не всичко е толкова просто със стартирането в CI.

Към съдържанието

Подготовка на Makefile

Използвам Makefile, тъй като е доста удобен както за управление на локална среда, така и за CI. Още онлайн коментари

# У меня в проектах все вспомогательные вещи лежат в директории `.indirect`,
# в том числе и `docker-compose.yml`

# Использовать bash с опцией pipefail 
# pipefail - фейлит выполнение пайпа, если команда выполнилась с ошибкой
SHELL=/bin/bash -o pipefail

# Останавливаем контейнеры и удаляем сеть
docker-kill:
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml kill
    docker network rm network-$${CI_JOB_ID:-testrail} || true

# Предварительно выполняем docker-kill 
docker-up: docker-kill
    # Создаем сеть для окружения 
    docker network create network-$${CI_JOB_ID:-testrail}
    # Забираем последние образы из docker-registry
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml pull
    # Запускаем окружение
    # force-recreate - принудительное пересоздание контейнеров
    # renew-anon-volumes - не использовать volumes предыдущих контейнеров
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
    # Ну и, на всякий случай, вывести что там у нас в принципе запущено на машинке
    docker ps

# Коллектим логи сервисов
docker-logs:
    mkdir ./logs || true
    docker logs testrail-web-$${CI_JOB_ID:-local}       >& logs/testrail-web.log
    docker logs testrail-fpm-$${CI_JOB_ID:-local}       >& logs/testrail-fpm.log
    docker logs testrail-migration-$${CI_JOB_ID:-local} >& logs/testrail-migration.log
    docker logs testrail-mysql-$${CI_JOB_ID:-local}     >& logs/testrail-mysql.log

# Очистка раннера
docker-clean:
    @echo Останавливаем все testrail-контейнеры
    docker kill $$(docker ps --filter=name=testrail -q) || true
    @echo Очистка докер контейнеров
    docker rm -f $$(docker ps -a -f --filter=name=testrail status=exited -q) || true
    @echo Очистка dangling образов
    docker rmi -f $$(docker images -f "dangling=true" -q) || true
    @echo Очистка testrail образов
    docker rmi -f $$(docker images --filter=reference='registry.gitlab.com/touchbit/image/testrail/*' -q) || true
    @echo Очистка всех неиспользуемых volume
    docker volume rm -f $$(docker volume ls -q) || true
    @echo Очистка всех testrail сетей
    docker network rm $(docker network ls --filter=name=testrail -q) || true
    docker ps

проверите

направи докер-ъп

$ make docker-up 
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Killing testrail-web-local   ... done
Killing testrail-fpm-local   ... done
Killing testrail-mysql-local ... done
docker network rm network-${CI_JOB_ID:-testrail} || true
network-testrail
docker network create network-${CI_JOB_ID:-testrail}
d2ec063324081c8bbc1b08fd92242c2ea59d70cf4025fab8efcbc5c6360f083f
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Pulling db        ... done
Pulling migration ... done
Pulling fpm       ... done
Pulling web       ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Recreating testrail-mysql-local ... done
Recreating testrail-fpm-local       ... done
Recreating testrail-migration-local ... done
Recreating testrail-web-local       ... done
docker ps
CONTAINER ID  PORTS                                     NAMES
a845d3cb0e5a  0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp  testrail-web-local
19d8ef001398  9000/tcp                                  testrail-fpm-local
e28840a2369c  3306/tcp, 33060/tcp                       testrail-migration-local
0e7900c23f37  3306/tcp                                  testrail-mysql-local

направете докер-дневници

$ make docker-logs
mkdir ./logs || true
mkdir: cannot create directory ‘./logs’: File exists
docker logs testrail-web-${CI_JOB_ID:-local}       >& logs/testrail-web.log
docker logs testrail-fpm-${CI_JOB_ID:-local}       >& logs/testrail-fpm.log
docker logs testrail-migration-${CI_JOB_ID:-local} >& logs/testrail-migration.log
docker logs testrail-mysql-${CI_JOB_ID:-local}     >& logs/testrail-mysql.log

GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

Към съдържанието

Подготвя се .gitlab-ci.yml

Изпълнение на интеграционни тестове

Integration:
  stage: test
  tags:
    - my-shell-runner
  before_script:
    # Аутентифицируемся в registry
    - docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
    # Генерируем псевдоуникальные TR_HTTP_PORT и TR_HTTPS_PORT
    - export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
    - export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
    # создаем директорию с идентификатором задачи
    - mkdir ${CI_JOB_ID}
    # копируем в созданную директорию наш docker-compose.yml
    # чтобы контекст был разный для каждой задачи
    - cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
  script:
    # поднимаем наше окружение
    - make docker-up
    # запускаем тесты исполняемым jar (у меня так)
    - java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
    # или в контейнере
    - docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
  after_script:
    # собираем логи
    - make docker-logs
    # останавливаем окружение
    - make docker-kill
  artifacts:
    # сохраняем логи
    when: always
    paths:
      - logs
    expire_in: 30 days

В резултат на изпълнението на такава задача, директорията с регистрационни файлове в артефакти ще съдържа регистрационни файлове на услуги и тестове. Което е много удобно в случай на грешки. Всеки тест паралелно пише свой собствен журнал, но ще говоря за това отделно.

GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

Към съдържанието

Почистване на бегача

Задачата ще се изпълнява само по график.

stages:
- clean
- build
- test

Clean runner:
  stage: clean
  only:
    - schedules
  tags:
    - my-shell-runner
  script:
    - make docker-clean

След това отидете на нашия проект GitLab -> CI/CD -> Графици -> Нов график и добавете нов график

GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

Към съдържанието

Резултат

Изпълнете 4 задачи в GitLab CI
GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

В регистрационните файлове на последната задача с интеграционни тестове виждаме контейнери от различни задачи

CONTAINER ID  NAMES
c6b76f9135ed  testrail-web-204645172
01d303262d8e  testrail-fpm-204645172
2cdab1edbf6a  testrail-migration-204645172
826aaf7c0a29  testrail-mysql-204645172
6dbb3fae0322  testrail-web-204645084
3540f8d448ce  testrail-fpm-204645084
70fea72aa10d  testrail-mysql-204645084
d8aa24b2892d  testrail-web-204644881
6d4ccd910fad  testrail-fpm-204644881
685d8023a3ec  testrail-mysql-204644881
1cdfc692003a  testrail-web-204644793
6f26dfb2683e  testrail-fpm-204644793
029e16b26201  testrail-mysql-204644793
c10443222ac6  testrail-web-204567103
04339229397e  testrail-fpm-204567103
6ae0accab28d  testrail-mysql-204567103
b66b60d79e43  testrail-web-204553690
033b1f46afa9  testrail-fpm-204553690
a8879c5ef941  testrail-mysql-204553690
069954ba6010  testrail-web-204553539
ed6b17d911a5  testrail-fpm-204553539
1a1eed057ea0  testrail-mysql-204553539

По-подробен дневник

$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /home/gitlab-runner/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
$ export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
$ export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
$ mkdir ${CI_JOB_ID}
$ cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
docker network rm testrail-network-${CI_JOB_ID:-local} || true
Error: No such network: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Pulling web       ... done
Pulling fpm       ... done
Pulling migration ... done
Pulling db        ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Creating volume "204645172_static-content" with default driver
Creating testrail-mysql-204645172 ... 
Creating testrail-mysql-204645172 ... done
Creating testrail-migration-204645172 ... done
Creating testrail-fpm-204645172       ... done
Creating testrail-web-204645172       ... done
docker ps
CONTAINER ID        IMAGE                                                          COMMAND                  CREATED              STATUS              PORTS                                           NAMES
c6b76f9135ed        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   13 seconds ago       Up 1 second         0.0.0.0:51148->80/tcp, 0.0.0.0:25426->443/tcp   testrail-web-204645172
01d303262d8e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   16 seconds ago       Up 13 seconds       9000/tcp                                        testrail-fpm-204645172
2cdab1edbf6a        registry.gitlab.com/touchbit/image/testrail/migration:latest   "docker-entrypoint.s…"   16 seconds ago       Up 13 seconds       3306/tcp, 33060/tcp                             testrail-migration-204645172
826aaf7c0a29        mysql:5.7.22                                                   "docker-entrypoint.s…"   18 seconds ago       Up 16 seconds       3306/tcp                                        testrail-mysql-204645172
6dbb3fae0322        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   36 seconds ago       Up 22 seconds       0.0.0.0:44202->80/tcp, 0.0.0.0:20151->443/tcp   testrail-web-204645084
3540f8d448ce        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   38 seconds ago       Up 35 seconds       9000/tcp                                        testrail-fpm-204645084
70fea72aa10d        mysql:5.7.22                                                   "docker-entrypoint.s…"   40 seconds ago       Up 37 seconds       3306/tcp                                        testrail-mysql-204645084
d8aa24b2892d        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   About a minute ago   Up 53 seconds       0.0.0.0:31103->80/tcp, 0.0.0.0:43872->443/tcp   testrail-web-204644881
6d4ccd910fad        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   About a minute ago   Up About a minute   9000/tcp                                        testrail-fpm-204644881
685d8023a3ec        mysql:5.7.22                                                   "docker-entrypoint.s…"   About a minute ago   Up About a minute   3306/tcp                                        testrail-mysql-204644881
1cdfc692003a        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   About a minute ago   Up About a minute   0.0.0.0:44752->80/tcp, 0.0.0.0:23540->443/tcp   testrail-web-204644793
6f26dfb2683e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   About a minute ago   Up About a minute   9000/tcp                                        testrail-fpm-204644793
029e16b26201        mysql:5.7.22                                                   "docker-entrypoint.s…"   About a minute ago   Up About a minute   3306/tcp                                        testrail-mysql-204644793
c10443222ac6        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   5 hours ago          Up 5 hours          0.0.0.0:57123->80/tcp, 0.0.0.0:31657->443/tcp   testrail-web-204567103
04339229397e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204567103
6ae0accab28d        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204567103
b66b60d79e43        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   5 hours ago          Up 5 hours          0.0.0.0:56321->80/tcp, 0.0.0.0:58749->443/tcp   testrail-web-204553690
033b1f46afa9        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204553690
a8879c5ef941        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204553690
069954ba6010        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   5 hours ago          Up 5 hours          0.0.0.0:32869->80/tcp, 0.0.0.0:16066->443/tcp   testrail-web-204553539
ed6b17d911a5        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204553539
1a1eed057ea0        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204553539

Всички задачи изпълнени успешно

Артефактите на задачите съдържат регистрационни файлове на услуги и тестове
GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

Всичко изглежда красиво, но има нюанс. Конвейер може да бъде принудително анулиран, докато се изпълняват тестове за интеграция, в който случай работещите контейнери няма да бъдат спрени. От време на време трябва да почиствате бегача. За съжаление задачата за ревизия в GitLab CE все още е в статус отворено

Но ние добавихме планирано стартиране на задача и никой не ни забранява да го стартираме ръчно.
Отидете на нашия проект -> CI/CD -> Графици и стартирайте задачата Clean runner

GitLab Shell Runner. Конкурентно стартирайте тествани услуги с Docker Compose

Общо:

  • Имаме един бегач на черупки.
  • Няма конфликти между задачите и средата.
  • Имаме паралелно стартиране на задачи с интеграционни тестове.
  • Можете да изпълнявате интеграционни тестове както локално, така и в контейнер.
  • Сервизните и тестовите регистрационни файлове се събират и прикачват към задачата за конвейер.
  • Възможно е да почистите бегача от стари докер изображения.

Времето за настройка е ~2 часа.
Това всъщност е всичко. Ще се радвам на обратна връзка.

Към съдържанието

Източник: www.habr.com

Добавяне на нов коментар