Что такое CI


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


Если вы не знаете как настроить CI в своем проекте, я приглашаю вас "под кат"


Всем любителям стилей и нотаций, я не всегда соблюдаю нотации и требования в силу личных обстоятельств и причин, которые я не хочу обсуждать. Я знаю, что я отступаю от канонов React и JS сообществ, поэтому сразу прошу меня за это извинить и считать данные вольности придурью автора. Моя цель поделиться опытом и рассказать людям насколько просто сегодня настроить CI, я не имею никакого отношения к Тревису или Хероку, более того, мне не нравится Heroku, я использовал его только из-за простоты настройки для новичков. Дальше TLDR.


Зачем нам это нужно


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


  • Берем задачу из списка/Получаем от начальства
  • Создаем новую ветку в git и открываем пул реквест
  • Пишем код
  • Лично или с помощью коллеги выполняем код-ревью (code review — обзор/проверку кода)
  • Запускаем тесты
  • Сливаем ветку в мастер
  • Выполняем сборку проекта
  • Публикуем новую сборку

Этот процесс повторяется для каждой задачи, если вы 10 дней писали код и на сборку/развертывание потратили 1 час, то это выглядит разумно и не трудозатратно. Но что если вы поправили мелкий баг за 1 минуту, но на развертывание потратите тот же час? В этой ситуации это выглядит довольно расточительно. А если вам нужно выполнять в день 10 — 20 багфиксов (bugfix, исправление ошибки)?


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


Что мы будем использовать для решения задачи


Когда я столкнулся с этой проблемой еще в далеком 2008м году, на рынке было очень мало решений, тогда для автоматизации этих процессов приходилось разворачивать свои сервера, следить за правильностью версий библиотек, писать скрипты для сборки проекта, писать скрипты для выгрузки проекта на сервера и много других трудоемких операций. Сейчас все проще, большая часть задач элементарно поддается автоматизации, на рынке множество облачных сервисов для их решения. После продолжительных поисков, я решил остановиться на open source проекте travis-ci.org. "Трэвис" бесплатен для open source проектов, имеет платный вариант для коммерческого использования. Он понравился мне за простоту настройки и использования. Тем не менее, чтобы это не выглядело рекламой, я хочу отметить, что на рынке появляется все больше достойных сервисов, например: CircleCI, Codeship.


Мы создадим React приложение, для тестирования будем использовать Jest, для развертывания Heroku. Предполагается, что читатель обладает базовыми знаниями в программировании, базовым английским, базовым интеллектом, имеет настроенную среду node.js, установленный yarn, имеет учетные записи на github.com, heroku.com, travis-ci.org или в состоянии создать их в процессе прохождения данного туториала.


Создаем приложение


Т.к. статья ориентирована на молодых разработчиков, мы будем использовать генератор
React приложений — create-react-app. Установим его глобально:


$ yarn global add create-react-app

После установки создадим наше приложение, к примеру мы будем писать веб-интерфейс для управления производственной линией.


$ create-react-app factory_line_manager

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


cd factory_line_manager
$ yarn start

В браузере должна открыться страница нашего приложения. По умолчанию, если порт 3000 свободен — localhost:3000



По умолчанию create-react-app создает нам проект with no build configuration, что означает — без конфигураций. Генератор создаст для нас стандартный файл конфигурации и нам не нужно будет настраивать webpack, jest, babel и прочие библиотеки. В 95% случаев эти настройки будут выдавать более качественный и чистый код, чем новичок сможет сконфигурировать самостоятельно. Поэтому, я настоятельно рекомендую оставлять конфигурацию как есть, до тех пор пока вы не поймете, как это работает.


Если Вам интересно как работает конфиг

Сделайте копию проекта и выполните следующую команду в консоли


$ yarn eject

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


Подключаем GIT


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


Зайдите на github и создайте новый репозиторий. Я создал следующий репозиторий habrahabr_topic_352282.


Находясь в папке проекта. Инициализируем гит:


$ git init

Добавляем все файлы нашего проекта в гит


$ git add .

Создаем первый комит:


$ git commit -m "First commit"

Подключаем локальную папку к Вашему репозиторию на гите. Будьте внимательны, замените evilosa — на свой профиль и habrahabr_topic_352282 — на имя созданного вами репозитория:


$ git remote add origin git@github.com:evilosa/habrahabr_topic_352282.git

Заливаем наш проект на удаленный сервер гит:


$ git push -u origin master

После последней команды должна произойти магия и код выгрузится в наш репозиторий на гите.


Настраиваем CI


Заходим на сайт travis-ci.org и входим с учетной записью гитхаба (Sign in with Github). В появившемся окне авторизуем приложение через OAuth:



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



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


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


sudo: false

language: node_js
node_js:
  - 9

branches:
  only:
    - master

Добавляем изменения в гит и выгружаем на сервер:


$ git add .
$ git commit -m "Add travis config"
$ git push -u origin master

Настройка первого этапа CI завершена. После выгрузки изменений в гит, тревис должен увидеть настройки и выполнить тестирование и сборку проекта:



В конце логов тревиса, вы должны увидеть статус успешного прохождения тестов:



Теперь при каждом комите в мастер ветку у нас будет запускаться автоматическое построение и запуск тестов на тревисе. Уже неплохо. Двигаемся дальше. Следующий шаг — автоматическая публикация нашего проекта на Heroku.


Зайдите на Heroku, создайте учетную запись и авторизуйтесь. Обратите внимание, что почта из домена mail.ru у них заблокирована, используйте сторонние сервисы.
Если Вы новый пользователь то увидите примерно следующее:



Создайте новое приложение нажав кнопку Create New App. Введите имя Вашего приложения и регион размещения:



Обратите внимание имя моего приложения на Heroku, не соответствует имени репозитория.


Вернемся в наш файл конфигурации тревиса .travis.yml. Добавьте в него следующий код подставив свои значения:


deploy:
  provider: heroku
  app: "Имя вашего приложения в Heroku"
  api_key:
    secure: "Ваш ключ API Heroku"

Как найти ключ API Heroku

Перейдите в настройки профиля, для этого кликните по иконке профиля и выберите пункт "Account settings". Найдите пункт API key:



После нажатия на кнопку Reveal у Вас появится возможность скопировать ключ.


Добавляем изменения в гит и выгружаем на сервер:


$ git add .
$ git commit -m "Add Heroku deploy to travis"
$ git push -u origin master

Проверяем логи тревиса. Если все сделано правильно, то мы должны увидеть следующее сообщение:



Переходим по ссылке вида https://<Имя вашего приложения на Heroku>.herokuapp.com/ и видим наше React приложение.


Если вылетела ошибка

Если при построении вы увидели вот такую ошибку:



Значит произошло обновление какого-либо пакета входящего в состав генератора create-react-app.


Обновите список пакетов локально и выгрузите изменения в мастер следующими командами:


$ yarn install
$ git add .
$ git commit -m "Update yarn.lock"
$ git push -u origin master

Наш CI готов, можно испытывать его боем.


Что делать дальше


Эта статья призвана ознакомить пользователя с основами CI и служит отправной точкой для дальнейших экспериментов. Из явных минусов представленного подхода — Ваш API ключ Heroku будет лежать в открытом репозитории. После прохождения туториала, я настоятельно рекомендую Вам его обновить. Для реальных проектов ключи определяются через encrypted variables, подробнее Вы можете ознакомиться с этим здесь.


Если Вам нужно публиковать несколько версий приложения, к примеру — production, staging. То вы можете сделать несколько веток в гите и управляя этим процессом, публиковать разные ветки на разные приложения в Heroku, пример:


deploy:
  provider: heroku
  app: 
    master: my-staging-application
    production: my-production-application
  api_key:
    secure: "Ваш ключ API для Heroku"

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


Бонус


Давайте еще, чтобы показать, что мы крутые прогеры, сделаем для нашего репозитория два баджа с указанием статуса сборки и процента покрытия нашего кода тестами — Coverage Status Coverage Status


Зайдите и зарегистрируйтесь на coveralls.io с учетной записью github. После успешной OAuth авторизации, в меню слева нажмите Add repos. В появившемся списке переключите тумблер для нужного проекта:



В файл .travis.yml добавим следующий код:


after_success:
  - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js
  - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

Замените содержимое файла readme.md подставив свои значения в ссылки:


# Factory line manager
[![Travis][build-badge]][build]
[![Coveralls][coveralls-badge]][coveralls]

Awesome factory line manager!

[build-badge]: https://img.shields.io/travis/<Ваше имя на гите>/<Имя вашего проекта>/master.png?style=flat-square
[build]: https://travis-ci.org/<Ваше имя на гите>/<Имя вашего проекта>

[coveralls-badge]: https://img.shields.io/coveralls/<Ваше имя на гите>/<Имя вашего проекта>/master.png?style=flat-square
[coveralls]: https://coveralls.io/github/<Ваше имя на гитхабе>/<Имя вашего проекта>

Добавьте каталог coverage в .gitignore
В проекте для тестов, по умолчанию добавим проверку покрытия кода тестами. Для этого в файле package.json поправьте код до следующего вида:


...
"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom --coverage --collectCoverageFrom=src/**/*js --collectCoverageFrom=!src/registerServiceWorker.js",
...

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


Установим нужные dev зависимости для проекта и выгрузим все на github:


$ yarn add codecov.io coveralls --dev
$ git add .
$ git commit -m "Add coverage"
$ git push -u origin master

Результат после сборки проекта тревисом:



Заключение


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


Надеюсь пост был Вам полезен, исходный код можно взять тут, если понравилось плюсуйте, если нет — люто минусуйте. Желаю успешного кодерства. Мир всем!


P.S. Я специально использовал разные имена в папке проекта, имени репозитория и имени приложения в Heroku. API ключ перегенерировал. Если кому интересно, в своем проде я добавляю в CI пайплайн промежуточный сервис для сборки Docker контейнеров и публикую готовые контейнеры в swarm кластер.

Комментарии (19)


  1. Evgen52
    04.04.2018 12:15

    У нас из-за специфики продукта с подобным подходом есть одна проблема. Билд занимает 3-4 часа, smoke тесты ещё примерно час. Если в течение дня сделать хотя бы 4-5 коммитов, то всё это растянется очень надолго. Какие есть общепринятые практики или советы на этот случай? Пока из идей только запускать сборки не по коммитам, а настроить регулярные билды по расписанию (раз в день или в неделю, например, неважно).


    1. nightvich
      04.04.2018 13:18

      Разделяй и властвуй!
      Дроби и оптимизируй.


    1. wert_lex
      04.04.2018 14:32
      +1

      Ну это же не про CI вопрос, а про то как вы вообще разрабатываете.
      Разработчик поправил пару строчек кода и дальше ждёт 3-4 часа чтобы как-то увидеть не допустил ли он опечатку? Есть подозрение что нет.
      Вот и с CI то же самое. Инкрементальные билды, или проект на части надо резать и тестировать отдельные изменения.
      Ну а полный ребилдолл уже конечно как-то по расписанию по ночам или ближе к релизу, или как у вас это происходит обычно.


      1. Evgen52
        04.04.2018 15:57

        Это вопрос как раз про CI, а именно про то, как его правильно применять в подобной ситуации. У разработчика есть возможность вручную сделать неполный билд всего за 10-15 минут, на котором можно проверить частично какую-то ограниченную функциональность. Но это ручное девелоперское тестирование и оно происходит до коммита (вы же не заливаете непротестированный код), и тестируются в основном непосредственные изменения. Цель же CI в большей степени (в моём личном понимании) — регрессионное тестирование, то есть проверить, что косвенно ничего не поломалось в других местах. Это разные задачи, и они решаются разными методами и инструментами. В силу специфики продукта (не просто билд какого-то кода, но ещё упаковка кучи стороннего контента, который менеджится другими людьми и командами независимо, а потом интегрируется воедино), внешних зависимостей очень много и сломаться может не только по причине бага в коде. И проверить продукт в целом и быть уверенным в том, что он работоспособен, можно только после полного билда в том виде, в котором будут билдиться официальные пакеты.


        P.S. Я прекрасно понимаю, что процессы в описаной мной картине далеко неидеальны, и принцип "разделяй и влавствуй" верный, но давайте оставим это за пределами данного обсуждения и примем как данность :) Очень часто имеем то, что имеем, а переделать с нуля возможности и ресурсов у бизнеса просто нет, но улучшить малой кровью что-то жизненно необходимо. CI в общем правильная штука.


        1. alsii
          04.04.2018 16:17

          Но это ручное девелоперское тестирование и оно происходит до коммита

          Вы имеете в виду "до мерджа в основную ветку"?


          (вы же не заливаете непротестированный код)

          Если организовано централизованное тестирование, то конечно да. И не только коммитим (в feature-branch), но еще и пушим в общий репозиторий. Иначе откуда система CI возьмет код для тестирования?


          Я прекрасно понимаю, что процессы в описаной мной картине далеко неидеальны

          Меня искренне радует ваш оптимизм :-)


          Очень часто имеем то, что имеем, а переделать с нуля возможности и ресурсов у бизнеса просто нет

          Тогда просто подождите, когда у бизнеса не будет хватать возможностей и ресурсов для продолжения разработки этого супермонолита и переходите в другогй бизнес. Или не ждите...


          1. Evgen52
            04.04.2018 18:24

            Вы имеете в виду "до мерджа в основную ветку"?

            Да, в этом контексте можете так понимать. Имелось в виду, что до того, как этот код будет доступен другим разработчикам / билд-системам, настроенным под официальные билды. Под основной веткой тут может быть мастер или релизные бранчи, неважно.


            Меня искренне радует ваш оптимизм :-)

            Ну это скорее не оптимизм, а реализм :-) Любые задачи в жизни приходится рассматривать не только с точки зрения того, как оно должно быть в идеальном мире, а больше с точки зрения того, что у нас есть вагон и маленькая тележка других задач с разными приоритетами, майлстоунами, а ресурсы не резиновые, к сожалению. Приходится искать компромиссы и думать, что можно применить в краткосрочной перспективе, а что заложить на будущее. У меня вопрос был именно про первое, поскольку со вторым всё более-менее очевидно :)


            1. alsii
              05.04.2018 12:09

              почитал, подумал и удалил...


    1. amarao
      04.04.2018 17:03

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

      Плюс: быстро
      Минус: может пропустить баг из-за того, что предыдущие тесты поменяли окружение.

      Частично можно компенсировать, если переподнимать стейджинг автоматически каждой ночью.


      1. Evgen52
        04.04.2018 18:28

        Большое спасибо, да, это имеет смысл поисследовать :)


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


    1. ctacka
      04.04.2018 21:47

      Инкрементальные билды + отдельно настроенное тестирование для бранча. То есть разработчик, когда только создает ветку для какой-то своей новой фичи/исправления баги, заранее конфигурирует, какие тесты будут для нее запускаться. Если тесты сгруппировать сообразно каким-то внутренним частям, то это ускорит тестирование и уменьшит ложные срабатывания.
      А ещё можно оптимизировать сборку, потому что если нет способа получить возможность посмотреть на продукт через 15-20 минут после внесения изменения, то возникает очень много других проблем. Соответственно, кроме дроблений и инкрементальных билдов, можно посмотреть распределенную сборку, выделить под нее побыстрее железо, держать собранными самые "долгие" компоненты. Да и вообще проанализировать, что там конкретно занимает эти 4 часа, потому что зачастую из-за плохо настроенной сборки время возрастает многократно.


      1. Evgen52
        04.04.2018 22:22

        Инкрементальные билды + отдельно настроенное тестирование для бранча. То есть разработчик, когда только создает ветку для какой-то своей новой фичи/исправления баги, заранее конфигурирует, какие тесты будут для нее запускаться. Если тесты сгруппировать сообразно каким-то внутренним частям, то это ускорит тестирование и уменьшит ложные срабатывания.

        Вот это интересно, спасибо!


        А ещё можно оптимизировать сборку, потому что если нет способа получить возможность посмотреть на продукт через 15-20 минут после внесения изменения, то возникает очень много других проблем. Соответственно, кроме дроблений и инкрементальных билдов, можно посмотреть распределенную сборку, выделить под нее побыстрее железо, держать собранными самые "долгие" компоненты. Да и вообще проанализировать, что там конкретно занимает эти 4 часа, потому что зачастую из-за плохо настроенной сборки время возрастает многократно.

        Снова всё сводится к тому, что надо ускорять сборку, а это и так понятно. В этом направлении и работаем, нет ничего принципиально нерешаемого здесь, в данном конкретном случае есть даже реальные mid-term планы, как это сократить до ~1-2 часов, всё не так уж плохо, в дальнейшем можно и ещё что-то придумать) Задавая вопрос, я больше рассчитывал поразмышлять над тем, как использовать CI, имея длительные и тяжелые билды как данность, с которой приходится жить и мириться) Такие проекты бывают, и не всегда есть возможность их ускорить, а CI был бы полезен. Сократить время билда тем или иным образом до приемлемого уровня и свести задачу к привычной — вариант, без сомнения, правильный, верный, очевидный и прямолинейный, тут никто не спорит) Просто иногда может получиться, что это невозможно (читай "экономически невыгодно") сделать в силу каких-то объективных причин в разумное время и без чрезмерного усложнения, которое потом выльется в огромную стоимость поддержки. В общем, предлагаю направить мысли в другую сторону))) Кому-то может быть полезно)


        1. wert_lex
          05.04.2018 08:51

          Ну вот еще раз скажу, вопрос у вас не про CI, а про то как вы проект собираете. CI — это просто автоматический билд-инженер, которые делает ровно то, что мог бы сделать разработчик, только ему лень делать это на каждый чих, а CI понятие "лень" неведомо.


          Т.е. если у вас разработчик руками после изменения прогоняет только некоторый сабсет тестов — то же самое должен делать и CI.


          CI — это не абы какая магия. С переменным успехом он прекрасно заменяется хуком в гите (не надо так делать конечно).


    1. Macbet
      04.04.2018 22:19

      Попробуйте делать сборки только по специфичным веткам, например ветка daily и daily_build в daily весь день усиленно пушите а потом в daily_build MR с изменениями и сборочка )


      1. Evgen52
        04.04.2018 22:27

        А в чем это лучше простых регулярных daily билдов по расписанию? Если правильно вас понял, то получается то же самое, только ещё и с усложнением в виде дополнительной ветки или двух. Или я что-то не так понял?


  1. alatushkin
    04.04.2018 13:22
    +1

    Только токены лучше задавать не прямо в travis.yml а через переменные окружения в настройках Тревиса для репозитория. Чтоб не уплыли.


    1. bluetooth
      04.04.2018 16:38

      Автор это упомянул:

      Из явных минусов представленного подхода — Ваш API ключ Heroku будет лежать в открытом репозитории. После прохождения туториала, я настоятельно рекомендую Вам его обновить. Для реальных проектов ключи определяются через encrypted variables, подробнее Вы можете ознакомиться с этим здесь.


  1. sebres
    04.04.2018 16:54

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

    Вот как раз слияние то при CI совершенно не обязательно. И тот же травис умеет например как отдельные ветки так и PR и иже с ними "исполнять".
    CI помогает написать сценарий для автоматической "сборки", и исполнить его многократно на "чистой" системе, с компиляцией под разные компиляторы (например gcc/clang), исполнением на разных версиях интерпретаторах (python2.x, python3.x, pypy и т.д.), прогоном тест-кейсов (для разных целевых параметров), проверкой покрытия кода (aka code-coverage) и еще кучей всего — чего разработчик и/или тестер желают на него навесить (например сборкой чего-нибудь с опцией типа "mem-debug" для проверки на memory leaks после исполнения тестов, или проверкой на подключение к "чужому" коду, если то модульно и т.д.).
    Особливо дотошные товарищи умудряются даже проверку скорости исполнения (сравнение результатов до/после) воткнуть.


    Собственно слияние рабочих копий в общую основную ветвь — это, ИМХО, совершенно вторично здесь.


    1. nanshakov
      04.04.2018 18:04

      А как сделать фишечку со скоростью?


      1. sebres
        04.04.2018 18:37
        +2

        # get previous best results:
        wget .../my-srv/my-url/best-results.txt
        # measure previous:
        git checkout %{previous}%
        my-prog-perf-tests > before.txt
        # measure current:
        git checkout %{current}%
        my-prog-perf-tests > after.txt
        # create diff's between both versions:
        diff -u before.txt after.txt > test-perf.diff
        diff -u best-results.txt after.txt > test-perf-rel-best.diff

        Получаем что-нибудь вида test-perf.diff, ну и дальше травим это какому-нибудь анализатору:


        # analyze:
        my-perf-analyser -best best-results.txt -prev 1 < test-perf.diff > analysis.txt
        my-perf-analyser -best best-results.txt < test-perf-rel-best.diff >> analysis.txt
        # out in log of CI:
        cat analysis.txt
        # out the best-results back to the server:
        curl -F "file=@best-results.txt;filename=best-results" .../my-srv/my-url

        На самом деле вариантов очень и очень много...