Введение в непрерывную интеграцию, доставку и развертывание

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

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

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

Читайте также: Сравнение инструментов непрерывной интеграции: Jenkins, GitLab CI, Buildbot, Drone, и Concourse

Непрерывная интеграция и ее преимущества

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

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

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

Чтобы устранить эти помехи в процессе интеграции, на практике НИ опирается на надежные наборы тестов и автоматизированную систему для их выполнения. Когда разработчик объединяет код с основным хранилищем, автоматизированные процессы запускают сборку нового кода. После этого для новой сборки запускаются тестовые наборы, которые проверяют, не возникли ли какие-либо проблемы. Если на этапе сборки или тестирования происходит сбой, команда получает предупреждение и может исправить сборку.

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

Непрерывная доставка и ее преимущества

Непрерывная доставка (НД) является продолжением непрерывной интеграции. Этот подход нацелен на автоматизацию процесса доставки программного обеспечения, что позволяет группам в любое время легко и уверенно развернуть свой код в производство. Поскольку кодовая база всегда находится в состоянии развертывания, релиз программного обеспечения становится непримечательным событием без сложного ритуала. Команды могут быть уверены, что они могут выпускать ПО в любое время без сложной координации или тестирования на поздней стадии. Как и НИ, непрерывная доставка – это практика, для эффективности которой требуется сочетание технических и организационных улучшений.

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

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

Организационные аспекты непрерывной доставки ставят высокий приоритет «развертываемости» (deployability) как принципиальную проблему. Это влияет на то, как функции создаются и подключаются к остальной части кодовой базы. Структуру кода необходимо продумать таким образом, чтобы функции могли в любое время безопасно развернуться в производстве, даже если они не завершены. Чтобы помочь с этим, появился ряд методов.

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

Непрерывное развертывание и его преимущества

Непрерывное развертывание (НР) – это расширение непрерывной доставки, которое автоматически развертывает каждую сборку, которая проходит полный цикл тестирования. Вместо того чтобы ждать, пока человек решит, что и когда развертывать в рабочей среде, система НР развертывает все, что успешно прошло конвейер на предыдущем этапе. Имейте в виду: новый код развертывается автоматически, но существуют методы для отсрочки активации новых функций или ограничения их работы (только для некоторой части пользователей). Развертывание автоматически передает функции и исправления клиентам и помогает избежать путаницы в отношении того, что в настоящее время развернуто в производство.

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

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

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

Основные подходы и практики непрерывных процессов

Непрерывная интеграция, доставка и развертывание применяются на разных этапах, но их объединяют некоторые концепции и практики, которые являются основополагающими для их успеха.

Небольшие итеративные изменения

Одной из наиболее важных практик при внедрении НИ является поддержка небольших изменений. Разработчики должны научиться разбивать большую работу на мелкие кусочки и выполнять ее понемногу. Существуют специальные методы (branch by abstraction и выключатели функций, о которых мы поговорим позже), которые помогают защитить функциональность основной ветки от постоянных изменений кода.

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

Trunk Based Development

В Trunk Based Development работа выполняется в основной ветке репозитория, либо же полученный код объединяется с общим репозиторием через короткие промежутки времени. Допускаются недолговечные ветки функций, если они представляют небольшие изменения и вливаются в главную ветку как можно скорее.

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

Релизы выполняются из основной ветки или из ветки релиза, специально созданной в основной ветке проекта. Никаких разработок в ветках релиза не происходит – это чтобы сосредоточиться на основной ветке как на единственном достоверном источнике.

Быстрая сборка и тестирование

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

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

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

Согласованность во всем конвейере развертывания

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

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

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

Разделение развертывания и релиза

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

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

Существует ряд методов, которые помогают командам развернуть код, отвечающий за функцию, но не пускать эту функцию в релиз. Переключатели функций (feature flags) устанавливают условную логику, что на основе значения переменной среды позволяет проверить, следует ли запускать код. Branch by abstraction позволяет разработчикам заменять релизы, размещая уровень абстракции между потребителями ресурсов и провайдером. Тщательное планирование этих методов дает возможность разделить эти два процесса.

Типы тестирования

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

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

Дымовое тестирование

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

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

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

Модульное тестирование

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

Модульные тесты необходимы для проверки правильности отдельных компонентов кода на внутреннюю согласованность и корректность, прежде чем они будут помещены в более сложные контексты. Ограниченный объем тестов и устранение зависимостей облегчают поиск причины дефекта. Это также лучший момент для тестирования различных входных данных и веток кода (в дальнейшем это будет сложнее сделать). Часто модульные тесты идут после тестов «на дым». Это первые тесты, которые запускаются при внесении каких-либо изменений.

Модульные тесты обычно выполняются отдельными разработчиками на отдельной рабочей станции перед отправкой изменений. Однако серверы непрерывной интеграции почти всегда повторно запускают эти тесты перед началом интеграционных тестов – в качестве средства безопасности.

Интеграционное тестирование

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

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

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

Комплексное тестирование

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

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

Приемочное тестирование

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

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

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

Дополнительная терминология

Ранее мы обсудили основные термины и подходы НИ, но существует много связанных понятий, с которыми вы можете столкнуться при изучении непрерывной интеграции, доставки и развертывания. Давайте рассмотрим несколько других терминов, которые вы, вероятно, встретите при работе в такой среде:

  • Blue-green развертывания (или просто сине зеленый деплой) — это стратегия тестирования кода в производственной среде и развертывания кода с минимальным временем простоя. При этом поддерживаются два набора производственных сред, а код развертывается в неактивном наборе, где может проводиться тестирование. Когда все готово к релизу, производственный трафик направляется на серверы с новым кодом, мгновенно делая изменения доступными.
  • Branch by abstraction – это метод выполнения основных операций по перепроектированию кода в активном проекте без долгоживущих веток разработки в репозитории исходного кода. Уровень абстракции создается и развертывается между потребителями и существующей реализацией, благодаря чему новый релиз можно собрать за абстракцией.
  • Сборка – это конкретная версия программного обеспечения, созданная из исходного кода. В зависимости от языка это может быть скомпилированный код или согласованный набор интерпретируемого кода.
  • Канареечный релиз – это стратегия выпуска изменений для ограниченного круга пользователей. Идея состоит в том, чтобы убедиться, что все правильно работает с производственными нагрузками, при этом сводя к минимуму влияние при возникновении проблем.
  • «Темный запуск», или dark launch – это практика развертывания кода в рабочей среде, которая получает производственный трафик, но не влияет на взаимодействие с пользователем. Новые изменения развертываются вместе с существующими реализациями, и один и тот же трафик часто направляется для тестирования в оба места. Старая реализация все еще будет подключена к пользовательскому интерфейсу, а новый код можно оценить на правильность «за кулисами», используя реальные пользовательские запросы в производственной среде.
  • Конвейер развертывания – это набор компонентов, которые перемещают программное обеспечение через все более строгие сценарии тестирования и развертывания, чтобы оценить его готовность к релизу. Конвейер обычно заканчивается автоматическим развертыванием в производство или предоставлением возможности сделать это вручную.
  • Переключатели функций (feature toggles или feature flags) – это метод развертывания новых функций за условной логикой на основе значения переменной среды. Новый код можно развернуть в рабочей среде без активации, установив соответствующий переключатель. Для выпуска программного обеспечения значение переменной среды изменяется, в результате чего активируется новый путь к коду. Флаги функций часто содержат логику, которая позволяет подмножествам пользователей получать доступ к новой функции, создавая механизм для постепенного развертывания нового кода.
  • Продвижение в контексте непрерывных процессов означает переход от сборки программного обеспечения к следующему этапу тестирования.
  • Продолжительное тестирование (soak testing) – это тестирование программного обеспечения в течение продолжительного периода времени под значительной производственной нагрузкой.

Заключение

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

Читайте также:

Tags: