Pipeline что это в программировании
Jenkins Pipeline. Что это и как использовать в тестировании
Меня зовут Александр Михайлов, я работаю в команде интеграционного тестирования компании ЮMoney.
Наша команда занимается приемочным тестированием. Оно включает в себя прогон и разбор автотестов на критичные бизнес-процессы в тестовой среде, приближенной по конфигурации к продакшену. Еще мы пишем фреймворк, заглушки, сервисы для тестирования — в целом, создаем экосистему для автоматизации тестирования и обучаем ручных тестировщиков автоматизации.
Надеюсь, что эта статья будет интересна как новичкам, так и тем, кто съел собаку в автоматизации тестирования. Мы рассмотрим базовый синтаксис Jenkins Pipeline, разберемся, как создать джобу на основе пайплайна, а также я расскажу про опыт внедрения неочевидной функциональности в CI — запуска и дожатия автотестов по условию.
Запуск автотестов на Jenkins — инструкция
Не новость, что автотесты эффективнее всего проводить после каждого изменения системы. Запускать их можно локально, но мы рекомендуем делать это только при отладке автотестов. Больший профит автотесты принесут при запуске на CI. В качестве CI-сервера у нас в компании используется Jenkins, в качестве тестового фреймворка — JUnit, а для отчетов — Allure Report.
Чтобы запускать тесты на Jenkins, нужно создать и сконфигурировать джобу.
Для этого достаточно выполнить несколько несложных шагов.
1) Нажать «Создать», выбрать задачу со свободной конфигурацией и назвать ее, например, TestJob.
Естественно, для этого у вас должны быть права в Jenkins. Если их нет, нужно обратиться к администратору Jenkins.
2) Указать репозиторий, откуда будет выкачиваться код проекта: URL, credentials и branch, с которого все будет собираться.
3) Добавить нужные параметры, в этом примере — количество потоков (threadsCount) и список тестов для запуска (testList).
Значение “*Test” для JUnit означает «Запустить все тесты».
4) Добавить команду для запуска тестов.
Наш вариант запускается на Gradle: мы указываем таску теста и передаем параметры в тесты.
Можно выполнить шаг сборки «Выполнить команду shell», либо через Gradle Plugin использовать шаг «Invoke Gradle Script».
5) Нужно добавить Allure-report (должен быть установлен https://plugins.jenkins.io/allure-jenkins-plugin/) в «Послесборочные операции», указав путь к артефактам Allure после прогона (по умолчанию allure-result).
Создав джобу и прогнав с ее помощью тестов, мы можем получить такой результат.
На скрине — реальный прогон. По таймлайну видно, как тесты разделяются по потокам и где были падения.
Несложно заметить, что тесты у нас падают.
Почему падают тесты
Падения могут случаться по разным причинам. В нашем случае на это влияют:
ограниченные ресурсы тестового стенда,
большое число микросервисов (
140); если при запуске интеграционных тестов какой-то один микросервис подтормаживает, тесты начинают валиться,
большое число интеграционных тестов (>3000 E2E),
врожденная нестабильность UI-тестов.
Что такое дожим
Дожим — это перезапуск автотестов в рамках одного прогона. При успешном прохождении автотеста можно считать его пройденным, не учитывая предыдущие падения в прогоне.
«Дожимать? Опасно же!»
Возразите вы и будете полностью правы. Безусловно, дожим автотестов может спровоцировать пропуск дефекта на продакшн. Баг может быть плавающим — при повторном запуске успешно просочиться и попасть на продакшн.
Но мы проанализировали статистику падений автотестов за длительный период и увидели, что большинство падений было связано с нестабильной тестовой средой, тестовыми данными. Поэтому мы взяли на себя риск и стали дожимать тесты. Прошло уже больше года, и не было ни одного дефекта, пропущенного на продакшн по этой причине. Зато стабильность тестов повысилась ощутимо.
Как решать задачу с дожимами
Мы пробовали разные решения: использовали модификацию поведения JUnit 4, JUnit 5, писали обертки на Kotlin. И, к сожалению, каждый раз реализация завязывалась на фичах языка или фреймворка.
Если процесс запускался с помощью JUnit 4 или JUnit 5, возможность перезапустить тесты была только сразу при падении. Тест упал, перезапустили его несколько раз подряд — и если сбоил какой-то микросервис из-за нагрузки, либо настройки тестовой среды были некорректные, то тест все три раза падал.
Это сильно удлиняло прогон, особенно когда проблема была в настройке тестовой среды, что приводило к провалу множества тестов.
Мы взглянули на проблему шире, решили убрать зависимость от тестового фреймворка или языка и реализовали перезапуск на более высоком уровне — на уровне CI. И сделали это с помощью Jenkins Pipeline.
Для этого подходил следующий алгоритм: получаем список упавших тестов и проверяем условия перезапуска. Если упавших тестов нет, завершаем прогон. Если есть, решаем, запускать их повторно или нет. Например, можно реализовать логику, чтобы не перезапускались тесты, если их упало больше какого-то критического числа. Или перед запуском можно сделать проверку доступности тестируемого сервиса.
Что такое Jenkins Pipeline
Jenkins Pipeline — набор плагинов, позволяющий определить жизненный цикл сборки и доставки приложения как код. Он представляет собой Groovy-скрипт с использованием Jenkins Pipeline DSL и хранится стандартно в системе контроля версий.
Существует два способа описания пайплайнов — скриптовый и декларативный.
Они оба имеют структуру, но в скриптовом она вольная — достаточно указать, на каком слейве запускаться (node), и стадию сборки (stage), а также написать Groovy-код для запуска атомарных степов.
Декларативный пайплайн определен более жестко, и, соответственно, его структура читается лучше.
Рассмотрим подробнее декларативный пайплайн.
В структуре должна быть определена директива pipeline.
Также нужно определить, на каком агенте (agent) будет запущена сборка.
Дальше идет определение stages, которые будут содержаться в пайплайне, и обязательно должен быть конкретный стейдж с названием stage(“name”). Если имени нет, тест упадет в runtime с ошибкой «Добавьте имя стейджа».
Обязательно должна быть директива steps, в которой уже содержатся атомарные шаги сборки. Например, вы можете вывести в консоль «Hello».
Мне нравится декларативный вид пайплайна тем, что он позволяет определить действия после каждого стейджа или, например, после всей сборки. Я рекомендую использовать его при описании пайплайнов на верхнем уровне.
Если сборка и стейдж завершились успешно, можно сохранить артефакты или почистить workspace после сборки. Если же при таких условиях использовался бы скриптовый пайплайн, пришлось бы за этим «флоу» следить самостоятельно, добавляя обработку исключений, условия и т.д.
При написании своего первого пайплайна я, естественно, использовал официальный мануал — там подробное описание синтаксиса и степов.
Сначала я даже растерялся и не знал, с чего начать — настолько там много информации. Если вы первый раз сталкиваетесь с написанием пайплайна, начать лучше со знакомства с генераторами фрагментов пайплайна из UI-интерфейса.
Если к URL вашего веб-интерфейса Jenkins добавить ендпойнт /pipelines-syntax, откроется страница, в которой есть ссылки на документацию и два сниппет-генератора, позволяющие генерировать пайплайн даже без знания его синтаксиса:
Declarative sections generator
Генераторы фрагментов — помощники в мире Jenkins
Для создания пайплайна сначала нужно декларативно его описать, а затем наполнить степами. Давайте посмотрим, как вспомогательные инструменты нам в этом помогут.
Declarative sections generator (JENKINS-URL/directive-generator) — генератор фрагментов для декларативного описания пайплайна.
Для добавления стадии нужно написать ее имя и указать, что будет внутри (steps). После нажатия кнопки «Сгенерировать» будет выведен код, который можно добавлять в пайплайн.
Также нам нужны шаги, в которых будут выполняться различные действия — например, запуск джобы Jenkins с тестами.
Snippet Generator (JENKINS-URL/pipeline-syntax) — поможет сгенерировать фрагменты шагов.
В Sample Step выбрать build: Build a job.
(Дальше функционал подсказывает) — необходимо определить параметры, которые будут переданы в джобу (для примера задано branch, project).
Нажать кнопку «Generate» — в результате сформируется готовый рабочий код.
Изменим параметры джобы на те, которые определили при ее создании.
Snippet Generator удобен тем, что подсвечивает шаги в зависимости от установленных плагинов. Если у вас есть, например, Allure-report, то плагин сразу покажет наличие расширения и поможет сгенерировать код, который будет работать.
Вставим сгенерированный код степа в пайплайн на следующем шаге.
Запуск тестов с помощью Pipeline — инструкция
Итак, давайте с помощью Declarative sections generator создадим пайплайн. В нем нужно указать директивы: pipeline, agent (агент, на котором будет запускаться пайплайн), а также stages и steps (вставка ранее сгенерированного кода).
Так получится пайплайн, который запустит джобу, созданную на предыдущем шаге через UI.
Напомню, что в параметры для запуска тестов мы передавали количество потоков и список тестов. Теперь к этому добавляем параметр runId (идентификатор прогона тестов) — он понадобится позднее для перезапуска конкретного сьюта тестов.
Чтобы запустить пайплайн, нужно создать проект.
Для этого нужно нажать на кнопку «Создать проект» и выбрать не джобу со свободной конфигурацией, а джобу Pipeline — осталось указать ей имя.
Добавить параметры runId, threadsCount, testList.
Склонировать из Git.
Пайплайн можно описать непосредственно как код и вставить в поле, но для версионирования нужно затягивать пайплайн из Git. Обязательно указываем путь до скрипта с пайплайном.
Готово, джобу можно запускать.
Хотим добавить немного дожатий
На этом этапе у нас уже готова джоба для запуска проекта с автотестами. Но хочу напомнить, что наша задача — не просто запускать тесты, а добавить им стабильности, исключив падения из-за внешних факторов. Для достижения этого результата было принято решение дожать автотесты.
Для реализации нужно:
вынести шаг запуска тестов в библиотечную функцию (shared steps),
получить упавшие тесты из прогона,
добавить условия перезапуска.
Теперь немного подробнее про каждый из этих шагов.
Многократное использование шагов — Shared Steps
В процессе написания пайплайнов я столкнулся с проблемой постоянного дублирования кода (часто используемых степов). Этого хотелось избежать.
Решение нашлось не сразу. Оказывается, для многократного использования кода в Jenkins есть встроенный механизм — shared libraries, который позволяет описать методы один раз и затем применять их во всех пайплайнах.
Существуют два варианта подключения этой библиотеки.
Написанный проект/код подключить через UI Jenkins. Для этого требуются отдельные права на добавление shared libraries или привлечение девопс-специалистов (что не всегда удобно).
Хранить код в отдельном проекте или в проекте с пайплайнами. При использовании этот код подключается как динамическая библиотека и выкачивается каждый раз при запуске пайплайна.
Мы используем второй вариант — размещаем shared steps в проекте с пайплайнами.
Для этого в проекте нужно:
в ней создать файл с названием метода, который планируется запускать — например, gradlew.groovy,
стандартно определить имя метода (должен называться call), то есть написать «def call» и определить входящие параметры,
в теле метода можно написать произвольный Groovy-код и/или Pipeline-степы.
Вынесение запуска тестов в shared steps в /var
Выносим startTests.groovy в /var.
Во-первых, нужно вынести запуск тестов в отдельный метод. Выглядит это так — создаем файл, называем метод def call, берем кусок кода, который был в пайплайне, и выносим его в этот step.
Структура проекта будет выглядеть так.
Подключение shared steps как внешней библиотеки.
Дальше нужно добавить вынесенные шаги в пайплайн. При динамической подгрузке библиотек (во время запуска пайплайна) эти шаги выкачиваются из репозитория и подключаются на лету.
Сделать это можно с помощью сниппет-генератора — выбираем степ library и указываем ветку, в которую все будет собираться и репозиторий. Дальше нажимаем кнопку «Сгенерировать» и вставляем получившийся пайплайн.
Теперь после подключения shared steps вместо шага запуска тестов build нужно вставить startTest. Не забудьте, что имя метода должно совпадать с именем файла.
Теперь наш пайплайн выглядит так.
Первый шаг реализован, теперь можно многократно запускать тесты в разных местах. Переходим к 2 шагу.
Получение упавших тестов из прогона
Теперь нам нужны упавшие тесты. Каким образом их извлечь?
Установить в Jenkins плагин JUnit Test Result Report и использовать его API.
Взять результаты прогона JUnit (обычно в формате XML), распарсить и извлечь нужные данные.
Запросить список упавших тестов из нужного места.
В нашем случае таким местом является собственный написанный сервис — Reporter. Во время прогона тестов результат по каждому из них отправляется именно туда. В конце прогона делаем http-запрос и получаем упавшие тесты.
Добавление условий перезапуска
На этом шаге следует добавить getFailedTests.groovy в /var. Представим, что у вас есть такой сервис — Reporter. Нужно назвать файл getFailedTests, сделать запрос httpRequest в этот сервис и распарсить его.
Отлично, второй шаг выполнен. Осталось добавить ту самую изюминку — условия перезапуска. Но сначала посмотрим, что получается.
Есть запуск тестов и получение результатов прогона. Теперь нужно добавить те самые условия, которые будут говорить: «Если все хорошо, завершай прогон. Если плохо, давай перезапускать».
Условия перезапуска
Какие условия для перезапуска можно реализовать?
Приведу часть условий, которые используем мы.
1) Если нет упавших тестов, прогон завершается.
2) Как я уже писал выше, на тестовой среде ресурсы ограничены, и бывает такое, что ТС захлебывается в большом количестве параллельных тестов. Чтобы на дожатии избежать падений тестов по этой причине, понижаем число потоков на повторном запуске. Именно для этого при создании джобы и в самом пайплайне мы добавили параметр threadsCount.
Если и после уменьшения потоков тесты не дожимаются (количество упавших тестов на предыдущем прогоне равно числу упавших тестов на текущем), тогда считаем, что дело не во влиянии ТС на тесты. Останавливаем прогон для анализа причин падений — и тем самым предотвращаем последующий холостой запуск.
3) Третья и самая простая проверка состоит в том, что если падает большое количество тестов, то дожимать долго. Скорее всего, причина падений какая-то глобальная, и ее нужно изучать.
Для себя мы определили: если тестов > 40, дожимать не автоматически не будем, потому что 40 наших E2E могут проходить порядка 15 минут.
Последние два условия — так называемые fail fast. Они позволяют при глобальных проблемах на тестовом стенде не делать прогоны, которые не приведут к дожиму тестов, но будут занимать ресурсы тестового стенда.
Итоговый pipeline
Итак, все 3 шага реализованы — итоговый пайплайн выглядит так.
Визуализация с Blue Ocean
Как все это выглядит при прогоне в Jenkins? У нас, к примеру, для визуализации в Jenkins установлен плагин Blue Ocean.
На картинке ниже можно увидеть, что:
запустился метод testwith_rerun,
прошел первый запуск,
прошла проверка упавших тестов,
запустился второй прогон,
после успешной проверки джоба завершилась.
Вот так выглядит визуализация нашего настоящего прогона.
В реальном примере две ветки: мы параллельно запускаем два проекта с тестами (на Java и Kotlin). Можно заметить, что тесты одного проекта прошли с первого раза, а вот тесты другого пришлось дожимать еще раз. Таким образом визуализация помогает найти этап, на котором падают тесты.
А так выглядит реальный timeline приемки релиза.
После первого прогона отправили дожимать упавшие тесты. Во второй раз упавших тестов намного меньше, дожимаем в третий раз — и вуаля, успешный build.
Мы перенесли логику перезапусков упавших тестов из тестового проекта на уровень выше — на CI. Таким образом сделали механизм перезапуска универсальным, более гибким и независимым от стека, на котором написаны автотесты.
Раньше наши тесты дожимались безусловно, по несколько раз, с неизменным количеством параллельных потоков. При перегрузке тестового стенда, некорректных настройках тестового окружения либо каких-то других проблемах — красные тесты перезапускались фиксированное число раз без шансов дожаться. В худшем случае прогоны могли длиться часами. Добавив условия fail fast, мы сократили это время.
При падении тестов инженер, ответственный за приемку, в некоторых ситуациях вручную стартовал джобу перезапуска, выполняя прогон с меньшим числом потоков. На это тоже уходило время. Добавив в условия пайплайна уменьшение числа потоков на перезапуске, мы сократили и это время.
Какой профит мы получили:
уменьшили time-to-market тестируемых изменений,
сократили длительность аренды тестового стенда под приемочное тестирование,
увеличили пропускную способность очереди приемочного тестирования,
не завязаны на тестовый фреймворк («под капотом» может быть что угодно — дожатия будут работать),
поделились знаниями об использовании Jenkins Pipeline.
Примеры кода выложены на GitHub. Если будут вопросы, задавайте — обязательно отвечу.
А еще в нашей компании принято награждать ачивкой за какое-то достижение. Статья получилось достаточно подробной и многословной, а потому вас, дочитавших до конца этот лонгрид, хотелось бы поощрить.
Помимо +100 к опыту и знаниям Jenkins вы получаете ачивку «Ю Academic»! 🙂
GitLab CI для непрерывной интеграции и доставки в production. Часть 1: наш пайплайн
Итак, GitLab CI: что можно ещё рассказать о нём? На хабре уже есть статьи про установку, про настройку раннеров, про командное использование, про GitLab Flow. Пожалуй, не хватает описаний того, как используется GitLab CI в реальном проекте, где задействовано несколько команд. А в современном мире разработки ПО это действительно так: ведь есть (как минимум) разработчики, тестировщики, DevOps- и релиз-инженеры. С подобным разделением на команды мы работаем уже несколько лет. В этой статье я расскажу о том, как мы, используя и улучшая возможности GitLab CI, реализовали и применяем в production для коллектива из нескольких команд процессы непрерывной интеграции (CI) и отчасти доставки приложений (CD).
Наш пайплайн
Если вы сталкивались с CI-системами ранее, то понятие пайплайна вам знакомо — это последовательность выполнения стадий (здесь и далее в статье для термина stage использую перевод «стадия»), каждая из которых включает несколько задач (здесь и далее в статье job — «задача»). От момента внесения изменений в код до выката в production приложение по очереди оказывается в руках разных команд — подобному тому, как это происходит на конвейере. Отсюда и возник термин pipeline («конвейер» — один из вариантов дословного перевода). В нашем случае это выглядит так:
Краткие пояснения по используемым стадиям:
Как это используется?
Начну с рассказа о пайплайне с точки зрения его использования — то, что можно назвать user story. Тут выяснится, что на самом деле пайплайна у нас даже два: укороченный для веток и полноценный для тегов. И вот как выглядят эти последовательности:
Пайплайн и стадии в деталях
Задачи на стадии build собирают приложение. Для этого мы используем свою разработку — Open Source-утилиту dapp (о её основных возможностях читайте и смотрите в этой статье + видео), которая хорошо ускоряет инкрементальную сборку. Поэтому сборка запускается автоматически для веток с префиксами feature_ (код приложения), infra_ (код инфраструктуры) и тегов, а не только для нескольких традиционно «главных» веток (master/develop/production/release).
Обновлено 06 сентября 2019 г.: в настоящее время проект dapp переименован в werf, его код переписан на Go, а документация значительно улучшена.
Следующая стадия — staging — это набор сред для разработчиков, DevOps-инженеров и тестировщиков. Здесь объявлено несколько задач, разворачивающих приложения из веток с префиксами feature_ и infra_ в урезанных средах для быстрого тестирования новой функциональности или инфраструктурных изменений (код сборки приложения хранится в репозитории приложения).
Стадии pre-production и production доступны только для тегов. Обычно тег вешается после того, как релиз-инженеры принимают несколько merge-запросов из протестированных веток. В целом можно сказать, что мы используем GitLab Flow с тем лишь отличием, что нет автоматического развёртывания на production и потому нет отдельных веток, а используются теги.
Стадия approve — это две задачи: approve и not approve. Первая включает возможность развёртывания на production, вторая — выключает. Эти задачи существуют для того, чтобы в случае проблем на production было видно, что развёртывание происходило не просто так, а с согласия релиз-инженера. Однако суть не в лишении кого-то премии, а в том, что непосредственно развёртывание на production проводит зачастую не сам релиз-инженер, а команда DevOps. Релиз-инженер, запуская задачу approve, разрешает тем самым «нажать на кнопку» deploy to production команде DevOps. Можно сказать, что эта стадия выносит на поверхность то, что могло бы остаться в почтовой переписке или в устной форме.
Такая схема взаимодействия хорошо себя показала в работе, однако пришлось потрудиться, чтобы реализовать её. Как оказалось, GitLab CI не поддерживает из коробки некоторые нужные вещи…
Всё довольно просто и скорее всего понятно. Для каждой задачи используются следующие директивы:
Он демонстрирует возможность формата YAML определять общие блоки и подключать их в нужное место на нужном уровне. Подробнее см. в документации.
Таким образом, задачи на стадиях build, testing, staging, pre-production, которые должны быть доступны для веток infra_, feature_ и тегов, принимают следующий вид:
А задачи на стадиях approve и production, которые доступны только для тегов, имеют такой вид:
Что дальше?
Полная реализация задуманного стала возможной только благодаря нескольким патчам к GitLab и использованию артефактов задач. Подробнее об этом читайте в следующей части статьи: «GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности».
Выстраиваем процесс разработки и CI pipeline, или Как разработчику стать DevOps для QA
Вот приходишь ты работать в маленький стартап с американскими корнями…
Пока ещё маленький такой стартап, но зато с многообещающим продуктом и большими планами на завоевание рынка.
И поначалу, пока команда разработчиков совсем крошечная (до 10 человек), разработка кодовой базы ведётся в общем репозитории на GitHub Enterprise, с быстрым выделением мелких фич, бранчеванием от master, и быстрыми же циклами релизов с мержем фич-бранчей напрямую в тот же мастер. Лид команды пока что способен отследить, кто чего накоммитил, и каждый коммит не только прочитать, но и понять, правильно ли оно, или не очень. Таким образом, пулл реквесты открываются, и быстро мержатся самим же разработчиком с устного одобрения лида (или отклоняются им).
Для обеспечения целостности кодовой базы команда уповает на юнит- и интеграционные тесты, коих написана уже пара тысяч штук, и обеспечено покрытие порядка 60% (по крайней мере, для бэкэнда). Лид разработки тоже прогоняет у себя полный цикл тестов на мастере перед релизом.
Процесс выглядит как-то так:
КОММИТЫ » ТЕСТЫ ВРУЧНУЮ » РЕЛИЗ
Проходит пара месяцев. Стартап показывает жизнеспособность, инвестиции позволяют нарастить команду разработчиков до 15 человек. В основном приходят фронтэндеры, и начинают быстро расширять фасад, который видят и используют конечные пользователи. Тестируется фасад фронтерами прямо у себя на рабочих маках, они пишут какие-то кейсы на Selenium, но у лида разработки уже нет времени прогонять их перед релизом, потому что Selenium известен своей неспешностью.
И тут случаются два факапа, буквально один за другим.
Сначала один из бэкэндеров случайно делает push force в master (бедняга простыл, затемпературил, голова не соображала), после чего две недели работы всей команды приходится восстанавливать по коммитику из чудом уцелевших локальных копий — все давно уже привыкли первым делом, придя на работу, сделать себе pull.
Потом одна из крупных фич, разрабатываемая фронтерами примерно пару месяцев в отдельной ветке, и зелёная по всем UI тестам, после мержа в master резко окрашивает его в красный, и чуть-чуть не обрушивает работу всего продукта. Прошляпили breaking изменения в собственном API. И тесты никак не помогли их отловить. Бывает. Но непорядок.
Так перед стартапом встаёт в полный рост вопрос о заведении команды QA, да и вообще, регламентов работы с фич-бранчами и общей методикой разработки, вкупе с дисциплиной. А также становится очевидно, что код перед пулл реквестом должен ревьюить не только лид разработки (у него и без того дел полно), но и другие коллеги. Нормальная проблема роста, в общем-то.
Вот мы и пришли к пункту «Дано:».
Сначала настроим в амазоновском облаке головной инстанс TC (+ два бесплатных агента), повесим их слушать коммиты в гихабовском репозитории (на каждый PR гитхаб делает виртуальный HEAD — слушать изменения ну очень просто), и автоматические сборки с прогоном юнит-тестов пойдут практически сами собой. Как коммитнет кто-нибудь, так через пять минут и сборка в очередь становится. Удобно.
КОММИТЫ » ПУЛЛ РЕКВЕСТ » БИЛД + ТЕСТЫ » РЕЛИЗ
У гитхаба в то время был ещё весьма неприятный интерфейс для просмотра пулл реквестов, и комментарии там оставлять тоже было не айс. Больно уж длинную портянку экранов надо было проматывать. То есть, отобрать у членов команды право на мерж было можно, но обеспечить нормальное ревью кода без сторонних сервисов — никак. Вдобавок, хотелось ещё получить заодно вменяемую интеграцию с Джирой, чтобы фичи к таскам, а таски к фичам сами прикреплялись.
По счастью, у Atlassian есть подобное решение, называется оно BitBucket Server, а в то время ещё звалось Stash. Как раз позволяет делать всю такую интеграционную магию с другими атлассиановскими продуктами, и очень удобно для ревью кода. Решили смигрировать.
Вот только этот чудесный продукт, в отличие от гитхаба, виртуальных HEAD на каждый PR не создаёт, и тимсити после миграции стало нечего слушать. С post-commit hooks дело тоже не пошло по причине отсутствия у всех времени хорошенько с ними разобраться.
Забегая вперёд — когда время появилось, мне удалось переписать 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, пробуем повторить и улучшить этот алгоритм. Для начала, заводим в амазоновском облаке здоровущий виртуальный инстанс с сотнями дискового пространства (а в exploded виде наше приложение довольно жирное) и гигабайтами оперативы.
В билды на тимсити, которые собирают WAR, добавляем дополнительный шаг сборки, который по SSH перекидывает их на тот жирный амазоновский инстанс, и также по SSH дёргает скрипт на bash, делающий следующее:
КОММИТЫ » ПУЛЛ РЕКВЕСТ » РЕВЬЮ КОДА || БИЛДЫ + ТЕСТЫ » РАЗВЁРТЫВАНИЕ QA ЭКЗЕМПЛЯРА » QA ИНЖЕНЕРЫ ЕГО МУЧАЮТ » РЕЛИЗ
Так, минуточку, а наши любимые QA инженеры что, вручную будут, что ли, в облако по SSH ходить, томкэт из командной строки запускать? Как-то не супер. Можно бы автоматически его поднимать, но неудобно, так как фич-бранчей уже под 60, и память даже на самом жирном инстансе всё-таки не резиновая. Тормозить будет.
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 поддиректорий много сотен раз, отчего консолька работала небыстро.
Проходит ещё совсем немного времени, и набор тесткейсов разрастается до такой степени, что 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, и возможностью иногда позаниматься подобного рода ненормальным программированием. Или процессами порулить. Можно всё сразу.
Только в Москву переехать не смогу. А вот поработать удалённо — с удовольствием.