Феншуйная автоматизация CI & CD с помощью Jenkins и Jira

в 9:05, , рубрики: bitbucket, continuous delivery, continuous integration, gradle, Jenkins CI, jira, release cycle, Блог компании Яндекс.Деньги, Разработка под e-commerce, системы сборки

image alt text

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

Рассказывать о долгом и тернистом пути всегда непросто. Однако за последние годы инфраструктура разработки Яндекс.Денег сделала большой шаг в сторону автоматизации самого важного для нас процесса — релиза, о чем просто грех не рассказать. Фактически получилось полноценное решение Continuous Integration и Continuous Delivery на базе связки Bitbucket, Jenkins и Jira.

Кому все это нужно

В разработке Яндекс.Денег процессы Continuous Integration и Continuous Delivery (CI/CD) применяются сравнительно давно, что позволяет доставлять на продакшен изменения небольшими порциями и поддерживать высокий релизный темп.

Немного об идеях Continuous Delivery & Continuous Integration.

По сути, это просто подход к разработке программного обеспечения:

  • Continuous Integration подразумевает частые автоматизированные сборки проекта с целью быстрого выявления интеграционных проблем. У вас всегда будет актуальная и готовая к тестам версия продукта.

  • Continuous Delivery предполагает частую доставку обновлений на «боевую» систему. Продукт поддерживается в актуальной версии, а любые ошибки при обновлении проще отследить, так как при каждом релизе объем изменений невелик.

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

Кроме того, автоматизированный процесс может значительно снизить трудозатраты команд. Без автоматизации CI/CD вы рискуете погрязнуть в человеческих ошибках и множестве ручных операций. Например, мы используем популярный таск-трекер Jira, который отлично приспособлен для ведения задач и интеграции с репозиторием Bitbucket, но с его помощью сложно автоматизировать релизный цикл. Поэтому наш процесс разработки до Jenkins выглядел так:

image alt text

Релизный процесс Яндекс.Денег.

Разработка задач и исправление ошибок выполнялись по Successful Git Branching Model, а тестирование происходило в Feature Branch. Весь ад ручных операций происходил как раз при подготовке Release Candidate к приемочным тестам. Разработчику необходимо было выполнить следующие шаги:

  1. Срезать релизную ветку с dev для подготовки очередного релиза;

  2. Собрать код будущего Release Candidate;

  3. По комментариям к коммитам найти все вошедшие в релиз задачи Jira и проставить для них связи с релизом в Jira;

  4. Создать тикеты в Jira на нагрузочное и приемочное тестирование;

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

Отработка этих пяти пунктов и согласование релиза с ответственными лицами занимали у нас от 1 дня до недели. Так не могло продолжаться вечно, поэтому вскоре появилась автоматизация этого процесса на Jenkins.

Что будем автоматизировать

Четыре года назад мы осознали, что пора что-то менять: размер системы, число сотрудников и изменений на боевых серверах росли на глазах. С 2014 года число релизов выросло в 3 раза — с 45 до 150 штук в месяц.

image alt text

На тот момент мы бежали со следующим багажом:

  • Платежный сервис состоял из несколько монолитов;

  • Не было обязательных автотестов;

  • Не было автоматизированного ревью кода и развертывания приложений.

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

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

image alt text

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

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

Почему именно Jenkins

Раньше мы использовали для сборки кода JetBrains Teamcity, пока рост числа разработчиков и сервисов не показал экономическую нецелесообразность такого решения для Яндекс.Денег. Поэтому обратились к OpenSource и в качестве «второй версии» сделали нечто сопоставимое на базе открытого и бесплатного Jenkins.

Задачи под автоматизацию получились следующие:

  1. Динамическая генерация задач Jenkins. Позволяет создавать и поддерживать в актуальном состоянии задачи на уровне компонент и веток внутри них;

  2. Подготовка к тестированию:

    1. Срезание релизной ветки. По кнопке в панели Jenkins в Bitbucket создается новая релизная ветка из ветки dev, в которую уже добавлены все изменения от разработчиков;

    2. Сборка для тестирования. Отдельный скрипт собирает пакет для тестирования. Jenkins создает RL-тикет в Jira и привязывает к нему подходящие по описанию задачи разработчиков;

    3. Скрипт формирует задачи на приемочное и интеграционное тестирование. Jenkins отправляет на почту письмо с информацией о релизе всем наблюдателям вошедших в релиз задач;

  3. Развертывание на продакшене:

    1. Сборка для развертывания на продакшене. По команде разработчика Jenkins добавляет код релизной ветки в master, а из master — в dev (чтобы у разработчиков была актуальная версия), после чего формирует пакет для развертывания;

    2. После сборки скрипт Jenkins создает в Jira задачу на развертывание релиза для отдела эксплуатации и отправляет ответственному на почту — можно выкладывать на продакшен.

Разберем более детально каждый из этапов.

Динамическая генерация задач Jenkins

При добавлении нового файла с описанием задачи в репозиторий компонента, Jenkins автоматически создаст для него задачу на сборку. За это отвечает наш механизм SyncBitbucket, рабочий пример которого лежит на Github.

Взаимодействие с Bitbucket происходит через API, а с Jenkins – при помощи плагина Job DSL.

Шаги динамической генерации выглядят следующим образом:

  1. Вручную создаем задачу на синхронизацию Bitbucket (файл synchronizeBitbucket.groovy). Задача пробегается по нашим проектам Bitbucket и генерирует задачи по синхронизации отдельных проектов. Проект — это набор репозиториев, в которых у нас лежат схожие по назначению сущности: сервисы, библиотеки, gradle-плагины. Запуск этой задачи происходит по расписанию, потому что мы не можем отследить появление новых проектов.

  2. Задача по синхронизации проекта (файл synchronizeProject.groovy) запускается также по расписанию, ищет репозитории и создает задачу на синхронизацию каждого из них.

  3. Задача по синхронизации репозитория (файл synchronizeRepo.groovy) является главной частью нашего механизма. Её цель состоит в том, чтобы обойти все бранчи, найти в них файлы скриптов для генерации задачи и запустить их на выполнение. Запуск задачи происходит по коммиту в репозиторий.

Скрипты для генерации задач написаны в формате JobDSLplugin. Для того чтобы разработчику добавить новый тип задачи в Jenkins, достаточно поместить файл с префиксом jenkins_ в папку Jenkins корневой директории репозитория. В скрипте создания задачи можно использовать данные, поставляемые механизмом синхронизации репозитория, к примеру: имя бранча, адрес репозитория и адрес каталога в Jenkins.

Пример описания задачи на срезание релизного бранча:

folder(jobParams.jenkinsDirectory)
if (jobParams.gitBranch != "dev") {
    return
}

job(jobParams.jenkinsDirectory + "/createReleaseBranch") {
    jdk('jdk1.8.0')
    scm {
        git {
            remote {
                url(jobParams.gitRepoUrl)
                refspec('refs/remotes/origin/*')
                branches("**/$jobParams.gitBranch")
            }
        }
    }

    steps {
        gradle {
            makeExecutable(true)
            description('Срезание релизного бранча')
            tasks(':createReleaseBranch')
        }
    }
}

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

Подготовка к тестированию

Для автоматизации релизного цикла в Jenkins потребовалось создать несколько скриптов, которые выглядят как «кнопки запуска» для каждого этапа релизного цикла:

  • createReleaseBranch — срезание релизной ветки;

  • prepareReleaseForTesting — сборка тестового релиза, постановка задачи тестировщикам.

Скрипты создаются как раз на этапе динамической генерации задач под каждую компоненту. То есть старт того или иного этапа релизного цикла означает запуск конкретного скрипта готовящейся к релизу компоненты.

После запуска createReleaseBranch из dev-ветки в Bitbucket формируется релизная ветка, а в Jenkins — отдельная папка. Имя для папки выбирается автоматически по схеме «release_версия».

image alt text

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

Сразу после отправки нового релиза на тестирование нужно рассказать об этом всем заинтересованным лицам. Так менеджер продукта сможет заранее договориться с маркетингом и PR о публичном анонсе, руководители направлений будут в курсе происходящего, а техподдержка сможет проработать ответы на возможные вопросы пользователей.

image alt text

Небольшое отступление про наши типы тикетов в Jira.

  • RL — это тикет с описанием релиза, содержащий ссылки на включенные в этот релиз задачи;

  • INT — задача на приемочное тестирование релиза;

  • LOAD — задача на нагрузочное тестирование.

Чтобы все это случилось, важно было подружить Jenkins с Jira. Тут весь вопрос упирался в организационные моменты, так как часто встречаются такие описания разработчиков к изменениям кода в Bitbucket:

  • «Кнопка иногда вылезала за границы экрана — поправил»;

  • «Добавил полоску сверху»;

  • «Исправил проверку на числа, больше не должно ругаться».

С такими описаниями сложно понять, к какой задаче в Jira относятся изменения кода. Поэтому с разработчиками договорились о том, что в первую очередь комментарий к изменениям в Bitbucket будет содержать номер задачи (например «BACKEND-186 Исправил отправку email»). Этот номер был и раньше, так как он нужен разработчику для анализа истории в Git и Bitbucket, но теперь он стал критичен.

Если же в силу вступит человеческий фактор и кто-то об этом забудет — Jenkins добавит комментарий «как есть» к общему письму о релизе, и тогда привязку задачи к RL в Jira выполнит, например, руководитель проекта.

Развертывание

За процесс развертывания отвечают две следующих задачи в Jenkins:

  • mergeToMaster — слияние протестированного кода в релизной ветке с master;

  • prepareReleaseForProduction — сборка релиза из master и постановка задачи на развертывание команде эксплуатации.

После тестирования ответственный переходит в папку релиза в Jenkins и запускает скрипт mergeToMaster, который выполняет в Bitbucket следующее: merge release->master и master->dev.

image alt text

Остается финальный этап — сборка для продакшена. За это отвечает скрипт prepareReleaseForProduction, который собирает .DEB пакет, ставит задачу в Jira отделу эксплуатации и оповещает ответственного о том, что релиз готов и можно выкладывать.

В качестве развития проекта автоматизации релизного цикла сейчас выстраиваем связь с интеграционными тестами в отделе тестирования, чтобы в дальнейшем сделать этап тестирования полностью автоматическим. Для того чтобы разработчики чаще выпускали релизы, мы научили Jenkins раз в сутки автоматически рассылать уведомления о новых задачах в ветках dev. Кроме того, есть надежда, что это подготовит разработчиков, тестировщиков и системных администраторов к будущему автоматическому срезанию релизов.

Любопытно узнать о вашем подходе к релизам — как боретесь с человеческими ошибками, поддерживаете ли какой-то стабильный темп выпуска?

Автор: denislykov

Источник

Поделиться

* - обязательные к заполнению поля