Блог

Непрерывная интеграция (Часть 2)

Перевод неустаревающей статьи Мартина Фаулера про Непрерывную Интеграцию. Несмотря на то, что статья написана в 2006 году и упоминаемые в ней инструменты уже устарели, описание самой практики остаётся актуальной и по сей день.

Непрерывная интеграция ― это практика разработки программного обеспечения, в которой участники команды часто выполняют интеграцию своих изменений. Как правило, каждый участник выполняет интеграцию как минимум раз в день, и в итоге достигается такой режим работы, при котором интеграция выполняется несколько раз в день. Каждая интеграция проверяется путем автоматической сборки (включающей тестирование), что позволяет находить ошибки интеграции как можно скорее. Многие команды убедились, что такой подход существенно снижает количество интеграционных проблем и позволяет быстрее разрабатывать целостный программный продукт. Эта статья представляет собой обзор непрерывной интеграции, описывающий эту технику и то, как она используется в настоящее время.
1 мая 2006 г

Содержание

Главная цель непрерывной интеграции ― получить быстрый фидбек. Ничто так не вредит проведению CI, как слишком долгая сборка. Здесь я хотел бы немного порассуждать о том, что можно считать долгой сборкой. Большинство моих коллег считают сборку, которая выполняется больше часа, абсолютно недопустимой. Я помню, как некоторые команды мечтали о быстрой сборке, но все же случается наблюдать когда трудно получать сборку с желаемой скоростью.
Однако для большинства проектов методика «десятиминутной сборки», используемая в экстремальном программировании, вполне приемлема. Для большинства современных проектов она достижима. На достижении быстрой сборки действительно стоит сосредоточить свои усилия, так как каждая минута, которую вы экономите в процессе сборки ― это дополнительная минута для каждого разработчика, выполняющего коммит. А поскольку в CI используются частые коммиты, это позволит сэкономить много полезного времени.
Если сейчас вы вынуждены наблюдать сборку, которая продолжается один час, задача ускорения сборки может показаться вам пугающей. Пугающей может быть даже задача работы над новым проектом и ускорения этой работы. По крайней мере, для корпоративных приложений мы может назвать обычное «узкое место». Это тестирование, а особенно те тесты, которые требуют использования внешних служб, например, баз данных.
Вероятно, самым важным шагом будет начать работу по настройке delivery pipeline. Основная идея delivery pipeline (его также называют build pipeline или поэтапной сборкой) заключается в том, что несколько сборок выполняются последовательно. Коммит в основную ветку запускает первую сборку (назовем ее сборкой коммита). Сборка при коммите ― это сборка, необходимая, когда кто-либо коммитит в основную ветку. Сборка при коммите должна быть быстрой. Хотя высокая скорость также имеет и ряд недостатков, которые уменьшают вероятность выявления ошибок. Основная сложность ― найти баланс между необходимостью выявлять ошибки и скоростью. Хорошая сборка по коммиту должна быть достаточно стабильной, чтобы другие люди могли с ней работать.
Джез Хамбл и Дэйв Фарли развили эти идеи в понятие «непрерывного развертывания», которое включает в себя более подробное рассмотрение «delivery pipeline». Их книга Непрерывное развертывание была заслуженно удостоена Премии Джолта в 2011 году.
Если сборка при коммите прошла успешно, значит, остальные разработчики могут спокойно работать с кодом. Однако есть и другие, более медленные, тесты, которые вы можете начать выполнять. На дополнительных компьютерах можно проводить дальнейшие процедуры тестирования сборки, которые выполняются дольше.
Простой пример такого подхода ― delivery pipeline, состоящий из двух этапов. Первый этап ― компиляция и запуск локальных юнит-тестов с использованием заглушек вместо баз данных. Такие тесты прогоняются очень быстро, в пределах десяти минут. Однако они не могут обнаружить ошибки, влияющие на взаимодействия более высокого уровня, например, использующие настоящую базу данных. На втором этапе запускается другой набор тестов, которые затрагивают настоящую базу данных и покрывают более сквозное поведение продукта. Выполнение такого набора тестов может занять пару часов.
В этом сценарии разработчики используют первый этап в качестве сборки при коммите и опираются на него в основном цикле CI. Сборка, соответствующая второму этапу, запускается тогда, когда это возможно, с использованием исполняемого файла на базе последнего успешного коммита для дальнейшего тестирования. Если эта вторая сборка завершается ошибкой, это не означает, что нужно «все остановить», как в первом случае, но команда все же должна сосредоточиться на скорейшем исправлении этой ошибки, в то время как первая сборка по коммиту продолжает работать. Как и в этом примере, последующие сборки — это, как правило, обычные тесты, и на данном этапе это тесты, которые замедляют работу.
Если вторичная сборка выявила ошибку, это означает, что сборку при коммите имеет смысл протестировать еще раз. Постарайтесь гарантировать, что любая ошибка найденная на поздней стадии, приведет к дополнительному тестированию сборки по коммиту, в которой она была найдена, чтобы в сборке по коммиту ошибка оставалась исправленной. Таким образом тесты запускаемые при . коммите усиливаются каждый раз, когда они запускаются. В некоторых случаях невозможно создать быстрый тест, который обнаружит ошибку. Тогда вам, возможно, стоит протестировать соответствующее условие во время вторичной сборки. В большинстве случаев, к счастью, вы все же можете добавить все необходимые тесты в сборку по коммиту.
В этом примере мы рассмотрели конвейер, состоящий из двух этапов, но по такому же принципу можно добавлять к нему новые этапы. Сборки, следующие за сборкой по коммиту, можно проводить параллельно, то есть, если у вас есть вторичные тесты, на прогон которых уходит два часа, вы можете ускорить работу, прогоняя их на двух компьютерах (по половине тестов на каждом компьютере). Используя параллельные вторичные сборки, как в описанном случае, вы можете добавлять в обычную процедуру сборки автоматизированное тестирование, в частности, тестирование производительности.
Цель такого тестирования ― выявить при контролируемых условиях любую проблему системы в рабочей среде. Значительная часть этого процесса ― среда, в которой будет запущена рабочая система. Если вы проводите тестирование в другой среде, то любое различие приводит к риску того, результаты этого тестирования, будут отличными от поведения в боевой среде.
Поэтому необходимо настроить тестовую среду так, чтобы она была по возможности точной копией рабочей среды. Используйте те же базы данных тех же версий и ту же версию операционной системы. Добавьте все библиотеки, используемые в рабочей среде, в тестовую среду, даже если система на самом деле в них не нуждается. Используйте те же IP-адреса и порты и то же аппаратное обеспечение.
Конечно, в реальной жизни возможны ограничения. Если вы пишите ПО для настольных компьютеров, не имеет смысла тестировать его на клонах всех возможных настольных компьютеров со всеми возможными сторонними программами, которые запускают разные люди. Кроме того, может оказаться слишком дорого клонировать некоторые рабочие среды (хотя я нередко встречал случаи ложной экономии, когда люди отказывались от клонирования умеренно дорогостоящих сред). Несмотря на эти ограничения, вашей целью по-прежнему остается максимально полное дублирование реальной рабочей среды и понимание рисков, которые вызывает любое различие между тестовой и рабочей средами.
Если вы используете простую установку без сложных связей, вы можете запускать свою сборку по коммиту в полностью идентичной среде. Однако иногда вам может понадобиться использование тестовых дублеров из-за медленного или прерывистого ответа системы. В итоге общей практикой стало использование изолированной среды для быстрого запуска тестов сборки по коммиту, а копии рабочей среды ― для вторичного тестирования.
Я заметил рост интереса к виртуализации для создания тестовых сред. Виртуальные машины можно сохранять со всеми необходимыми элементами, благодаря встроенным возможностям виртуализации. В результате установка последней сборки и прогон тестов становятся относительно простой задачей. Более того, таким способом вы можете запускать несколько тестов на одном компьютере или эмулировать сеть из нескольких компьютеров на одном компьютере. Поскольку снижение производительности, характерное для виртуализации, в последнее время уменьшается, применение этот вариант становится все более целесообразным.
Одна из главных трудностей процесса разработки программного обеспечения ― убедиться, что вы создаете нужный продукт. Мы заметили, что людям бывает очень сложно заранее корректно сформулировать, что они хотят. Гораздо проще посмотреть на что-то, что требует доработки, и сформулировать, что именно надо изменить. Процессы гибкой разработки явно учитывают эту особенность человеческого поведения и извлекают из нее выгоду.
Чтобы такие процессы работали эффективно, каждый участник проекта должен иметь возможность получить последний исполняемый файл и запустить его: для демонстраций, для исследовательского тестирования или просто для просмотра изменений за неделю.
Добиться этого очень просто: убедитесь, что у вас есть хорошо известное место, где каждый участник может найти последний исполняемый файл. В подобном хранилище может быть полезно размещать несколько исполняемых файлов. В этом случае для тестирования коммита следует использовать самый последний исполняемый файл. Этот файл должен быть очень стабильным, чтобы гарантировать надежность коммита.
Если вы работаете по четко определенным итерациям, имеет смысл помещать сборки в хранилище, в конце каждой итерации. В частности, для демонстраций необходимо программное обеспечение с хорошо знакомыми функциями, поэтому стоит пожертвовать чем-то более поздним в пользу того с чему тот, кто демонстрирует хорошо знаком.
<!--kg-card-begin: html-->
<a id = "transparency"></a>
<!--kg-card-end: html-->
Непрерывная интеграция — это, прежде всего, коммуникация, поэтому необходимо гарантировать, чтобы каждый ее участник мог легко увидеть текущее состояние системы и внесенные в нее изменения.
В частности, очень важно, чтобы все знали, в каком состоянии находится сборка основной ветки. Если вы используете Cruise, то на встроенном в него сайте вы можете посмотреть, выполняется ли в данный момент сборка, и узнать состояние последней сборки основной ветки. В некоторых командах эту информацию делают еще более наглядной, встраивая в систему сборки монитор, непрерывно показывающий состояние сборки. Когда сборка выполняется, на мониторе обычно показываются зеленые огоньки, а при возникновении ошибок ― красные. Также популярен подход, когда используются красные и зеленые лава-лампы, которые не только показывают состояние сборки, но и как долго сборка находится в том или ином состоянии. Пузырьки в красной лампе означают, что сборка сломана уже долгое время. Каждая команда сама выбирает, какие датчики состояния сборки использовать ― и хорошо бы здесь можно применять игровой подход (недавно я видел, как кто-то экспериментировал с танцующим кроликом).
Если вы используете процесс непрерывной интеграции вручную, такая наглядность по-прежнему важна. Монитор на физическом компьютере сборки может показывать состояние сборки основной ветки. Иногда на стол разработчика, выполняющего очередную сборку, помещают какой-либо опознавательный знак или предмет (это может быть что-то глупое и смешное, например, резиновая курица). Иногда разработчики оповещают команду об успешной сборке каким-либо звуком (например, звоном колокольчика).
Но, конечно, сайты на CI-сервере могут дать гораздо больше информации. Cruise показывает не только, кто в данный момент выполняет сборку, но и какие изменения были внесены. Кроме того, Cruise показывает историю изменений, позволяя участникам команды всегда быть в курсе недавней активности в проекте. Я знаю руководителей команд, которые используют подобные сайты, чтобы быть в курсе того, чем занимаются участники команды и какие изменения вносятся в систему.
Другое преимущество использования сайтов заключается в том, что удаленно работающие участники команды могут быть в курсе статуса проекта. В общем случае, я предпочитаю ситуацию, когда все, кто активно работает над проектом, сидят рядом, но иногда о состоянии проекта важно узнавать людям извне. Кроме того, некоторым группам может быть полезно собирать воедино информацию о сборке по нескольких проектам, чтобы получать статус различных проектов простым и автоматизированным способом.
Эффективно отображать информацию можно не только на экране компьютера. В одном из проектов, внедряющих CI, мы использовали один из моих любимых способов отображения информации. По ряду причин, о которых долго рассказывать, в этом проекте не удавалось создавать стабильные сборки. Мы повесили на стену календарь, на котором был показан весь год, и каждый день был обведен маленьким квадратиком. Каждый день группа тестирования должна была помещать на соответствующий день зеленый стикер, если они получили стабильную сборку, которая прошла все тесты при коммите, и красный стикер ― в противном случае. Спустя некоторое время календарь выявил состояние процесса сборки, показывая стабильное улучшение. Вскоре зеленых квадратиков стало так много, что календарь мы убрали, так как он выполнил свою функцию.
Для использования непрерывной интеграции вам понадобится несколько сред ― одна для запуска тестов по коммиту, и несколько для запуска вторичных тестов. Поскольку вам придется перемещать исполняемые файлы между этими средами, вы наверняка захотите делать это автоматически. Поэтому важно создать скрипты, которые позволят вам быстро развертывать приложение в любой среде.
Естественным следствием такого подхода станет наличие скриптов, которые с такой же легкостью позволят вам развернуть приложение и в рабочей среде. Вам необязательно развертывать приложение в рабочей среде каждый день (хотя я встречал проекты, где делают именно так), но автоматическое развертывание поможет вам ускорить этот процесс и сократить количество ошибок. Кроме того, это недорого, так как этот скрипт будет использовать те же средства, что и скрипты для развертывания в тестовых средах.
Многие беспокоятся о том, как поступать с базами данных при частых релизах. Мы с Прамодом Садаладжем написали эту статью, чтобы рассказать о том, как выполнять автоматический рефакторинг и миграцию баз данных.
Если вы автоматически устанавливаете приложение в боевой среде и при этом автоматизировали что-то лишнее, необходимо подумать о возможности автоматического отката. Время от времени случаются неудачи, и если дурно пахнущее коричневое вещество начинает попадать на вращающийся металл, хорошо бы быть в состоянии быстро вернуться к последнему известному стабильному состоянию. Кроме того, возможность автоматического отката снижает напряжение во время развертывания, и поощряет людей выполнять развертывание чаще и, таким образом, быстрее доставлять пользователям новые функции. (Сообщество Ruby on Rails разработало инструмент Capistrano, который, в частности, может эффективно выполнять подобные откаты).
В кластерных средах мне приходилось наблюдать поэтапное развертывание(rollout), когда новое программное обеспечение развертывается на одном узле за определенный период времени, постепенно заменяя установленное приложение в течение нескольких часов.
В частности, я встречал интересную вариацию этого подхода при развертывании публичного веб-приложения ― когда тестовая сборка постепенно развертывается для определенного набора пользователей. В этом случае команда может посмотреть, как используется тестовая сборка, прежде чем развернуть ее для всех пользователей. Такой подход позволяет вам протестировать новые функции и пользовательский интерфейс, прежде чем сделать окончательный выбор. Автоматическое развертывание в связке с хорошей CI-дисциплиной ― необходимы для успешного выполнения такой работы.
В общем и целом, я думаю, что самое большое и широко известное преимущество непрерывной интеграции заключается в снижении рисков. Я все еще мысленно возвращаюсь к раннему проекту разработки, о котором я упоминал в первом абзаце этой статьи. Тогда участники команды находились на последнем этапе (как они надеялись) большого проекта, но при этом понятия не имели, сколько времени займет этот этап.
Проблема отложенной интеграции заключается в том, что очень сложно предсказать, сколько времени она займет, и, что еще хуже, очень сложно понять, как далеко вы продвинулись в этом процессе. В результате вы оказываетесь в «слепом пятне» во время самой напряженной стадии проекта, даже если ваш проект ― один из тех редких случаев, когда сроки не превышены.
Непрерывная интеграция полностью избавляет вас от этой проблемы. Интеграция длится недолго, и «слепые пятна» при этом отсутствуют. В любой момент времени вы знаете, на какой стадии вы находитесь, что работает, а что нет, и какие неисправленные дефекты есть в вашей системе.
Дефекты — это та самая неприятная вещь, которая разрушает ваше чувство уверенности, мешает выполнению сроков, и снижает вашу репутацию. Дефекты в развернутом программном продукте приводят к тому, что пользователи злятся на вас. Дефекты в текущей работе создают вам препятствия и мешают вам добиться корректной работы остальной части программного продукта.
Непрерывная интеграция не избавляет вас от дефектов, но существенно упрощает их обнаружение и исправление. В этом отношении она имеет много общего с самотестируемым кодом. Если вы внесли в код ошибку и быстро ее обнаружили, вам будет намного проще избавиться от нее. Поскольку вы изменили при этом лишь маленький фрагмент системы, вам не придется долго ее искать. А поскольку над этим фрагментом системы вы только что работали, вы еще хорошо его помните, что также облегчает поиск ошибки. Вы также можете выполнить diff-отладку ― сравнение текущей версии системы с предыдущей, в которой этой дефекта еще не было.
Дефекты также имеют свойство накапливаться. Чем больше у вас ошибок, тем сложнее исправлять каждую из них. Отчасти это объясняется тем, что дефекты взаимодействуют между собой, и тогда результат нескольких дефектов создает новый дефект. Из-за этого отдельные ошибки труднее искать. Кроме того, работает психологический фактор ― когда ошибок много, у людей меньше энергии, чтобы искать и исправлять их. Авторы книги «Прагматичные программисты» называют это явление теорией разбитых окон.
В результате проекты с непрерывной интеграцией обычно имеют гораздо меньше ошибок, как в рабочем продукте, так и в работе. Однако я хотел бы подчеркнуть, что это преимущество напрямую связано с тем, насколько хорош ваш набор тестов. Вы можете обнаружить, что заметно улучшить набор тестов не так уж и трудно. Тем не менее обычно проходит некоторое количество времени, пока участники команды привыкнут к низкому уровню ошибок, которого они потенциально могут достичь. Для достижения этого уровня необходимо постоянно работать над улучшением своих тестов.
Если вы используете непрерывную интеграцию, вы тем самым устраняете одно из самых больших препятствий на пути к частому развертыванию. Частое развертывание имеет большое значение, так как оно позволяет пользователям быстрее получать новые функции и быстрее давать по ним обратную связь и в целом лучше взаимодействовать с вами во время цикла разработки. Это помогает преодолеть барьеры между клиентами и разработчиками ― а это, по моему убеждению, самые большие барьеры на пути к успешной разработке программного обеспечения.
Итак, вы заинтересовались и хотите попробовать непрерывную интеграцию. С чего начать? Полный набор практик, который я описал выше, поможет вам получить все преимущества непрерывной интеграции, но вам необязательно начинать использовать все эти практики сразу.
Здесь нет единого рецепта, многое зависит от вашей структуры и команды. Но я могу дать подсказать несколько вещей, которые мы изучили.
Один из первых шагов ― автоматизация сборки. Поместите все, что вам необходимо, в систему управления исходным кодом, чтобы можно было запустить сборку с помощью одной команды. Для многих проектов это может оказаться непростой задачей, однако это сделать необходимо, чтобы все остальные аспекты работали. Для начала вы можете выполнять сборку время от времени по необходимости или просто автоматизировать ночную сборку. Хотя это еще не будет непрерывной интеграцией, автоматизация ночной сборки будет важным первым шагом к ней.
Добавьте в свою сборку некоторое количество автоматизированных тестов. Попробуйте выделить основные области, в которых часто возникают проблемы, и добавьте автоматизированные тесты для проверки этих областей. На существующем проекте особенно трудно быстро получить по-настоящему хороший набор тестов ― необходимо потратить некоторое время на их создание. Однако вам придется с чего-то начать, поговорка «Рим не сразу строился» здесь вполне применима.
Попробуйте ускорить сборку при коммите. Непрерывная интеграция, при которой сборка длится несколько часов ― лучше, чем ничего, но лучше все-таки постараться достичь магических десяти минут. Иногда это требует серьезной переделки кодовой базы, при которой вы удаляете зависимости от медленных участков системы.
Если вы начинаете новый проект, начните применять непрерывную интеграцию с самого начала. Наблюдайте за временем выполнения сборки и предпринимайте меры, если оно начинает превышать десять минут. Действуя быстро, вы сможете выполнить необходимую реструктуризацию, прежде чем кодовая база увеличится настолько, что превратится в сущее наказание.
И, главное, попросите помощи. Найдите того, кто уже вводил непрерывную интеграцию раньше и может вам помочь. Вводить ее сложно, как и любую другую технику, если вы не знаете, как должен выглядеть конечный результат. Найти наставника может быть дорого, но вы в любом случае заплатите за потерю времени и производительности, если не найдете его. (Реклама: да, мы в ThoughtWorks оказываем некоторые консультации в этой области. В конце концов, мы можем делать это после того, как прошли практически через все возможные ошибки).
За несколько лет, которые прошли с того момента, когда мы с Мэттом написали первую статью, непрерывная интеграция стала одной из ведущих техник разработки программного обеспечения. В ThoughtWorks она используется практически во всех проектах, и мы наблюдаем, как она используется во всем мире. Я почти никогда не слышал негативных отзывов об этой технике, в отличие от других более противоречивых практик экстремального программирования.
Если вы не используете непрерывную интеграцию, я настоятельно рекомендую вам попробовать это делать. А если используете, то, возможно, некоторые идеи из этой статьи могут помочь вам делать это более эффективно. За последние несколько лет мы узнали о непрерывной интеграции много нового, и я надеюсь, что есть еще много того, что можем узнать и улучшить.
Что еще почитать
Эссе, наподобие этого, может только поверхностно покрыть эту тему, но так как это важная тема, я создал страницу с руководствомна моём сайте для поиска большей информации.
Для того чтобы исследовать Непрерывную Интеграцию более детально я советую вам взглянуть на одноименную книгу Пола Дюваля по этой теме (она кстати выиграла Jolt award — я даже не мог этого представить). Тема непрерывного развертывания более широго рассмотрена в книге Джеза Хамбла и Дэйва ФарлиНепрерывное развертывание, которая тоже завоевала Jolt award.
Больше информации по непрерывной интеграции вы также можете найти на сайте ThoughtWorks.