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. Кроме того, есть надежда, что это подготовит разработчиков, тестировщиков и системных администраторов к будущему автоматическому срезанию релизов.


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

Поделиться с друзьями
-->

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


  1. SyrexS
    18.05.2017 12:42

    Подскажите, а с Jira общаетесь через REST API?


    1. Silron
      18.05.2017 14:05

      Используем jira-rest-java-client и Jira без кастомных плагинов.
      Функциональности пока хватает.


  1. IvanGaas
    18.05.2017 14:30

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

    А можно подробнее? В чем экономическая нецелесообразность? Цена лицензии?


    1. denislykov
      18.05.2017 15:34
      +1

      Арифметика тут простая. Если ознакомиться с
      ценами на Teamcity
      то становится понятно, что есть ограничение по числу агентов, которые выполняют сборку и тестирование, и по тестовым конфигурациям. Когда стало расти число разработчиков, сервисов и изменений в сервисах задачи на проверку стали выстраиваться в очередь. Сначала мы пошли по пути увеличения числа серверов, агентов и лицензий, но так долго продолжаться не могло. Конфигураций и агентов не хватало, люди стали откладывать автотесты «на потом», а об автоматическом создании конфигураций и запуске проверки по коммиту и речи быть не могло. Сейчас в 15 командах работает 80 бекенд и фронтенд разработчиков, которые вносят изменения в 150 репозиториев. Умножьте все это на feature(s), dev, release(s) и master сборки, прибавьте к автотестам задачи по выпуску релизов и бесплатное решение становится единственным вариантом дальнейшего роста разработки.


      1. HSerg
        19.05.2017 10:42

        А сколько сейчас на таких размеров команду серверов (или виртуалок в своём облаке?) с агентами? И используется ли какой-нибудь autoscaling для этой инфраструктуры?


        1. denislykov
          19.05.2017 12:16

          Сейчас у нас под Jenkins выделено 4 железных сервера. Начинали с одного сервера и добавляли новые по мере необходимости. Это происходит не очень часто, поэтому autoscaling не используется.


  1. amdmax
    19.05.2017 10:42
    +1

    У нас связка GitLab + Jenkins (gitlab plugin) выполняет аналогичные задачи.
    Из плюсов — настройка хуков сборки на пуш, мердж-реквест, тег и тд, с сборка именно той ветки, в которую запушили, без необходимости собирать всё подряд. Релиз выкатывается при мерже dev'а с master'ом.
    Гитлаб имеет поддержку из коробки популярных таск и баг-трекеров.
    Из минусов — требовательность к ресурсам самого гитлаба и некоторые нюансы в работе хуков


    1. denislykov
      19.05.2017 12:32

      Мы не собираем и не тестируем «все подряд». В Bitbucket настроены хуки, которые срабатывают на пуши и пул-реквесты, и уведомляют Jenkins об изменениях.
      А вот автоматической раскатки релизов на бою у нас пока нет, но мы готовимся к этому морально и технически))