Добрый день.


Практически сразу после установки и конфигурации CI/CD по инструкции из предыдущего поста у команды возник вопрос как правильно осуществлять интеграционное тестирование. У нас уже был опыт запуска тестовых зависимостей в docker контейнерах, но это стало проблематичным так как теперь сама сборка была запущена в контейнере. В этой заметке я бы хотел описать два возможных способа интеграционного тестирования внутри контейнера, которые подошли моей команде.


Требования к интеграционному тестированию


По определению интеграционным тестированием называется тестирование, которое проверяет работу приложения с его зависимыми компонентами. Примерами может быть базы данных, очереди а также другие сервисы.


В рамках тестирования нам хотелось:


  • одинаково запускать тесты как локально так и на jenkins
  • избежать предварительной установки и конфигурации зависимых приложений
  • декларировать и запускать зависимости используя файлы в репозитории проекта
  • иметь возможность запускать несколько сборок параллельно
  • желательно иметь возможность добавлять новые зависимости в будущем
  • желательно иметь возможность тестировать с разными версиями одной и той же зависимости

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


Вариант 1: встроенные решения


В экосистеме java существует достаточно много библиотек которые запускают зависимость для теста:



Данный подход является максимально простым и удовлетворяет большинство из требований описанных ранее, но не является универсальным с точки зрения добавления новых тестовых зависимостей (mysql?) либо использования конкретных либо многих версий зависимостей.


Хорошо подходит для простых сервисов с 1-2 зависимостями.


Вариант 2: testcontainers


Docker является логичным способом разрешить недостатки предыдущего подхода: можно найти (или создать Docker image) для любой зависимости с любой версией. Вероятно, некоторые из зависимостей и в продакшене запущены используя те же самые образы.


Если локально запуск образа (или несколько используя docker-compose) не составит проблем, то на CI возникнут сложности так как сама сборка происходит в контейнере. Хотя и возможно запустить docker в docker но это не рекомендуется самим создателем dind. Предпочтительным способом обойти эту проблему является переиспользование уже запущенного docker процесса который часто называют sibling docker. Для этого нужно чтобы дочерний docker процесс использовал /var/run/docker.sock от родительского. В предыдущем посте это уже было использовано для публикации docker images с собранным приложением.


Было принято решение использовать библиотеку testcontainers так как она:


  • предоставляет хороший api для управления зависимостями
  • имеет интеграции с наиболее популярными базами данных и очередей
  • использует sibling docker подход при запуске в контейнере
  • одинаково работает локально и на ci
  • останавливает контейнеры после сборки

Хорошо подходит для более сложных сервисов либо для сервисов с особыми требованиями к зависимостям.


Управление ресурсами


Далее стоит обратить внимание на потребление ресурсов сборкой проекта (которое может значительно возрасти после добавления интеграционных тестов).


На текущий момент сборка не указывает необходимое количество памяти и cpu shares, что можеть две потенциальные проблемы:


  • Первая — это слишком много параллельных сборок на одной машине, что приведет в высокому load factor. Вероятно, сборки пройдут, но потратят на это намного больше времени.
  • Вторая — это OOM kill. Кубернетис может решить что вы потребляете слишком много памяти и просто будет убивать сборки прежде чем они завершаться.

Ограничить ресурсы контейнера в поде можно используя конструкцию:


  resources:
    requests:
      cpu: 1
      memory: 512Mi
    limits:
      cpu: 1
      memory: 512Mi

Jdk9 и выше уже имееют поддержку работы в контейнере (-XX:+UseContainerSupport (включен по умолчанию), работает в комбинации с -XX:InitialRAMPercentage / -XX:MaxRAMPercentage)


Полный пример можно посмотреть тут.


Для корректной работы Jdk8 требуется либо обновление 131 и выше с включенными флагами -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap для чтения доступной памяти из cgroup а не из хост машины, либо каждый раз указывать доступный размер хипа вручную используя Xmx.
Пример доступен тут.


Стоит заметить, что kubernetes ничего не знает о ресурсах затраченных на контейнеры запущенные используя testcontainers либо sibling-docker. Для корректной работы в данной ситуации можно зарезервировать ресурсы в контейнере maven с учетом всех тестовых зависимостей.


Заключение


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


Пример приложения с интеграционными тестами используя testcontainers вы можете найти здесь и конфигурацию для запуска Jenkins на kubernetes здесь.

Комментарии (0)