Выстраиваем процесс разработки и CI pipeline, или Как разработчику стать DevOps для QA +12


Дано:

  1. крупный проект на Java с фронтом на Angular,
  2. разрабатываемый небольшой командой (~15 человек),
  3. с использованием кучи (порядка 40 штук параллельно) фич-бранчей,
  4. в git-репозитории;
  5. несколько виртуальных серверов в приватном амазоновском облаке, которые можно использовать под задачи разработки;
  6. разработчик, который немного подустал от Java, и хочет сделать что-нибудь по-настоящему полезное для постановки процессов.

Требуется:

  1. обеспечить возможность команде QA инженеров тестировать каждый фич-бранч, как вручную, так и автоматизированно, на выделенном стенде, который не мешает остальным.


Консоль управления космическим кораблёмQA стендом

Вот приходишь ты работать в маленький стартап с американскими корнями…

Пока ещё маленький такой стартап, но зато с многообещающим продуктом и большими планами на завоевание рынка.

И поначалу, пока команда разработчиков совсем крошечная (до 10 человек), разработка кодовой базы ведётся в общем репозитории на GitHub Enterprise, с быстрым выделением мелких фич, бранчеванием от master, и быстрыми же циклами релизов с мержем фич-бранчей напрямую в тот же мастер. Лид команды пока что способен отследить, кто чего накоммитил, и каждый коммит не только прочитать, но и понять, правильно ли оно, или не очень. Таким образом, пулл реквесты открываются, и быстро мержатся самим же разработчиком с устного одобрения лида (или отклоняются им).

Для обеспечения целостности кодовой базы команда уповает на юнит- и интеграционные тесты, коих написана уже пара тысяч штук, и обеспечено покрытие порядка 60% (по крайней мере, для бэкэнда). Лид разработки тоже прогоняет у себя полный цикл тестов на мастере перед релизом.

Процесс выглядит как-то так:

КОММИТЫ » ТЕСТЫ ВРУЧНУЮ » РЕЛИЗ

Проходит пара месяцев. Стартап показывает жизнеспособность, инвестиции позволяют нарастить команду разработчиков до 15 человек. В основном приходят фронтэндеры, и начинают быстро расширять фасад, который видят и используют конечные пользователи. Тестируется фасад фронтерами прямо у себя на рабочих маках, они пишут какие-то кейсы на Selenium, но у лида разработки уже нет времени прогонять их перед релизом, потому что Selenium известен своей неспешностью.

И тут случаются два факапа, буквально один за другим.

Сначала один из бэкэндеров случайно делает push force в master (бедняга простыл, затемпературил, голова не соображала), после чего две недели работы всей команды приходится восстанавливать по коммитику из чудом уцелевших локальных копий — все давно уже привыкли первым делом, придя на работу, сделать себе pull.

Потом одна из крупных фич, разрабатываемая фронтерами примерно пару месяцев в отдельной ветке, и зелёная по всем UI тестам, после мержа в master резко окрашивает его в красный, и чуть-чуть не обрушивает работу всего продукта. Прошляпили breaking изменения в собственном API. И тесты никак не помогли их отловить. Бывает. Но непорядок.

Так перед стартапом встаёт в полный рост вопрос о заведении команды QA, да и вообще, регламентов работы с фич-бранчами и общей методикой разработки, вкупе с дисциплиной. А также становится очевидно, что код перед пулл реквестом должен ревьюить не только лид разработки (у него и без того дел полно), но и другие коллеги. Нормальная проблема роста, в общем-то.

Вот мы и пришли к пункту «Дано:».

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

Что ж, приступим.

Сначала настроим в амазоновском облаке головной инстанс TC (+ два бесплатных агента), повесим их слушать коммиты в гихабовском репозитории (на каждый PR гитхаб делает виртуальный HEAD — слушать изменения ну очень просто), и автоматические сборки с прогоном юнит-тестов пойдут практически сами собой. Как коммитнет кто-нибудь, так через пять минут и сборка в очередь становится. Удобно.

КОММИТЫ » ПУЛЛ РЕКВЕСТ » БИЛД + ТЕСТЫ » РЕЛИЗ

Но недостаточно.

У гитхаба в то время был ещё весьма неприятный интерфейс для просмотра пулл реквестов, и комментарии там оставлять тоже было не айс. Больно уж длинную портянку экранов надо было проматывать. То есть, отобрать у членов команды право на мерж было можно, но обеспечить нормальное ревью кода без сторонних сервисов — никак. Вдобавок, хотелось ещё получить заодно вменяемую интеграцию с Джирой, чтобы фичи к таскам, а таски к фичам сами прикреплялись.

По счастью, у Atlassian есть подобное решение, называется оно BitBucket Server, а в то время ещё звалось Stash. Как раз позволяет делать всю такую интеграционную магию с другими атлассиановскими продуктами, и очень удобно для ревью кода. Решили смигрировать.

Вот только этот чудесный продукт, в отличие от гитхаба, виртуальных HEAD на каждый PR не создаёт, и тимсити после миграции стало нечего слушать. С post-commit hooks дело тоже не пошло по причине отсутствия у всех времени хорошенько с ними разобраться.

Поэтому, попервоначалу интеграция стэша с TeamCity была сделана через кривоватый костыль. Намаявшись с хуками, заокеанский коллега вместо того, чтобы использовать встроенное REST API для просмотра пулл реквестов, в отчаянии накропал на скорую руку на bash парсинг лога, который вечно крутится вокруг его tail -f, отыскивает грепом изменения нужного вида, и потом дёргает уже REST API TC. Не самый логичный подход, и некоторые билды начали задваиваться, но что поделаешь, некогда.

Забегая вперёд — когда время появилось, мне удалось переписать stash-watcher.sh по-человечески, забирая изменения через штатный REST, распарсивая JSON-ответ при помощи великой и могучей утилиты jq, — мега-вещь для любого девопса, делающего интеграцию тулзовин! — и дёргая TeamCity только тогда, когда это на самом деле надо. Ну, ещё заодно прописал скрипт системным демоном, чтобы он стартовал при перезагрузке сам. Амазоновские инстансы изредка надо бывает перестартовать.

Вот, сложилось два кусочка головоломки.

КОММИТЫ » ПУЛЛ РЕКВЕСТ » РЕВЬЮ КОДА || БИЛДЫ + ТЕСТЫ » РЕЛИЗ

За это время в команде возникли QA инженеры.

Бедненькие! За день переключаться локально между пятью фич-бранчами, собирать и запускать их вручную!? Да врагу такого не пожелаешь!!!

Признаюсь честно: я искренне люблю QA инженеров (особенно девушек). И, в общем-то, не я один. Даже коллеги из НЙ, изначально свято веровавшие в юнит-тесты, как оказалось, их любят. Только они об этом ещё не догадывались, когда расплывчато сформулировали задачку «надо как-нибудь исследовать такой вопрос, чтобы можно было автоматом запускать где-то у нас в облаке по экземпляру приложения на каждый бранч, ну, типа, чтобы бизнесу можно было глазами посмотреть, что конкретно там сейчас с разрабатываемой фичей происходит. Would you?»

— Окей, — сказал я (ну а кто ещё? Кто однажды вляпался в DevOps, тот и крайний), — Вот и пункт «Требуется:» прибыл.

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

Тут ещё надо сказать, что приложение представляет собой несколько WAR-контейнеров, которые запускаются под Apache Tomcat. WAR же, как известно, это обычный архив ZIP с особенной структурой директорий и манифестом внутри. И при сборке приложения его конфигурация (путь к базе, пути к REST endpoints других WAR, и всё такое прочее) зашивается куда-то внутрь ресурсов. А чтобы скормить WAR томкэту, надо прописать в конфигах, откудова его брать, по какому url, и на каком порту его развёртывать.

А если мы хотим запустить сразу много экземпляров одного и того же WAR? Конфигурить томкэт на лету, чтобы раскидывать их по разным портам или url-ам? И что делать с конфигами, зашитыми внутрь ресурсов WAR?

Какая-то дурная постановка вопроса.

Значит, мы пойдём другим путём. Например, IDEA при запуске WAR в отладчике скармливает томкэту через ключ командной строки -Dcatalina.base путь к копии директории $TOMCAT_PATH/conf, и запускает WAR не единым куском, а в exploded виде, то есть, разархивированным, — чтобы на лету можно было файлы с байткодом подменять.

Подсмотрев, что делает IDEA, пробуем повторить и улучшить этот алгоритм. Для начала, заводим в амазоновском облаке здоровущий виртуальный инстанс с сотнями дискового пространства (а в exploded виде наше приложение довольно жирное) и гигабайтами оперативы.

Поднимаем там nginx — потому что в nginx довольно просто завести правило перенаправления запросов HTTP к адресу #####.dev.стартап.ком/путь/до/REST/endpoint на localhost:#####/путь/до/REST/endpoint и обратно. ##### — это уже конкретный номер порта, который конфигурируется в томкэтовых конфигах. Да, нечего пытаться даже запустить все фич-бранчи под одним томкэтом, вместо этого, для каждого из них будем заводить отдельную директорию $TOMCAT_PATH/conf, и запускать свой томкэт. Это в разы проще и надёжнее, и проблем с параллельностью нет.

Думаем, откуда взять циферки, чтобы они для разных экземпляров не совпадали. Номер билда? Нет, в таком случае QA запутаются, к какой фиче какой экземпляр относится. Номер гитовой ревизии отпадает по той же причине. Ну, делать нечего, заставляем всех разработчиков именовать ветки так, чтобы они обязательно включали в себя номер таски из Джиры по образцу feature-#####-что-нибудь или bugfix-#####-что-нибудь. Вот последние три цифры номера и будут входить в номер порта. А ещё это красиво.

В билды на тимсити, которые собирают WAR, добавляем дополнительный шаг сборки, который по SSH перекидывает их на тот жирный амазоновский инстанс, и также по SSH дёргает скрипт на bash, делающий следующее:

  1. распаковку WAR-ов в директорию /deployments/d###,
  2. копию из /deployments/skel директории conf для томкэта,
  3. вызывающий накатку отдельного экземпляра БД из дампа (дамп БД лежит в дереве исходников, так что он тоже под рукой),
  4. при помощи awk, sed, grep, find, и такой-то матери исправляющий конфиги томкэта из копии conf, а также конфиги в ресурсах распакованных WAR таким образом, чтобы в них были правильные порты, пути до базы, REST endpoints, и всего прочего.

После чего остаётся только запустить томкэт с ключом -Dcatalina.base=/deployments/d###, и готово.

КОММИТЫ » ПУЛЛ РЕКВЕСТ » РЕВЬЮ КОДА || БИЛДЫ + ТЕСТЫ » РАЗВЁРТЫВАНИЕ QA ЭКЗЕМПЛЯРА » QA ИНЖЕНЕРЫ ЕГО МУЧАЮТ » РЕЛИЗ

Так, минуточку, а наши любимые QA инженеры что, вручную будут, что ли, в облако по SSH ходить, томкэт из командной строки запускать? Как-то не супер. Можно бы автоматически его поднимать, но неудобно, так как фич-бранчей уже под 60, и память даже на самом жирном инстансе всё-таки не резиновая. Тормозить будет.

Думай, голова, шапку куплю. А! Так можно же написать консольку для управления задеплоенными инстансами, коли всё валяется в /deployments/d###. Пройти по поддиректориям, выплюнуть для каждой ссылки на старт/стоп, например.

nginx у нас уже поднят, настроить в нём classic CGI — как два байта об файерволл. А что такое classic CGI? Это когда на стандартный вход какого-то бинарника подаётся HTTP-запрос со всеми заголовками, и ставятся некоторые переменные окружения, а со стандартного выхода забирается HTTP-ответ, также со всеми заголовками. Тоже проще пареной репы, всё это можно буквально руками сделать.

Руками? Так не написать ли мне обработчик директории /deployments на bash? Потому что, наверное, могу. Как напишу, да как выложу на list.dev.стартап.ком (доступен будет только из внутренней сети стартапа, как и все экземпляры)… Иногда хочется чего-нибудь не только полезного, но и слегка ненормального. Такого, как минимальный обработчик HTTP-запросов на bash.

Вот и написал. Реально, скрипт на bash, который при помощи awk, sed, grep, find, и такой-то матери пробегается по поддиректориям /deployments, и выцепляет, где в какой что лежит. Номер билда, номер гитовой ревизии, имя фич-бранча — вся эта фигня и так на всякий случай уже передавалась из TC вместе с WAR-ником.

Заработало с полпинка. Один недостаток — парсить входные команды вида list.dev.стартап.ком/refresh?start=d### при помощи регулярок bash и никсовых утилит всё же не очень удобно. Но это уже я сам виноват — придумал глобальные слэш-команды и знак-вопроса-действия для экземпляров. Да, и вызывались внешние утилиты там для 60 поддиректорий много сотен раз, отчего консолька работала небыстро.

С другой стороны, определить, запущен ли конкретный экземпляр, можно из вывода стандартного ps (тот же греп в помощь), а также можно вызвать, к примеру, netstat или mysql -e "SHOW DATABASES" не отходя от кассы, и сунуть это в стандартный вывод, слегка подредактировав седом или авком для читаемости. Для диагностики очень хорошо, удобно.

А ещё аппетит приходит во время еды, так что вскоре в консольке появляются команды для killall -9 java (иногда хочется начать неделю с чистого листа), uptime, и несколько других полезностей. Самая главная — это возможность удалить экземпляр приложения вместе с базой. По крону, конечно, директория /deployments через две недели почистится (изначально было предусмотрено), но иногда хочется задеплоенную копию билда реджектнутого лидом PR убрать с глаз долой, чтобы не мозолила.

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

Добавляем возможность сделать моментальный снимок для задеплоенного экземпляра. Его привязываем уже к номеру гитовой ревизии (там циферки, по результатов экспериментов, вполне уникальные), и складываем в /deployments/s### (другая буква префикса, чтобы у экземпляров и снимков были разные пространства имён). Деплоим примерно тем же скриптом, что и с тимсити, только базу копируем не из дампа, а существующую.

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

КОММИТЫ » ПУЛЛ РЕКВЕСТ » РЕВЬЮ КОДА || БИЛДЫ + ТЕСТЫ » РАЗВЁРТЫВАНИЕ QA ЭКЗЕМПЛЯРА || СНИМОК QA ЭКЗЕМПЛЯРА » QA ИНЖЕНЕРЫ ЕГО МУЧАЮТ » РЕЛИЗ

Ого! Всего-то за полгода с хаотического процесса, когда разработчики коммитят фичи кто во что горазд, мы пришли к логичной, стройной системе continuous integration pipeline, где каждый шаг регламентирован, и каждый инструмент максимально автоматизирован.

Стоит только разработчику создать PR, как процесс деплоймента тестового экземпляра уже, считай, запущен, и буквально через час (если повезёт — число параллельных фич-бранчей с ростом команды вскоре возросло до сотни, пришлось поднимать аж семь инстансов под TC) у QA будет готовая к тестированию фича. Гоняй хоть вручную, хоть скриптами через REST API, а если надо, то диагностируй её и разбирайся с багами при помощи консоли управления тестовыми экземплярами.

Ну а дальше уже лирика. Через некоторое время тормоза консоли всем надоели, и мне пришлось вспоминать молодость, переписав её с bash (жаль, вся ненормальность этого маленького проекта разом потерялась) на обычный скучный PHP (впрочем, не на Java же такие задачи делать, в самом деле), а один из фронтеров сподобился переделать UI из олдскульного plain HTML на вполне современное ангуляровское приложение. Впрочем, я настоял на сохранении интерфейса а-ля девяностые, просто по приколу. Добавилась возможность просмотра stdout и stderr у томкэта. Сделали специальный CLI интерфейс для вызова REST API прямо на месте, ну и ещё немножко маленьких полезняшек.

Жутко удобная штука получилась.*


Вы только посмотрите, какие счастливые лица у команды QA инженеров!

* Хотите такую?
Напишите мне. С удовольствием рассмотрю предложения о работе в местах, где нужны матёрые (больше 10 лет опыта) специалисты с Primary Skill == Java, и возможностью иногда позаниматься подобного рода ненормальным программированием. Или процессами порулить. Можно всё сразу.

Только в Москву переехать не смогу. А вот поработать удалённо — с удовольствием.
-->


К сожалению, не доступен сервер mySQL