...

суббота, 13 июля 2019 г.

[Из песочницы] Настройка ClickHouse для интеграционного тестирования в gitlab-ci

У нас был сервис на golang, отдельный топик kafka, clickhouse, gitlab-ci и падающий пайплайн, протухший ssh-ключ и вот это вот все, а еще сезон отпусков, жуткие ливни в городе, сломавшийся ноутбук, алерты по ночам, и горящий прод. Не то, чтобы это все было нужно для этой статьи, но раз показываешь типичные будни тестировщика, то иди в своем намерении до конца. Единственное, что меня беспокоило — это p0. В мире нет ничего более отчаянного, мрачного и подавленного, чем тестировщик, который пропустил это на прод. Но я знала, что довольно скоро я в это окунусь.

Зачем все это?


Cуществует распространенная связка сервисов — сам сервис, который что-то делает, — и база данных, в которую эти результаты записываются. иногда это происходит напрямую, то есть “сервис — база”. В моем случае запись происходит через посредника, то есть “сервис — очередь — база”. 

Итого, есть несколько элементов, и граница этих элементов — выход одного и вход другого — это то самое место, где появляются проблемы. Они просто не могут там не появиться.

Яркий пример: в сервисе поле price обрабатывается как float32, в базе оно настроено как decimal(18, 5), подаем с выхода сервиса на базу максимальное значение float32 в качестве тесткейса — ой, база не отвечает. Или уже более грустный пример — база не крашится, но в логах ошибки записи данных в базу нет. просто в базу данные перестают наливаться. Или запись проходит, но с потерей данных или с искажением: поле выходит из сервиса как float64, а записывается как float32. 

Или в процессе жизненного цикла сервиса решили, что надо поменять тип того или иного поля. Поле уже давно реализовано на проде, но вот необходимо его отредактировать. И конечно же поменяли это только в одном месте. Хоба, что-то опять пошло не так.

Задача


Я не хочу следить за всеми этими изменениями. Я хочу, чтобы оно не падало. Я хочу, чтобы запись проходила корректно.

Выход: интеграционные тесты! 

Реализация и трудности 


Где ломать?


Есть dev-окружение: жутко нестабильное и обычно используется разработчиками как песочница. Там творится хаос и анархия, характерные для жесткого бекенда. 

Есть test-окружение или qa-стенд: настроено уже получше, за ним даже следят devops, но пока их не пнешь, ничего не произойдет. и еще это окружение часто обновляется. а еще чаще там что-то сломано. 

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

Так что же делать с окружением, когда оно или нестабильное, или боевое? Правильно, создавать свое!

Что делать с базой?


Базу можно запускать несколькими способами.

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

Во-первых, можно поднять покостылить clickhouse-server с нужными настройками, раскатать на нем необходимые sql и общаться с ним посредством clickhouse-client. На первой же успешной попытке положить подобную базу, загрустил и ci. тесты зафейлились, сервер не потух и продолжил работать. Скажем так, до меня до сих пор остается загадкой, почему оно вообще запустилось. (оно само, я ни при чем). Не советую этот вариант.

Удобный вариант из коробки — использование docker образа.
Скачиваем нужную версию к себе на машину. Clickhouse для старта нужен config.xml с настройками. Подробнее тут
Для переиспользуемого образа клика надо создать правильный dockerfile. Указываем в нем, что хотим скопировать config.xl в папку, докидываем другие требуемые конфиги. Обязательно копируем скрипты для разворачивания своей базы. 

Так как к образу мы будем обращаться извне, то надо открыть те порты, по которым будем общаться с кликхаусом. Клик работает на 8123 по http и на 9000 по tcp.

Получаем следующий dockerfile:

From yandex/clickhouse-server

Expose 8123
Expose 9000

Add config.xml /etc/clickhouse-server/config.xml
Add my_init_script.sql /docker-entrypoint-initdb.d/


Как закинуть образ в ci? 


Чтобы с docker-образом как-то работать в ci, его надо там как-то вызвать. 

Можно закоммитить и запушить образ в свой репозиторий и в рамках запуска тестов выполнять docker run с нужными параметрами. Только вот docker-образ клика весит под 350мб. неприлично такие файлы держать в git.

Кроме того, если один и тот же docker-образ нужен на разных проектах (например, разные сервисы пишут в одну и ту же базу), то тем более так делать не стоит. Можно использовать хранилище образов docker registry
Считаем, что в нашем проекте он уже есть и используется. Поэтому логинимся, собираем docker-образ и пушим его туда. 

docker build -t my_clickhouse_image .
docker login my_registry_path.domain.com
docker push my_clickhouse_image


Вжух и наш образ улетел в registry. Обязательно указываем тег при сборке!

База готова.

Подробнее про registry тут

Что делать с ci ?


Как в рамках одного шага запустить и свой сервис, и базу? 

Все зависит от того, как у нас запускается и используется сервис. Если с сервисом работать как с docker-образом, да и вообще весь .gitlab-ci.yml работает только с ними, то все просто. 
Существует приблуда dind — docker-in-docker. Она указывается как основной сервис, с которым работает ci, и позволяет и докером полноценно пользоваться, и вообще не напрягаться.

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

image: docker:stable
services:
- docker:dind

stages:
- build
  …
- test-click
...
- test
- release
  …

test-click:
  variables:
    VERY_IMPORTANT_VARIABLE: “its value”
  before_script:
  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
  - docker pull My_Service_Image
  - docker pull My_ClickHouse_Image
  - docker run -FLAGS My_ClickHouse_Image
  - docker run My_Service_Image /path/to/tests


В официальном докере докера указывается, что не рекомендуется использовать dind, но если очень надо…

В моем проекте сервис надо тестировать через запуск бинарника. Тут и начинается магия
Для этого нужно использовать базу как сервис. Официальная документация gitlab-ci приводит использование контейнера с базой в качестве примера самого распространенного варианта использования docker-контейнера в ci. Даже приведены примеры настроек mysql, postress и redis. Но мы же не ищем легких путей, нам нужен clickhouse.

Подключаем базу! Обязательно указываем alias. если его не указать, то базе будет приписываться рандомное имя и рандомный ip. То есть, будет непонятно, как именно к ней обращаться. С alias такой проблемы не будет — в коде тестов обращение будет выглядеть как, например, по хттп http://my_alias_name:8123.

Для тестов все так же требуется образ базы, который мы старательно запушили в registry. для скачивания образа необходимо выполнить docker login и docker pull, только ci не знает, что такое docker — надо его установить.

Итоговый код для шага в gitlab-ci.yml:

Integration tests:
Services:
- name: my_clickhouse:latest
  alias: clicktest
Stage: tests
Variables:
Variables_for_my_service: “value”
Before_script:
- curl -ssl https://get.docker.com/ | sh
- docker login -u gitlab-ci-token -p $ci_build_token my_registry_path.domain.com
Script:
- ./bin/my_service &
- go test -v ./tests -tags=integration
Dependencies:
- build


Профит


  • У меня есть работающая связка сервис-базка.
  • В рамках автотеста легко обратиться к базе — просто по alias.
  • Обнуляю записи и настройки базы в рамках setup теста, вызываю работу сервиса, он пишет в базу, обращаюсь к базе, смотрю, что база не отвалилась, смотрю, что пришло, валидирую. накидываю побольше тестов.
  • Можно ручками не тестировать!

Результаты


Казалось бы, пара строчек настройки в gitlab-ci. Собрать docker образ — просто. Запустить базку локально — просто. у меня за сутки появилась интеграция с первыми тестами, которые нашли проблемы. Но попытки запустить в это в ci обратились в неделю боли и безысходности. А теперь и в недели боли и безысходности разработчиков, которым придется чинить все, что они там напрограммировали.

Что мы сумели сделать?


  • Мы настроили контейнер с clickhouse.
  • Запушили контейнер в локальное хранилище.
  • Научились подтягивать этот образ в шаг ci.
  • Запустили его в раннере.

Легко отправили данные в базу и обратились к ней из теста.

Автоматизация — это довольно простой способ избавить себя от рутины ручного протыкивания интеграции.

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

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий