GitLab Shell Runner. Конкурентный запуск тестируемых сервисов при помощи Docker Compose


    Данная статья будет интересна как тестировщикам, так и разработчикам, но рассчитана в большей степени на автоматизаторов, которые столкнулись с проблемой настройки GitLab CI/CD для проведения интеграционного тестирования в условиях недостаточности инфраструктурных ресурсов и/или отсутствия платформы оркестрации контейнеров. Я расскажу, как настроить развертывание тестируемых окружений при помощи docker compose на одном единственном GitLab shell раннере и так, чтобы при развертывании нескольких окружений запускаемые сервисы друг другу не мешали.



    Содержание




    Предпосылки


    1. В моей практике частенько случалось "лечить" интеграционное тестирование на проектах. И зачастую первой и самой значительной проблемой является CI pipeline, в котором интеграционное тестирование разрабатываемого сервиса(ов) проводится в dev/stage окружении. Это вызывало не мало проблем:


      • Из-за дефектов в том или ином сервисе в процессе интеграционного тестирования тестовый контур может быть испорчен битыми данными. Бывали случаи, когда отправка запроса с битым JSON-форматом вешал сервис, что приводило стенд полностью в нерабочее состояние.
      • Замедлением работы тестового контура с ростом тестовых данных. Думаю, описывать пример с очисткой/откатом БД не имеет смысла. В своей практике я не встречал проекта, где эта процедура проходила бы гладко.
      • Риск нарушить работоспособность тестового контура при тестировании общих настроек системы. Например, user/group/password/application policy.
      • Тестовые данные от автотестов мешают жить ручным тестировщикам.

      Кто-то скажет, что хорошие автотесты должны чистить данные после себя. У меня есть аргументы против:


      • Динамические стенды весьма удобны в использовании.
      • Не каждый объект можно удалить из системы через API. Например вызов на удаление объекта не реализован, так как противоречит бизнес логике.
      • При создании объекта через API может создаваться огромное количество метаданных, которые удалить проблематично.
      • Если тесты имеют зависимость между собой, то процесс очистки данных после выполнения тестов превращается в головную боль.
      • Дополнительные (и, на мой взгляд, не оправданные) вызовы к API.
      • И главный аргумент: когда тестовые данные начинают чистить прямо из БД. Это превращается в настоящий PK/FK цирк! От разработчиков слышно: «Я только табличку добавил/удалил/переименовал, почему 100500 интеграционных тестов попадало?»

      По моему мнению, самое оптимальное решение — это динамическое окружение.


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

    У меня есть собственный GitLab раннер для своих проектов и с этими вопросами я столкнулся при разработке Java клиента для TestRail. А точнее при запуске интеграционных тестов. Вот далее и будем решать эти вопросы с примерами из данного проекта.


    К содержанию



    GitLab Shell Runner


    Для раннера рекомендую линуксовую виртуалку с 4 vCPU, 4 GB RAM, 50 GB HDD.
    На просторах интернета очень много информации по настройке gitlab-runner, поэтому коротко:


    • Заходим на машинку по SSH
    • Если у вас менее 8 GB RAM, то рекомендую сделать swap 10 GB, чтобы не приходил OOM killer и не убивал нам задачи из-за нехватки 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

      И если картина выглядит примерно так, то или добавляйте swap, или докидывайте RAM.




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


      concurrent=20
      [[runners]]
        request_concurrency = 10

      Это позволит запускать параллельные задачи на одном раннере. Более подробно читать тут.
      Если у вас машинка помощнее, например 8 vCPU, 16 GB RAM, то эти цифры можно сделать как минимум в 2 раза больше. Но все зависит от того, что конкретно будет запускаться на данном раннере и в каком количестве.



    Этого достаточно.


    К содержанию



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


    Основная задача — это docker-compose.yml, который будет использован как локально, так и в CI pipeline.


    Для запуска нескольких экземпляров окружения будет использоваться переменная COMPOSE_PROJECT_NAME (см. makefile).


    Пример моего docker-compose.yml


    version: "3"
    
    # Для корректной работы web (php) и fmt нужно, 
    # чтобы контейнеры имели общий исполняемый контент.
    # В нашем случае, это директория /var/www/testrail
    volumes:
      static-content:
    
    services:
      db:
        image: mysql:5.7.22
        environment:
          MYSQL_HOST: db
          MYSQL_DATABASE: mydb
          MYSQL_ROOT_PASSWORD: 1234
          SKIP_GRANT_TABLES: 1
          SKIP_NETWORKING: 1
          SERVICE_TAGS: dev
          SERVICE_NAME: mysql
    
      migration:
        image: registry.gitlab.com/touchbit/image/testrail/migration:latest
        links:
        - db
        depends_on:
        - db
    
      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
    
      web:
        image: registry.gitlab.com/touchbit/image/testrail/web:latest
        # Если переменные 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

    К содержанию



    Подготовка Makefile


    Я использую Makefile, так как это весьма удобно как для локального управления окружением, так и в CI.


    Далее комментарии инлайн


    # У меня в проектах все вспомогательные вещи лежат в директории `.indirect`,
    # в том числе и `docker-compose.yml`
    
    # Использовать bash с опцией pipefail 
    # pipefail - фейлит выполнение пайпа, если команда выполнилась с ошибкой
    SHELL=/bin/bash -o pipefail
    
    # Если переменная CI_JOB_ID не определена 
    ifeq ($(CI_JOB_ID),)
        # присваиваем значение local
        CI_JOB_ID := local
    endif
    
    # Экспортируем переменную окружения
    export COMPOSE_PROJECT_NAME = $(CI_JOB_ID)-testrail
    
    # Останавливаем и удаляем контейнеры, сеть, volumes
    docker-down:
        docker-compose -f .indirect/docker-compose.yml down
    
    # Предварительно выполняем docker-down (опционально)
    docker-up: docker-down
        # Забираем последние образы из docker-registry
        docker-compose -f .indirect/docker-compose.yml pull
        # Запускаем окружение
        # force-recreate - принудительное пересоздание контейнеров
        # renew-anon-volumes - не использовать volumes предыдущих контейнеров
        docker-compose -f .indirect/docker-compose.yml up --force-recreate --renew-anon-volumes -d
        # Ну и, на всякий случай, вывести что там у нас в принципе запущено на машинке
        docker ps
    
    # Коллектим логи сервисов
    docker-logs:
        mkdir -p ./logs
        docker logs $${COMPOSE_PROJECT_NAME}_web_1       >& logs/testrail-web.log       || true
        docker logs $${COMPOSE_PROJECT_NAME}_fpm_1       >& logs/testrail-fpm.log       || true
        docker logs $${COMPOSE_PROJECT_NAME}_migration_1 >& logs/testrail-migration.log || true
        docker logs $${COMPOSE_PROJECT_NAME}_db_1        >& logs/testrail-mysql.log     || true
    
    # Очистка раннера
    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 .indirect/docker-compose.yml pull
    Pulling db        ... done
    Pulling migration ... done
    Pulling fpm       ... done
    Pulling web       ... done
    docker-compose -f .indirect/docker-compose.yml up --force-recreate --renew-anon-volumes -d
    Creating network "local-testrail_default" with the default driver
    Recreating local-testrail_db_1 ... done
    Recreating local-testrail_migration_1 ... done
    Recreating local-testrail_fpm_1       ... done
    Recreating local-testrail_web_1       ... done
    docker ps
    CONTAINER ID        NAMES
    3b8f9d4af29c        local-testrail_web_1
    5622c7d742d5        local-testrail_fpm_1
    b580e3392038        local-testrail_migration_1
    e467630bd3a5        local-testrail_db_1
    

    Проверяем CI запуск
    $ export CI_JOB_ID=123456789
    $ make docker-up 
    docker-compose -f .indirect/docker-compose.yml pull
    Pulling db        ... done
    Pulling migration ... done
    Pulling fpm       ... done
    Pulling web       ... done
    docker-compose -f .indirect/docker-compose.yml up --force-recreate --renew-anon-volumes -d
    Creating network "123456789-testrail_default" with the default driver
    Creating volume "123456789-testrail_static-content" with default driver
    Creating 123456789-testrail_db_1 ... done
    Creating 123456789-testrail_fpm_1       ... done
    Creating 123456789-testrail_migration_1 ... done
    Creating 123456789-testrail_web_1       ... done
    docker ps
    CONTAINER ID        NAMES
    ccf1ad33d0e8        123456789-testrail_web_1
    bc079964f681        123456789-testrail_fpm_1
    10dc9d4d8f2a        123456789-testrail_migration_1
    fe98d43c380e        123456789-testrail_db_1
    

    Проверяем сбор логов
    $ make docker-logs
    mkdir -p ./logs
    docker logs ${COMPOSE_PROJECT_NAME}_web_1       >& logs/testrail-web.log       || true
    docker logs ${COMPOSE_PROJECT_NAME}_fpm_1       >& logs/testrail-fpm.log       || true
    docker logs ${COMPOSE_PROJECT_NAME}_migration_1 >& logs/testrail-migration.log || true
    docker logs ${COMPOSE_PROJECT_NAME}_db_1        >& logs/testrail-mysql.log     || true


    К содержанию



    Подготовка .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)
      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-down
      artifacts:
        # сохраняем логи
        when: always
        paths:
          - logs
        expire_in: 30 days

    В результате запуска такой задачи в артефактах директория logs будет содержать логи сервисов и тестов. Что очень удобно в случае возникновения ошибок. У меня каждый тест в параллели пишет свой лог, но об этом я расскажу отдельно.



    К содержанию



    Очистка раннера


    Задача будет запускаться только по расписанию.


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

    Далее идем в наш GitLab проект -> CI/CD -> Schedules -> New Schedule и добавляем новое расписание



    К содержанию



    Результат


    Запускаем 4 задачи в GitLab CI


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


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

    Более детальный лог
    $ 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-testrail_static-content" with default driver
    Creating 204645172-testrail-mysql_1 ... 
    Creating 204645172-testrail-mysql_1 ... done
    Creating 204645172-testrail-migration_1 ... done
    Creating 204645172-testrail-fpm_1       ... done
    Creating 204645172-testrail-web_1       ... 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   204645172-testrail-web_1
    01d303262d8e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   16 seconds ago       Up 13 seconds       9000/tcp                                        204645172-testrail-fpm_1
    2cdab1edbf6a        registry.gitlab.com/touchbit/image/testrail/migration:latest   "docker-entrypoint.s…"   16 seconds ago       Up 13 seconds       3306/tcp, 33060/tcp                             204645172-testrail-migration_1
    826aaf7c0a29        mysql:5.7.22                                                   "docker-entrypoint.s…"   18 seconds ago       Up 16 seconds       3306/tcp                                        204645172-testrail-mysql_1
    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   204645084-testrail-web_1
    3540f8d448ce        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   38 seconds ago       Up 35 seconds       9000/tcp                                        204645084-testrail-fpm_1
    70fea72aa10d        mysql:5.7.22                                                   "docker-entrypoint.s…"   40 seconds ago       Up 37 seconds       3306/tcp                                        204645084-testrail-mysql_1
    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   204644881-testrail-web_1
    6d4ccd910fad        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   About a minute ago   Up About a minute   9000/tcp                                        204644881-testrail-fpm_1
    685d8023a3ec        mysql:5.7.22                                                   "docker-entrypoint.s…"   About a minute ago   Up About a minute   3306/tcp                                        204644881-testrail-mysql_1
    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   204644793-testrail-web_1
    6f26dfb2683e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   About a minute ago   Up About a minute   9000/tcp                                        204644793-testrail-fpm_1
    029e16b26201        mysql:5.7.22                                                   "docker-entrypoint.s…"   About a minute ago   Up About a minute   3306/tcp                                        204644793-testrail-mysql_1
    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   204567103-testrail-web_1
    04339229397e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        204567103-testrail-fpm_1
    6ae0accab28d        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        204567103-testrail-mysql_1
    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   204553690-testrail-web_1
    033b1f46afa9        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        204553690-testrail-fpm_1
    a8879c5ef941        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        204553690-testrail-mysql_1
    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   204553539-testrail-web_1
    ed6b17d911a5        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        204553539-testrail-fpm_1
    1a1eed057ea0        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        204553539-testrail-mysql_1

    Все задачи успешно завершены

    Артефакты задачи содержат логи сервисов и тестов



    Вроде все по красоте, но есть нюанс. Pipeline может быть принудительно отменен во время выполнения интеграционных тестов, и в этом случае запущенные контейнеры не будут остановлены. Время от времени нужно чистить раннер. К сожалению, задача на доработку в GitLab CE все еще в статусе Open


    Но у нас добавлен запуск задачи по расписанию, и никто нам не запрещает ее запустить вручную.
    Переходим в наш проект -> CI/CD -> Schedules и запускаем задачу Clean runner



    Итого:


    • У нас один shell runner.
    • Конфликтов между задачами и окружением нет.
    • У нас параллельный запуск задач с интеграционными тестами.
    • Можно запускать интеграционные тесты как локально, так и в контейнере.
    • Логи сервисов и тестов собираются и прикрепляются к pipeline-задаче.
    • Есть возможность очистки раннера от старых docker-образов.

    Время настройки — ~2 часа.
    Вот, собственно, и все. Буду рад фидбэку.


    P.S.
    Особая благодарность freeseacher vvasilenok ivanych. Ваши коментарии оказались очень ценными в контектсе публикации.


    К содержанию

    Поделиться публикацией

    Комментарии 20

      0
      Я возможно не верно понял Makefile, но у вас в остановке контейнеров и их чистке нет фильтрации по CI_JOB_ID. т.е убиваются контейнеры из параллельных пайпов, которые возможно еще работают?
        0
        Все верно.
        Задача по расписанию на очистку раннера настроена на 5 утра. В 5 утра крайне низкая вероятность, что кто-то будет что-то пушить.
        Но, если возникла необходимость почистить стенд прямо сейчас, то да. Все запущенные контейнеры будут остановлены и выполняемые задачи с интеграционными тестами упадут. Но предполагается, что запускающий понимает последствия.
        Еще момент. Для очистки стенда, фильтрация по CI_JOB_ID и не предполагается. Мы же раннер чистим в принципе, а не от «ошметков» какой-либо конкретной задачи.
          0
          Ясно. Пропустил момент что вы чистите по расписанию, а не после сборки. А у вас места хватает тогда? У нас за рабочий день может быть до 100 сборок (тестируем каждый новый пуш коммитов во всех ветках). Если волумы не удалять сразу — за день гигов 100 мусора. И это с ограничением в 4 раннера.
            0
            Коммитов могло быть много, а пушей в репу вот не сильно много было. Чтоб 100 гигов за день, такого не помню.
            В моей практике мы делали максимально удобно для локального запуска окружения/тестов. Разработчики прежде чем пушить гоняли тесты у себя локально. Но тут повторюсь все должно быть максимально удобно. И собрать и тесты прогнать (быстро), и в случае ошибок максимально информативные логи посмотреть. Как оказалось, разработчику проще локально все прогнать и чуть-что логи грепнуть, чем по ссылкам GitLab маневрировать.
            0
            А почему бы не фильтровать по дате запуска? Ну к примеру чистить только то, что живо более часа?

            А с отменой мы вроде с помощью trap справляемся вместо after_script, но надо посмотреть точнее, возможно ошибаюсь.
              0
              trap к сожалению не работает для отмененных задач. На сколько я понял, при отмене задачи терминальная сессия убивается с SIGKILL вместе с исполняемыми в данной сессии процессами. Т.е. trap как процесс тоже убивается. Но штука определенно классная.
          +1
          Почему не docker-runner и dind? Это решает проблему чистки раннера, а логи в виде артефактов так же можно сохранять и видеть в панели гитлаба.
          Почему docker-compose v3? Вы же наверняка swarm не используете — так и используйте тогда v2.4
            0
            Почему не docker-runner и dind? Это решает проблему чистки раннера, а логи в виде артефактов так же можно сохранять и видеть в панели гитлаба.


            Ну наверное потому, что это не так-то просто. Вы же имеете в виду запустить в dind-е демон docker, а потом в этом же dind-е запускать инстансы docker-compose?

            Идея завлекательная, но вызывает кучу вопросов, которые нужно как-то решить. К примеру — как в dind будут попадать образы сервисов, например mysql? Загружаться через docker pull? Тогда загрузку этих образов придется делать каждый раз при запуске нового теста, ведь контейнер dind-а будет удален после теста, а вместе с ним и загруженные образы.

            Или Вы имеете в виду что-то другое?
              0
              Нет. Вы все правильно говорите. С другой стороны, кто мешает пустить весь трафик через docker registry proxy, чтобы не скачивать образы из интернета?
              Зато в плюсах у вас всегда «чистая» среда. Просто реально бывают кейсы, что образ скачан, в источнике он обновился, а пока ручками пулл не сделаешь, то и сборка пойдет на базе старого.
              К тому же, между тестами можно сервисы пошарить. В общем, проблема со временем загрузки — надуманная.
              Поэтому, кмк, dind — хорошая вещь.
              Вы же имеете в виду запустить в dind-е демон docker, а потом в этом же dind-е запускать инстансы docker-compose?

              гитлаб так не работает. Я сам долго допетрить не мог, но суть в том, что поднимается два контейнера.
              Один — в котором бегут ваши тесты (и докер-клиентом, если нужно). А второй — с докер-демоном. Из контейнера с тестами с помощью docker cli можно сходить во второй контейнер и запустить еще контейнеров. А еще есть volume в котором лежат данные запусков… Рекомендую ознакомиться с документацией и поэкспериментировать.
              0

              dind — не нужен, ибо тру мозахизм. А вот образ dind с примонтированным docker.sock — вот это вещь! И кэширование работает!

              0
              Спасибо, очень интересно и полезно для новичка.
                0
                Действительно, почему не docker-runner и services?
                gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/ci/docker/using_docker_images.md#what-is-a-service
                Делаем свои образы с центральным логированием на основном, логи в артефактах забираем.
                И concurrent на одной машине без изоляции — плохо. Лично сталкивался с этим багом gitlab.com/gitlab-org/gitlab-ee/issues/2588
                Лучше сделать несколько раннеров, способных работать с одним инстансом системы.
                  +2

                  Спасибо! Весьма полезно. А какую задачу вырешали выводя CI_JOB_ID в имя контейнера?
                  И знали ли при -p ключ у docker-compose? Что бы можно было делать например так


                    script:
                      - docker-compose -f .docker/docker-compose.yml -p ${CI_COMMIT_REF_SLUG}-tests build tests
                      - docker-compose -f .docker/docker-compose.yml -p ${CI_COMMIT_REF_SLUG}-tests up --exit-code-from tests tests
                    after_script:
                      - docker-compose -f .docker/docker-compose.yml -p ${CI_COMMIT_REF_SLUG}-tests down -v
                    0
                    Ключ -p — реальная находка :-)
                    +1

                    А почему не использовать COMPOSE_PROJECT_NAME=CI_JOB_ID, в этом случае у всех контейнеров и сетей будет уникальное имя?

                      +1
                      Там выше уже пару комментариев про изоляцию написали, но всё-таки вставлю свои более подробные пять копеек.

                      В первую очередь мы делаем уникальные названия сервисов для CI. Одной из уникальных переменных в GitLab CI является переменная CI_JOB_ID. Если указать container_name со значением «service-${CI_JOB_ID:-local}»...


                      Менять название контейнера, добавляя в него номер задания, не нужно. Изоляция одного инстанса от другого делается с помощью опции -p:

                      docker-compose -p $CI_JOB_ID
                      


                      С этой опцией все сервисы, указанные в docker-compose.yml, будут запущены в изолированном окружении — инстансе. Каждому контейнеру инстанса будет автоматически добавлен префикс $CI_JOB_ID_ и все контейнеры инстанса будут запущены в автоматически созданной для них сети с таким же префиксом.

                      Во вторую очередь мы делаем общую сеть для запускаемых сервисов. Это дает нам изоляцию на уровне сети при запуске нескольких тестовых окружений.


                      Создавать сеть вручную тоже не нужно. Как написал выше — опция -p создаст для инстанса нужную сеть автоматически.

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


                      Так останавливать инстанс не нужно. Вместо kill используйте down — эта команда удалит после себя мусор (контейнеры, сети и прочее) и дальнейшая очистка раннера будет не в пример проще.

                      Мне сейчас не хочется поштучно пересматривать Ваши команды очистки, но с большой вероятностью все они (или большая часть) окажутся не нужны.
                        0
                        freeseacher vvasilenok ivanych плюсик вам в карму, добрые люди. Поправил и статью и свой gitlab-ci.
                          0
                          Кстати.

                          Сборка логов в after_script — плохое решение и вот почему: эта процедура будет выполняться всегда, в том числе и тогда, когда тесты прошли успешно. А если тесты прошли успешно — то логи не нужны. В итоге, в случае успешного прохождения тестов тратится куча времени на ненужную сборку логов.

                          Сейчас у Вас, вероятно, мало логов, плюс Вы их сохраняете в артефакты всегда, без разбора, поэтому проблема Вам может быть не очевидна. Но я вот столкнулся с этим в полный рост — у меня собираются не просто логи, а видео тестирования GUI, и копирование сотен мегабайт ненужных файлов весьма напрягает.

                          Что с этим делать — не знаю, не нашел пока изящного решения, поэтому советов не даю, это скорее пророчество Вам на будущее:)
                            0

                            Решение выше предложил 411.
                            trap “<муваем логи в отдельную папочку>” ERR
                            make test


                            В этом случе, если make test завершится с ошибкой, то сработает перемещение логов в специальную папку, а дальше их уже собрать как артефакты. Если же все хорошо, то в логах пайплайна будет предупреждение, что артефакты не найдены.

                              0
                              О, спасибо за наводку на trap. Выглядит многообещающе.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое