В настоящее время Continuous Integration помогает разработчикам автоматизировать собственную сборку и тестирование в удаленных репозиториях. Более того, когда мы работаем в команде, CI гарантирует, что все ветки на удаленных репозиториях будут протестированы автоматической сборкой. Поэтому в своем первом посте я хочу рассказать вам о том, как я создаю интеграционное тестирование с помощью docker и Github action.
Обзор
Предположим, что у нас есть 4 сервиса, назовем их сервисы A, B, C и D соответственно. Каждый сервис будет иметь свою собственную систему баз данных и зависимости между каждым сервисом.
Чтобы у нас была одинаковая картина и представление о том, что мы собираемся делать, мы будем тестировать сервисы A, которые имеют зависимости с сервисами B, C и D на репозитории github. Эта практика будет намного проще, если мы будем работать в локальной среде, но в удаленной среде есть кое-что, над чем нам нужно поработать, но поверьте мне, вы привыкнете к этому после того, как потерпите неудачу 4 или 5 раз.
Настройка действий Github
Давайте начнем с настройки действий github. Эта функция Github позволяет автоматически собирать и тестировать ваш код. Мы можем либо использовать конфигурацию по умолчанию, создаваемую действием Github, либо настроить собственный рабочий процесс.
После выбора мы создадим конфигурацию, которая будет представлять собой файл yml
и будет создана в каталоге .github/workflow
. В этом примере мы будем использовать yarn в качестве инструмента менеджера пакетов.
#Sample yaml configuration
name: "CI"
on:
pull_request:
push:
branches:
- master
- "releases/*"
jobs:
# Integration Test
tests:
name: "Integration Test"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: yarn
- run: yarn test
Создание Docker compose для сборки контейнера сервиса
Предположим, что мы уже создали docker-файл и образ для сборки контейнера, одной из основных проблем, из-за которой моя сборка не удавалась много раз, является сеть. Обычно, если наш контейнер docker является автономным и не имеет никакой интеграции с другими сервисами, он будет работать совершенно нормально, но в данном случае, который включает в себя множество сервисов для тестирования, без сети сервисы не могут общаться друг с другом.
# sample docker-compose.yml for service A
version: '3'
services:
#database for service A
service-a-db:
image: mongo
networks:
- testing-network
service-a:
image: <your-docker-repo-name>/service-a
restart: always
depends_on:
- mongo
ports:
- 8888:8888
networks:
- testing-network
environment:
# your environment variable
# network can be any name but make sure it use a same name.
networks:
testing-network:
Из примера конфигурации yaml видно, что у службы A есть одна служба базы данных service-a-db, которая имеет ту же сеть, что и служба A. При такой конфигурации служба A будет находиться в одной сети с базой данных службы A. В приведенном ниже примере будет показана вся конфигурация каждой службы в файле docker-compose.yml
.
#docker-compose.yml
version: '3'
services:
#database for service A
service-a-db:
image: mongo
networks:
- testing-network
service-a:
image: <your-docker-repo-name>/service-a
restart: always
depends_on:
- mongo
ports:
- 8888:8888
networks:
- testing-network
environment:
SERVICE_D_HOST=localhost
SERVICE_D_PORT=8080
DB_HOST = mongo
DB_PORT = 27017
#database for service B
service-b-db:
image: mongo
networks:
- testing-network
service-b:
image: <your-docker-repo-name>/service-b
restart: always
depends_on:
- mongo
ports:
- 45000:45000
networks:
- testing-network
environment:
DB_HOST = mongo
DB_PORT = 27017
#database for service C
service-c-db:
image: mongo
networks:
- testing-network
service-c:
image: <your-docker-repo-name>/service-c
restart: always
depends_on:
- mongo
ports:
- 4000:4000
networks:
- testing-network
environment:
SERVICE_B_HOST=localhost
SERVICE_B_PORT=45000
DB_HOST = mongo
DB_PORT = 27017
#database for service D
service-d-db:
image: mongo
networks:
- testing-network
service-d:
image: <your-docker-repo-name>/service-d
restart: always
depends_on:
- mongo
ports:
- 8080:8080
networks:
- testing-network
environment:
SERVICE_B_HOST=localhost
SERVICE_B_PORT=45000
SERVICE_C_HOST=localhost
SERVICE_C_PORT=4000
DB_HOST = mongo
DB_PORT = 27017
networks:
testing-network:
К сожалению, только настройки сети в конфигурации будет недостаточно для организации связи между сервисами. В обычных случаях мы будем использовать 'localhost
в качестве имени хоста по умолчанию для службы, но localhost
подходит в основном для локальных сред, которые не имеют возможности удаленного доступа. Поэтому нам нужно изменить имя хоста на имя сервиса, чтобы при запуске внутри среды docker каждый сервис мог распознавать друг друга в пределах одной сети. Например, на диаграмме сервис A имеет зависимость с сервисом D, поэтому мы должны изменить SERVICE_D_HOST
с localhost
на service-d
, что является именем сервиса, с которым интегрируется сервис A. Также у службы A есть служба базы данных, называемая service-a-db, поэтому мы должны изменить DB_HOST
на имя службы. После того как мы изменим все имена хостов и db, конечный результат будет выглядеть следующим образом.
# docker-compose.yml
version: '3'
services:
#database for service A
service-a-db:
image: mongo
networks:
- testing-network
service-a:
image: <your-docker-repo-name>/service-a
restart: always
depends_on:
- mongo
ports:
- 8888:8888
networks:
- testing-network
environment:
SERVICE_D_HOST=service-d
SERVICE_D_PORT=8080
DB_HOST = service-a-db
DB_PORT = 27017
#database for service B
service-b-db:
image: mongo
networks:
- testing-network
service-b:
image: <your-docker-repo-name>/service-b
restart: always
depends_on:
- mongo
ports:
- 45000:45000
networks:
- testing-network
environment:
DB_HOST = service-b-db
DB_PORT = 27017
#database for service C
service-c-db:
image: mongo
networks:
- testing-network
service-c:
image: <your-docker-repo-name>/service-c
restart: always
depends_on:
- mongo
ports:
- 4000:4000
networks:
- testing-network
environment:
SERVICE_B_HOST=service-b
SERVICE_B_PORT=45000
DB_HOST = service-c-db
DB_PORT = 27017
#database for service D
service-d-db:
image: mongo
networks:
- testing-network
service-d:
image: <your-docker-repo-name>/service-d
restart: always
depends_on:
- mongo
ports:
- 8080:8080
networks:
- testing-network
environment:
SERVICE_B_HOST=service-b
SERVICE_B_PORT=45000
SERVICE_C_HOST=service-c
SERVICE_C_PORT=4000
DB_HOST = service-d-db
DB_PORT = 27017
networks:
testing-network:
Добавление Docker к действиям GitHub
Теперь для финальной части, мы добавляем файл docker compose, который содержит все необходимые нам сервисы, включая протестированные сервисы.
name: "CI"
on:
pull_request:
push:
branches:
- master
- "releases/*"
jobs:
tests:
name: "Integration testing"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Set up environment"
run: docker compose -f docker-compose.yml up -d
- name: "Wait for docker to finish building"
run: sleep 30
- name: "Test server"
run: docker exec -it service-a-1 yarn test
В этой конфигурации мы используем docker compose up -d
для сборки контейнера на фоне -d
не будет замораживать терминал с кучей лог-сообщений от сборки докера. После этого мы подождем 30 секунд, пока Docker закончит сборку. На самом деле написание подобной логики не так уж эффективно, потому что она может быть быстрее или медленнее 30 секунд. Лучше всего использовать цикл while и проверять ответ сервера, чтобы выйти из цикла, как только условие будет выполнено с ответом сервера, что означает завершение сборки docker.
После того как тестовая среда готова, мы можем начать тестирование. В приведенной выше конфигурации я использую yarn test
в качестве команды для тестирования сервиса. Поэтому я использую команду docker exec -it service-a-1 yarn test
для выполнения тестовой команды внутри тестируемого сервиса, которым в данном случае является service-a (за именем контейнера по умолчанию будет следовать -1, поэтому убедитесь, что вы поставили его после имени контейнера).
Заключение
Мы видим, что основными ключевыми моментами конфигурации для того, чтобы каждый сервис мог взаимодействовать друг с другом, являются:
- сеть: убедитесь, что используется одна и та же сеть в каждом сервисе.
- host: localhost не будет работать, если это удаленное окружение, поэтому убедитесь, что вместо него используется имя сервиса.
С помощью GitHub Action и docker можно создать тестовую среду на нашем удаленном хранилище, поэтому нам не нужно развертывать другие сервисы, чтобы использовать его для тестирования.
Надеюсь, вам понравился этот материал, и я открыт для обратной связи по поводу этого поста, так что не стесняйтесь оставлять комментарии или предложения. Спасибо! 😄