О том как перестать бояться и полюбить частые релизы

в 8:48, , рубрики: continuous integration, cpan, perl, метки: , ,

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

image

Расскажу немного о разработке на моем текущем месте работы.

Мы используем гибкую методологию разработки (Agile), причем сначала мы практиковали Scrum, а сейчас мы работаем по Kanban. Изначально я сам довольно скептически относился к подобному мероприятию, но буквально через пару недель я убедился что это работает.

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

Частые релизы — это не просто хорошо, это отлично: налаженный и частый процесс релиза, избавляет разработчика от страха перед обновлениями, а заказчик видит прогресс.

Такой процесс разработки отлично накладывается на Continuous Integration. Про него тоже пишут книжки и длинные статьи, вкратце, — это выполнение частых автоматизированных сборок проекта для скорейшего выявления и решения интеграционных проблем (wikipedia).

Мы разрабатываем распределенную систему, в качестве backend(API) у нас perl, клиентский код пишется на javascript, в качестве сервера непрерывной интеграции — jenkins ci.

Для тех кто не знаком с ним объясню на пальцах что такое jenkins: это сервис с web-интерфейсом, где настраиваются задачи для сборки. Задача представляет собой пошаговую инструкцию что должен выполнить сервер для сборки какого-либо пакета, в нашем случае это rpm. Например, обычно это происходит так:

  • сделать checkout из VCS (системы контроля версий);
  • запустить тесты;
  • скомпилировать и собрать необходимую структуру файлов;
  • запаковать в rpm-файл;
  • поместить в репозитарий.

Для создания rpm-пакета требуется написание spec-файла, который тоже представляет собой набор инструкций для сборки и установки пакета на конечной машине.

Итак: есть CI-сервер, на котором собирается rpm пакет, пакет потом попадает в yum репозитарий, и есть конечный сервер или кластер серверов, на которой данный пакет ставится.

У себя в проекте мы используем модули с CPAN — повторное использование кода это хорошо, но влечет за собой некоторые накладные расходы, за которые многие недолюбливают perl.

Зависимости

Рассмотрим пример: нам понадобилось использовать некий хэш-алгоритм, задача довольно тривиальная, берем модуль на CPAN и ставим в своё девелоперское окружение:

    # cpanm Some::CPAN::Module

после чего пишем код:

   use Some::CPAN::Module;
   sub encrypt {
       my $string = shift;
       return Some::CPAN::Module::mkHash($string);
   }

и покрываем его тестом:

    ok(encrypt("test") eq Some::CPAN::Module::mkHash("test"), "encrypt function");

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

И тут неприятность: тест не проходит — на сборочном сервере нет необходимого модуля.

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

Для сборки и прохождения тестов добавляем в spec-файл зависимость:

  BuildRequires: perl-Some-CPAN-Module

Для разворачивания на конечном сервере добавляем:

  Requires: perl-Some-CPAN-Module

Если повезло, то нужный rpm уже есть в yum-репозитарии, если нет — нужно собрать пакет и поместить его в yum-репозитарий.

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

Решение: выясняем версию модуля с которой наш код работает, собираем rpm c этой версией и указываем точную версию модуля в speс-файле.

   BuildRequires: perl-Some-CPAN-Module = 1.41

   Requires: perl-Some-CPAN-Module = 1.41

Другая проблема может произойти, если у модуля с CPAN есть свои зависимости, например, Some::CPAN::Module зависит от Other::CPAN::Module, в результате на девелоперском окружении и на билд-сервере после сборок-установок пакетов версии опять разъехались и тест фейлится, сервис по-прежнему не стартует. И таких модулей в реальном проекте может быть не один десяток. В узких кругах это называется медузой зависимостей.

Решение: можно также отследить зависимости у зависимостей и все их указать в spec-файле, но это решение требует много сил и времени на отслеживание и последующую сборку. Которое при нашей методологии разработки лимитировано.

Возможно альтернативное решение: поднятие собственного зеркала CPAN, например, при помощи pinto но это является темой данной статьи.

Есть другое решение.

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

Carton

Я хочу рассказать про инструмент, под названием Carton. Его автор, Tatsuhiko Miyagawa, является также автором кучи полезных утилит среди которых starman и cpanm.

Что такое Carton? Это менеджер зависимостей для перловых модулей. Написан он под впечатлением от Bundler для Ruby.

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

    requires 'Some::CPAN::Module', '1.45'; 

далее запускается команда:

    carton install 

и в директорию local/lib ставится необходимый модуль и также создается cpanfile.snapshot, в котором сохраняется информация об установленных с CPAN модулях. Этот файл нужно также добавить в VCS, чтобы в дальнейшем на билд-сервере при запуске carton install приехали зависимости из снапшота, тем самым гарантируя идентичность окружения.

Все что теперь нужно это добавить в @INC директорию local/lib.

Можно указать точную версию, которая необходима или допустимый диапазон версий:

    requires 'Some::CPAN::Module', '>= 1.41, < 1.46';

Более того можно указать модули, специфичные для разных окружений, например:

  osname 'MSWin32' => sub {
      requires 'Win32::File';
  };

  on 'test' => sub {
      requires 'Test::More', '>= 0.96, < 2.0';
      recommends 'Test::TCP', '1.12';
  };

  on 'develop' => sub {
      recommends 'Devel::NYTProf';
  };

Carton работает в упряжке вместе с cpanm еще одной полезной утилитой того же автора, именно ей мы можем быть благодарны за непосредственную установку модулей.

Теперь когда все зависимости у нас имеются в cpanfile, снапшот девелоперского окружения в cpanfile.snapshot и все это помещено в VCS в spec-файле перед запуском тестов можно добавить команду carton install.

Эту же команду можно и добавить в стадию установки при разворачивании пакета на сервер для которого предназначается rpm-пакет.

Но мы пошли дальше.
image
У автора Carton'a видение работы его утилиты следующее: Carton ставится на все необходимые сервера, далее утилитой типа capistranо выполняется команда сразу на всех необходимых удаленных машинах:

  # cap carton install

Немного странно, необходимо скачать из интернета или с локального зеркала CPAN весь ворох зависимостей, а этом может занимать довольно продолжительное время, а что если в этот момент возникли какие-то сетевые проблемы?

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

Такой подход популярен для проектов, например, на scala — создается über-jar в котором все зависимости проекта + код, на go пошли еще дальше — создают über-бинарник, в котором все зависимости уже вкомпилированы.

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

Автор: heoh

Источник

Поделиться

* - обязательные к заполнению поля