Хотел бы поделиться небольшим опытом по автоматизации и стандартизации процесса разработки. Статья не претендует быть истиной, но я надеюсь будет интересным чтивом для конца рабочей недели.
Для начала немножко расскажу с чего все начиналось:
Самой большой проблемой было отсутствие, каких-либо механизмов для контроля согласованности по функционалу между компонентами системы. Не было ни одного критерия, основываясь на котором, можно было бы смело сказать, что взятые в какой-то момент времени две компоненты системы согласованы между собой.
Согласованными по функционалу мы называем компоненты которые общаются между собой на одном языке одной версии протокола. В нашем случае версия протокола так же тесно переплетена с бизнес логикой. Точнее сказать ни какая новая бизнес логика не может быть введена без изменения или расширения протокола.
В ходе долгих дебатов о том “как правильно вести svn” был выбран вариант фиксирования тегов и сформулирован ряд требований/правил которому следует придерживаться:
Trunk
Tag N
Оговорка: в случае появления новой компоненты системы, которую уже можно начинать тестировать, но которая явно не дотягивает до функционала последнего тега, для того что бы попасть в общий процесс тестирования, такую компоненту можно брать из транка на ответственность разработчика и общем понимании, что это абсолютно исключительная и временная ситуация, пока не появится новый тег.
Наши теги, не такие уж и теги. Но они ближе по своей сути именно в понятию “тег” нежели к понятию “бранч”.
На обсуждении был и другой формат ведения SVN — вести основную разработку в бранчах.Т.е. если у нас возникает мажорное изменение, то мы создаем отдельный бранч, реализуем там это изменение и мержим его в транк. В этом случае транк всегда должен быть стабильным, что бы в любой момент времени можно было взять все компоненты системы и они были как минимум согласованы по функционалу, как максимум готовы для продакшена. В нашем случае это выглядит, не жизнеспособным. Так как порой одно мажорное изменение может вылезти в момент реализации другого. Тогда пришлось бы создавать на него отдельный бранч и переключатся на него. Проблемы начались бы в момент когда нужно было бы смержить две ветки в транк.
У нас весьма долгий цикл разработки, и еще не обкатан процесс перехода с версии на версию. Поэтому отдавая в публичное тестирование наш продукт, мы хотели бы иметь возможность делать хот фиксы. Но не имея тега(ветки) на каждую финальную версию, сделать это было бы не возможно. Так же мы не имеем возможности говорить, что “мол это будет исправлено в следующей версии”.
Дальше в статье я буду использовать термин “тег” именно в том значении которое я описал выше.
От тегов не было бы пользы не будь в нем все компоненты согласованы по функционалу. Узким местом в это случае является момент времени когда мысрезаем скальп создаем тег. Как правило на одной из планерок мы принимаем решение о том, что в течении ближайшей недели мы должны подготовить транк для создание тега. То есть каждый из проектов должен прийти в состояние когда он согласован по функционалу со всеми компонентами с которыми он непосредственно общается. После этого мы создаем тег. Который уходит в тестирование. Тег мы именуем в специальном формате MAJOR_NUM.MINOR_NUM.
Следующим важным шагом был ввод критерия основываясь на котором можно было бы смело заявить, что две компоненты согласованы между собой. Мы решили, что этим критерием будет версия. При этом версия сама по себе должна нести информацию основываясь на которой можно понять, что две компоненты согласованы.
Формат версии: MAJOR_NUM.MINOR_NUM.REVISION. Т.е. первая часть версии является так же номером тега. А мы знаем, что у нас все компоненты в теге согласованны. Указание сразу двух чисел в названии тега, позволяет делать нормальный переход между мажорными версиями, т.е. вместо 1.1.x, 1.2.x, 1.3.x, 2.4.x, у нас будет 1.1.x, 1.2.x, 1.3.x, 2.1.x. Номер ревизии остается и используется как обычно. Это номер ревизии каждой из отдельных компонент, а не просто последняя ревизия тега.
Следующим шагом был процесс внедрения CI (Continuous Integration). Выбор пал на, думаю многим известную CI систему, Jenkins. Выбор был между Cruise control, с которым я имею не самый приятный опыт работы, Team City который является платным для нормального его использования и непосредственно Jenkins, который бесплатен, широко распространен и хорошо документирован. Фактор бесплатности был немаловажен, так как страшно просить у начальства деньги за что то, что не факт что приживется.
Компоненты нашей системы весьма разнообразные и на текущий момент используют 3 платформы для сборки: windows, linux и mac os. Получается один мастер и 2 slave (не хочу я больше повторять слово “раб”). Вопрос где делать мастера не возникал, конечно же на Linux. Была развернута начальная конфигурация из одного мастера (Linux) и одного slave (Windows). Начался процесс автоматизации сборки всех наших компонент. В ходе работы, уже появился тег 1.1 и число задач в Jenkins перевалило за 10. И тут начались проблемы. В один прекрасный день Jenkins упал. Мы его подняли, он у пал снова. И еще раз. И продолжал падать, независимо от того, как мы его запускаем через Tomcat или как службу. Независимо от JAVA машины, open JDK или SUN java. Независимо от текущей версии. Вернее падал не сам Jenkins, а JAVA машина которую он рушил. Я даже завел отдельный баг в их багтрекете (JENKINS-16199, правда на это все и заглохло). Потратив полторы недели на всякие попытки понять что происходит или хотя бы почему, было принято решение перенести мастера на Windows систему. И о чудо, в той же самой конфигурации все стало работать стабильно. Со времени число задач возросло в два раза, но все продолжает работать. В итоге конечная структура выглядит так:
Мастер на Windows, два Slave, на Linux и MacOS, взаимодействие по SSH через публичные ключи.
Стандартную поставку Jenkins мы расширили следующими плагинами:
Copy Artifact Plugin — Используем для того что бы артефакт одной сборки использовать в другой. Тут есть один простой момент, на котором можно споткнуться.
Если вы в одной из задача сохраняете артефакт вот так
А в другой, используя данный плагин, загружаете этот арт
То вы как минимум должны указать путь до него так же как в первом случае, а еще не помешает установить опцию “Flatten directories” что бы отбросить лишние пути и скопировать только сам арт в то место куда вам нужно.
Jenkins description setter plugin — Позволяет указывать некоторую информацию о каждой сборке в поле Build Description. Те задачи которые имеют версию, выводят ее туда.
Extra Columns Plugin — позволяет добавить поле Build Description на View.
Publish Over SSH — Отличный плагин для заливки файлов и выполнения команд на удаленной машине. Правда пока его не используем в силу того, что автотестирование еще не налажено. Недостатком данного плагина является то, что требуется предварительная конфигурации всех SSH соединений и в каждой задаче явно указывается какое соединение использовать на этапе ее создания.
Python Plugin — часть шагов сборки написано на Python, потому что так проще. В том числе есть отдельная задача которая делает почти все тоже самое что Publish Over SSH но только позволяя на лету конфигурировать параметры доступа к удаленной машине.
XCode integration — Используется для сборки проекта на MacOS. Нам пришлось попотеть для того, что бы уметь делать сборки как для разных версий iOS. В итоге на билд машине было разрешено использовать команду sudo xcode-select -switch без ввода пароля.
Я уже упоминал необходимость удаленной заливки и выполнения команд, которую почти прекрасно решает плагин Publish Over SSH. Но который не позволяет непосредственно для каждого процесса сборки указать нужный таргет.
У нас есть свой инсталятор, несколько тестировшиков и несколько машин. Многим лень тащить этот файл себе и что-то делать с ним что бы установить продукт. Для этих целей была создана сцепиальная задача “Remote deploy”.
И так, что она умеет:
Забрать инсталятор из тега который укажет пользователь
Выполнить его установку на той машине которую укажет пользователь
Вот как это выглядит
Данная задача является параметризированной.
Параметры
TAG, HOST, USER, DEPLOY_DIR, OPENSSL_DIR, тут никакой магии, обычные строки.
JOBS_NUMBER — “Build selector for Copy Atrifact”, позволяет указать какую версию сборки брать, последнюю удачную или какой-то определенный номер или что-то еще.
PASSWORD это параметр типа “Password Parameter” — закрывает пароль звездочками.
Вот таким образом забирается нужная сборка из нужной задачи
Виды в Jenkins это средство кастомизации отображения проектов. Мы убрали одну колонку и добавили “Build Description” для наших видов Trunk, Tag_X. Мы хотели что бы на дефолтном виде Jenkins было так же. Но Jenkins не позволяет делать такие манипуляции с его главным видом. Вместо этого можно создать свой вид (Default), добавить в него все проекты по маске .* и сделать этот вид, видом по умолчанию.
У нас есть специальный скрипт для создание версии и всех ее возможных вариаций. В начале он появлялся в каждом из проектов в котором нужен был подобный функционал. Спустя время, мы его положили в одном месте и везде где он нужен, тянем его как внешнюю зависимость. И тогда случился Epic fail, когда его случайно поправили в одном из проектов, где он был зависимостью. В Jenkins выстроилось в очередь 15 проектов на пересборку, холостую пересборку.
Первое что пришло в голову, это заблокировать файл для изменений. На файл повесили свойство needs-lock и от специального build юзера заблокировали его (svn lock).
Но есть решение лучше. Гораздо лучше. Jenkins позволяет ввести фильтрацию перед запуском сборки инициированной изменением репозитория. Это находится в разделе “Управление исходным кодом -> Расширенные”. Нас интересует параметр “Excluded Regions” в котором можно указать изменения в каких файлах и директориях стоит игнорировать.
На этом мое повествование заканчивается. В данный момент я уже покинул то место работы где был получен сей опыт и написана данная статья, поэтому на возможно возникшие вопросы о деталях и настройках навряд ли смогу ответить.
Спасибо за внимание.
FrimInc, zzapp за вычитку статьи
В начале было слово
Для начала немножко расскажу с чего все начиналось:
- Репозитарий в котором была только одна основная ветка.
- 5 проектов, которые должны взаимодействовать в единой экосистеме. И еще несколько на подходе.
- Постоянно дополняющиеся требования, приводящие к необратимым изменениям в протоколе общения между всеми компонентами (не самая лучшая ситуация, но так уже было).
- Каждый из проектов имел свой формат версии. Одни основывались на ревизии, другие на дате, третье и на том и на том.
- Отсутствие какой-либо согласованности между всеми компонентами (была только на словах).
- Отсутствие какой-либо автоматизации процессов (что-то все-таки было, но не густо).
- Отсутствие единого хранилища всех дистрибутивов (одни хранились в svn, другие в Track Studio, третье пересобирались каждый раз из исходников (было забавно смотреть как тестировщики выгружали из svn исходники приложения и делали make clean all что бы получить новый бинарник для тестирования)).
В начале была структуризация
Самой большой проблемой было отсутствие, каких-либо механизмов для контроля согласованности по функционалу между компонентами системы. Не было ни одного критерия, основываясь на котором, можно было бы смело сказать, что взятые в какой-то момент времени две компоненты системы согласованы между собой.
Согласованными по функционалу мы называем компоненты которые общаются между собой на одном языке одной версии протокола. В нашем случае версия протокола так же тесно переплетена с бизнес логикой. Точнее сказать ни какая новая бизнес логика не может быть введена без изменения или расширения протокола.
В ходе долгих дебатов о том “как правильно вести svn” был выбран вариант фиксирования тегов и сформулирован ряд требований/правил которому следует придерживаться:
Trunk
- может быть не согласован по функционалу
- не подлежит ручному комплексному тестированию
- не подлежит для выката в продакшен или мержу в бранчи
Tag N
- все компоненты согласованы по функционалу
- функционал не меняется
- участвует в основном процессе тестирования
- Может быть помечен как Production после тестирования
- Может быть заморожен и забыт (удален) после функционального тестирования
- Bug Fix мержутся в Trunk
- Из Trunk мержутся только критические баги выявленные в более ранних версиях
Оговорка: в случае появления новой компоненты системы, которую уже можно начинать тестировать, но которая явно не дотягивает до функционала последнего тега, для того что бы попасть в общий процесс тестирования, такую компоненту можно брать из транка на ответственность разработчика и общем понимании, что это абсолютно исключительная и временная ситуация, пока не появится новый тег.
Наши теги, не такие уж и теги. Но они ближе по своей сути именно в понятию “тег” нежели к понятию “бранч”.
На обсуждении был и другой формат ведения SVN — вести основную разработку в бранчах.Т.е. если у нас возникает мажорное изменение, то мы создаем отдельный бранч, реализуем там это изменение и мержим его в транк. В этом случае транк всегда должен быть стабильным, что бы в любой момент времени можно было взять все компоненты системы и они были как минимум согласованы по функционалу, как максимум готовы для продакшена. В нашем случае это выглядит, не жизнеспособным. Так как порой одно мажорное изменение может вылезти в момент реализации другого. Тогда пришлось бы создавать на него отдельный бранч и переключатся на него. Проблемы начались бы в момент когда нужно было бы смержить две ветки в транк.
У нас весьма долгий цикл разработки, и еще не обкатан процесс перехода с версии на версию. Поэтому отдавая в публичное тестирование наш продукт, мы хотели бы иметь возможность делать хот фиксы. Но не имея тега(ветки) на каждую финальную версию, сделать это было бы не возможно. Так же мы не имеем возможности говорить, что “мол это будет исправлено в следующей версии”.
Дальше в статье я буду использовать термин “тег” именно в том значении которое я описал выше.
Мы срезаем скальпы
От тегов не было бы пользы не будь в нем все компоненты согласованы по функционалу. Узким местом в это случае является момент времени когда мы
Единый формат версии
Следующим важным шагом был ввод критерия основываясь на котором можно было бы смело заявить, что две компоненты согласованы между собой. Мы решили, что этим критерием будет версия. При этом версия сама по себе должна нести информацию основываясь на которой можно понять, что две компоненты согласованы.
Формат версии: MAJOR_NUM.MINOR_NUM.REVISION. Т.е. первая часть версии является так же номером тега. А мы знаем, что у нас все компоненты в теге согласованны. Указание сразу двух чисел в названии тега, позволяет делать нормальный переход между мажорными версиями, т.е. вместо 1.1.x, 1.2.x, 1.3.x, 2.4.x, у нас будет 1.1.x, 1.2.x, 1.3.x, 2.1.x. Номер ревизии остается и используется как обычно. Это номер ревизии каждой из отдельных компонент, а не просто последняя ревизия тега.
Потом пришла автоматизация
Следующим шагом был процесс внедрения CI (Continuous Integration). Выбор пал на, думаю многим известную CI систему, Jenkins. Выбор был между Cruise control, с которым я имею не самый приятный опыт работы, Team City который является платным для нормального его использования и непосредственно Jenkins, который бесплатен, широко распространен и хорошо документирован. Фактор бесплатности был немаловажен, так как страшно просить у начальства деньги за что то, что не факт что приживется.
Я твой хозяин ты мой раб
Компоненты нашей системы весьма разнообразные и на текущий момент используют 3 платформы для сборки: windows, linux и mac os. Получается один мастер и 2 slave (не хочу я больше повторять слово “раб”). Вопрос где делать мастера не возникал, конечно же на Linux. Была развернута начальная конфигурация из одного мастера (Linux) и одного slave (Windows). Начался процесс автоматизации сборки всех наших компонент. В ходе работы, уже появился тег 1.1 и число задач в Jenkins перевалило за 10. И тут начались проблемы. В один прекрасный день Jenkins упал. Мы его подняли, он у пал снова. И еще раз. И продолжал падать, независимо от того, как мы его запускаем через Tomcat или как службу. Независимо от JAVA машины, open JDK или SUN java. Независимо от текущей версии. Вернее падал не сам Jenkins, а JAVA машина которую он рушил. Я даже завел отдельный баг в их багтрекете (JENKINS-16199, правда на это все и заглохло). Потратив полторы недели на всякие попытки понять что происходит или хотя бы почему, было принято решение перенести мастера на Windows систему. И о чудо, в той же самой конфигурации все стало работать стабильно. Со времени число задач возросло в два раза, но все продолжает работать. В итоге конечная структура выглядит так:
Мастер на Windows, два Slave, на Linux и MacOS, взаимодействие по SSH через публичные ключи.
Об имплантантах
Стандартную поставку Jenkins мы расширили следующими плагинами:
Copy Artifact Plugin — Используем для того что бы артефакт одной сборки использовать в другой. Тут есть один простой момент, на котором можно споткнуться.
Если вы в одной из задача сохраняете артефакт вот так
А в другой, используя данный плагин, загружаете этот арт
То вы как минимум должны указать путь до него так же как в первом случае, а еще не помешает установить опцию “Flatten directories” что бы отбросить лишние пути и скопировать только сам арт в то место куда вам нужно.
Jenkins description setter plugin — Позволяет указывать некоторую информацию о каждой сборке в поле Build Description. Те задачи которые имеют версию, выводят ее туда.
Extra Columns Plugin — позволяет добавить поле Build Description на View.
Publish Over SSH — Отличный плагин для заливки файлов и выполнения команд на удаленной машине. Правда пока его не используем в силу того, что автотестирование еще не налажено. Недостатком данного плагина является то, что требуется предварительная конфигурации всех SSH соединений и в каждой задаче явно указывается какое соединение использовать на этапе ее создания.
Python Plugin — часть шагов сборки написано на Python, потому что так проще. В том числе есть отдельная задача которая делает почти все тоже самое что Publish Over SSH но только позволяя на лету конфигурировать параметры доступа к удаленной машине.
XCode integration — Используется для сборки проекта на MacOS. Нам пришлось попотеть для того, что бы уметь делать сборки как для разных версий iOS. В итоге на билд машине было разрешено использовать команду sudo xcode-select -switch без ввода пароля.
Особая, черная, магия
Я уже упоминал необходимость удаленной заливки и выполнения команд, которую почти прекрасно решает плагин Publish Over SSH. Но который не позволяет непосредственно для каждого процесса сборки указать нужный таргет.
У нас есть свой инсталятор, несколько тестировшиков и несколько машин. Многим лень тащить этот файл себе и что-то делать с ним что бы установить продукт. Для этих целей была создана сцепиальная задача “Remote deploy”.
И так, что она умеет:
Забрать инсталятор из тега который укажет пользователь
Выполнить его установку на той машине которую укажет пользователь
Вот как это выглядит
Данная задача является параметризированной.
Параметры
TAG, HOST, USER, DEPLOY_DIR, OPENSSL_DIR, тут никакой магии, обычные строки.
JOBS_NUMBER — “Build selector for Copy Atrifact”, позволяет указать какую версию сборки брать, последнюю удачную или какой-то определенный номер или что-то еще.
PASSWORD это параметр типа “Password Parameter” — закрывает пароль звездочками.
Вот таким образом забирается нужная сборка из нужной задачи
Настройка Вида
Виды в Jenkins это средство кастомизации отображения проектов. Мы убрали одну колонку и добавили “Build Description” для наших видов Trunk, Tag_X. Мы хотели что бы на дефолтном виде Jenkins было так же. Но Jenkins не позволяет делать такие манипуляции с его главным видом. Вместо этого можно создать свой вид (Default), добавить в него все проекты по маске .* и сделать этот вид, видом по умолчанию.
Стреляем в холостую
У нас есть специальный скрипт для создание версии и всех ее возможных вариаций. В начале он появлялся в каждом из проектов в котором нужен был подобный функционал. Спустя время, мы его положили в одном месте и везде где он нужен, тянем его как внешнюю зависимость. И тогда случился Epic fail, когда его случайно поправили в одном из проектов, где он был зависимостью. В Jenkins выстроилось в очередь 15 проектов на пересборку, холостую пересборку.
Первое что пришло в голову, это заблокировать файл для изменений. На файл повесили свойство needs-lock и от специального build юзера заблокировали его (svn lock).
Но есть решение лучше. Гораздо лучше. Jenkins позволяет ввести фильтрацию перед запуском сборки инициированной изменением репозитория. Это находится в разделе “Управление исходным кодом -> Расширенные”. Нас интересует параметр “Excluded Regions” в котором можно указать изменения в каких файлах и директориях стоит игнорировать.
В место заключения
На этом мое повествование заканчивается. В данный момент я уже покинул то место работы где был получен сей опыт и написана данная статья, поэтому на возможно возникшие вопросы о деталях и настройках навряд ли смогу ответить.
Спасибо за внимание.
Благодарности
FrimInc, zzapp за вычитку статьи