- PVSM.RU - https://www.pvsm.ru -
Мы давно ничего не рассказывали об автоматизации разработки. Поэтому в этот раз расскажу о том, как мы сократили время релиза с трех дней до одного и убрали из процесса участие человека с его возможными ошибками.
Рассказывать о долгом и тернистом пути всегда непросто. Однако за последние годы инфраструктура разработки Яндекс.Денег сделала большой шаг в сторону автоматизации самого важного для нас процесса — релиза, о чем просто грех не рассказать. Фактически получилось полноценное решение Continuous Integration и Continuous Delivery на базе связки Bitbucket [1], Jenkins [2] и Jira [3].
В разработке Яндекс.Денег процессы Continuous Integration и Continuous Delivery (CI/CD) применяются сравнительно давно, что позволяет доставлять на продакшен изменения небольшими порциями и поддерживать высокий релизный темп.
По сути, это просто подход к разработке программного обеспечения:
Continuous Integration подразумевает частые автоматизированные сборки проекта с целью быстрого выявления интеграционных проблем. У вас всегда будет актуальная и готовая к тестам версия продукта.
Вообще, сложный процесс для релизов нужен не всем и не всегда. Например, стартапам он вряд ли пригодится из-за множества ненужных им этапов — им проще собраться и все обсудить в узком кругу. Но если в вашей структуре так не получится, а недостаточно быстрый процесс релиза грозит сразу десятком критичных изменений на продакшене за раз — стоит задуматься об автоматизации разработки.
Кроме того, автоматизированный процесс может значительно снизить трудозатраты команд. Без автоматизации CI/CD вы рискуете погрязнуть в человеческих ошибках и множестве ручных операций. Например, мы используем популярный таск-трекер Jira, который отлично приспособлен для ведения задач и интеграции с репозиторием Bitbucket, но с его помощью сложно автоматизировать релизный цикл. Поэтому наш процесс разработки до Jenkins выглядел так:
Релизный процесс Яндекс.Денег.
Разработка задач и исправление ошибок выполнялись по Successful Git Branching Model [4], а тестирование происходило в Feature Branch. Весь ад ручных операций происходил как раз при подготовке Release Candidate к приемочным тестам. Разработчику необходимо было выполнить следующие шаги:
Срезать релизную ветку с dev для подготовки очередного релиза;
Собрать код будущего Release Candidate;
По комментариям к коммитам найти все вошедшие в релиз задачи Jira и проставить для них связи с релизом в Jira;
Создать тикеты в Jira на нагрузочное и приемочное тестирование;
Отработка этих пяти пунктов и согласование релиза с ответственными лицами занимали у нас от 1 дня до недели. Так не могло продолжаться вечно, поэтому вскоре появилась автоматизация этого процесса на Jenkins.
Четыре года назад мы осознали, что пора что-то менять: размер системы, число сотрудников и изменений на боевых серверах росли на глазах. С 2014 года число релизов выросло в 3 раза — с 45 до 150 штук в месяц.
На тот момент мы бежали со следующим багажом:
Платежный сервис состоял из несколько монолитов;
Не было обязательных автотестов;
После одобрения релиза на развертывание в продакшен, к делу приступала группа инженеров эксплуатации. Так как компоненты платежного сервиса у нас работают в кластерных конфигурациях, развертывание обновления для каждого компонента выливалось в десяток итераций. Только развертыванием релизов на нескольких средах ежедневно занималось 5 человек.
Со всем этим можно жить когда релизов немного и штат такой, что можно собраться в переговорке и быстро все обсудить-согласовать. Но в какой-то момент это стало серьезной проблемой и тормозом в возрастающем темпе релизов. Мы начали долгий путь переосмысления процесса разработки, который можно кратко изобразить так:
В сущности, даже полностью автоматизированный процесс подготовки релизов не позволит серьезно повысить темп, так как мы упремся в производительность отдела эксплуатации — в один момент времени на продакшене у нас принято разворачивать только одно обновление. Это сделано для простоты диагностики возможных ошибок из-за релиза.
Получается, что автоматизировать нужно не только формирование и тестирование релиза для передачи в эксплуатацию, но и само развертывание. То есть улучшать процессы со стороны разработки и эксплуатации. Первая попытка автоматизировать эксплуатацию как раз описана в одной из прошлых статей [5] — опыт был полезный, а сделанные ошибки позволили довести до ума техническое решение. Сейчас оно внедряется для большинства компонент и в будущем, скорее всего, станет поводом для еще одной публикации.
Раньше мы использовали для сборки кода JetBrains Teamcity, пока рост числа разработчиков и сервисов не показал экономическую нецелесообразность такого решения для Яндекс.Денег. Поэтому обратились к OpenSource и в качестве «второй версии» сделали нечто сопоставимое на базе открытого и бесплатного Jenkins [2].
Задачи под автоматизацию получились следующие:
Динамическая генерация задач Jenkins. Позволяет создавать и поддерживать в актуальном состоянии задачи на уровне компонент и веток внутри них;
Подготовка к тестированию:
Срезание релизной ветки. По кнопке в панели Jenkins в Bitbucket создается новая релизная ветка из ветки dev, в которую уже добавлены все изменения от разработчиков;
Сборка для тестирования. Отдельный скрипт собирает пакет для тестирования. Jenkins создает RL-тикет в Jira и привязывает к нему подходящие по описанию задачи разработчиков;
Развертывание на продакшене:
Сборка для развертывания на продакшене. По команде разработчика Jenkins добавляет код релизной ветки в master, а из master — в dev (чтобы у разработчиков была актуальная версия), после чего формирует пакет для развертывания;
Разберем более детально каждый из этапов.
При добавлении нового файла с описанием задачи в репозиторий компонента, Jenkins автоматически создаст для него задачу на сборку. За это отвечает наш механизм SyncBitbucket, рабочий пример которого лежит на Github [6].
Взаимодействие с Bitbucket происходит через API, а с Jenkins – при помощи плагина Job DSL.
Шаги динамической генерации выглядят следующим образом:
Вручную создаем задачу на синхронизацию Bitbucket (файл synchronizeBitbucket.groovy [7]). Задача пробегается по нашим проектам Bitbucket и генерирует задачи по синхронизации отдельных проектов. Проект — это набор репозиториев, в которых у нас лежат схожие по назначению сущности: сервисы, библиотеки, gradle-плагины. Запуск этой задачи происходит по расписанию, потому что мы не можем отследить появление новых проектов.
Задача по синхронизации проекта (файл synchronizeProject.groovy [8]) запускается также по расписанию, ищет репозитории и создает задачу на синхронизацию каждого из них.
Скрипты для генерации задач написаны в формате JobDSLplugin [10]. Для того чтобы разработчику добавить новый тип задачи в 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 [11], причем под каждую ветку можно создавать собственный набор сборок.
Для автоматизации релизного цикла в Jenkins потребовалось создать несколько скриптов, которые выглядят как «кнопки запуска» для каждого этапа релизного цикла:
createReleaseBranch — срезание релизной ветки;
Скрипты создаются как раз на этапе динамической генерации задач под каждую компоненту. То есть старт того или иного этапа релизного цикла означает запуск конкретного скрипта готовящейся к релизу компоненты.
После запуска createReleaseBranch из dev-ветки в Bitbucket формируется релизная ветка, а в Jenkins — отдельная папка. Имя для папки выбирается автоматически по схеме «release_версия».
Далее программисту нужно перейти в новую папку и запустить там отдельный скрипт prepareReleaseForTesting, который соберет пакет обновления и разошлет уведомления о сборке релиза всем заинтересованным. Кроме того, в Jira создаются задачи приемочного и нагрузочного тестирования.
Сразу после отправки нового релиза на тестирование нужно рассказать об этом всем заинтересованным лицам. Так менеджер продукта сможет заранее договориться с маркетингом и PR о публичном анонсе, руководители направлений будут в курсе происходящего, а техподдержка сможет проработать ответы на возможные вопросы пользователей.
RL — это тикет с описанием релиза, содержащий ссылки на включенные в этот релиз задачи;
INT — задача на приемочное тестирование релиза;
Чтобы все это случилось, важно было подружить Jenkins с Jira. Тут весь вопрос упирался в организационные моменты, так как часто встречаются такие описания разработчиков к изменениям кода в Bitbucket:
«Кнопка иногда вылезала за границы экрана — поправил»;
«Добавил полоску сверху»;
С такими описаниями сложно понять, к какой задаче в Jira относятся изменения кода. Поэтому с разработчиками договорились о том, что в первую очередь комментарий к изменениям в Bitbucket будет содержать номер задачи (например «BACKEND-186 Исправил отправку email»). Этот номер был и раньше, так как он нужен разработчику для анализа истории в Git и Bitbucket, но теперь он стал критичен.
Если же в силу вступит человеческий фактор и кто-то об этом забудет — Jenkins добавит комментарий «как есть» к общему письму о релизе, и тогда привязку задачи к RL в Jira выполнит, например, руководитель проекта.
За процесс развертывания отвечают две следующих задачи в Jenkins:
mergeToMaster — слияние протестированного кода в релизной ветке с master;
После тестирования ответственный переходит в папку релиза в Jenkins и запускает скрипт mergeToMaster, который выполняет в Bitbucket следующее: merge release->master и master->dev.
Остается финальный этап — сборка для продакшена. За это отвечает скрипт prepareReleaseForProduction, который собирает .DEB пакет, ставит задачу в Jira отделу эксплуатации и оповещает ответственного о том, что релиз готов и можно выкладывать.
В качестве развития проекта автоматизации релизного цикла сейчас выстраиваем связь с интеграционными тестами в отделе тестирования, чтобы в дальнейшем сделать этап тестирования полностью автоматическим. Для того чтобы разработчики чаще выпускали релизы, мы научили Jenkins раз в сутки автоматически рассылать уведомления о новых задачах в ветках dev. Кроме того, есть надежда, что это подготовит разработчиков, тестировщиков и системных администраторов к будущему автоматическому срезанию релизов.
Любопытно узнать о вашем подходе к релизам — как боретесь с человеческими ошибками, поддерживаете ли какой-то стабильный темп выпуска?
Автор: denislykov
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/continuous-integration/255554
Ссылки в тексте:
[1] Bitbucket: https://bitbucket.org
[2] Jenkins: https://jenkins.io
[3] Jira: https://ru.atlassian.com/software/jira
[4] Successful Git Branching Model: http://nvie.com/posts/a-successful-git-branching-model
[5] одной из прошлых статей: https://habrahabr.ru/post/322302/
[6] Github: https://gist.github.com/f0y/bf7ff6570699cc368613c0dd60cd9c69
[7] synchronizeBitbucket.groovy: https://gist.github.com/f0y/bf7ff6570699cc368613c0dd60cd9c69#file-synchronizebitbucket-groovy
[8] synchronizeProject.groovy: https://gist.github.com/f0y/bf7ff6570699cc368613c0dd60cd9c69#file-synchronizeproject-groovy
[9] synchronizeRepo.groovy: https://gist.github.com/f0y/bf7ff6570699cc368613c0dd60cd9c69#file-synchronizerepo-groovy
[10] JobDSLplugin: https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin
[11] Travis CI: https://travis-ci.org/
[12] Источник: https://habrahabr.ru/post/328092/
Нажмите здесь для печати.