Думаю многим знакомо понятие «борьба за staging», когда все разработчики одновременно за день до релиза хотят поделиться своими наработками, чтобы тестировщик их проверил как можно скорее и не пришлось всю ночь править баги, да? Кому интересно посмотреть как мы решаем данную проблему для RoR-проектов с помощью Capistrano прошу под кат.



Немного об инструментах


Каждый новый тикет мы делаем в отдельной ветке, как советует это git-flow. В качестве таск-менеджера/баг-трекера мы используем JIRA, поэтому номер тикета в JIRA = названию ветки. Jenkins CI мы используем не на полную мощь, пока только для деплоя кода на staging разработческой версии после мержа в нее для интеграционного тестирования.

Суть проблемы


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

Что кроме Capistrano?


Рассматривались различные варианты от шаринга рабочей машины с помощью ngrok до SaaS наподобие teatro. Первый вариант оказался неудобен, а второй отпал потому что не все проекты есть возможность отправить третьей стороне. Поэтому ввиду того, что все наши RoR-проекты деплоятся с помощью capistrano, было принято решение написать небольшое расширение, которое будет разворачивать проект из определенной ветки на свой хост (например, jira-123.example.com).

Процесс


Если коротко, то процесс разработки выглядит так: после выполнения тикета разработчик выливает его на демо-хост, после проверки тестировщиком создается мерж реквест, после закрытия которого Jenkins выливает будущий релиз на staging.

Что делает capistrano-demo


Все то, что делает разработчик при разработке – выливает код, накатывает миграции и запускает сторонние сервисы (sidekiq, resque и т.д.).

Данный плагин имеет ряд ограничений, самое больше это то, что он работает только для RoR-проектов, и только с git.

Конфигурация


# По умолчанию используется одна БД для всех хостов, если есть деструктивные миграции, 
# то гем дает возможность вручную вписать имя БД
set :demo_db, -> { demo_default_db }

# Хост, на поддомене которого будет создавать демо-хост
set :demo_host, -> { fetch(:application) }

# Команда которую нужно выполнить при рестарте хоста.
# Пример с одно из наших проектов, где требовалось перезагрузить unicorn, nginx и sidekiq:
#      invoke 'unicorn:restart'
#      invoke 'sidekiq:restart'
#      execute :sudo, :service, 'nginx restart'
#      execute :rake, 'cache:clear'
set :demo_restart_cmd, -> { raise 'You must specify "demo_restart_cmd" proc' }

# Папка, в которой лежат шаблоны конфигов, для конкретного окружения
# Пример:
#      File.expand_path("../../../../config/stages/#{fetch(:stage)}/templates", __FILE__)
set :demo_templates_dir, nil

# Хеш, для настройки какой шаблон куда положить после компиляции, шаблоны должны быть .erb
# Пример:
#       set :demo_templates_entries, [
#            {template: '/nginx.conf.erb', file: demo_path.join('config', 'nginx.conf')},
#            {template: '/database.yml.erb', file: demo_path.join('config', 'database.yml')},
#            {template: '/unicorn.rb.erb', file: demo_path.join('config', 'unicorn.rb')},
#            {template: '/settings.local.yml.erb', file: demo_path.join('config', 'settings.local.yml')}]
set :demo_templates_entries, []

Как пользоваться


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

  • demo:create — создание/обновление демо-хоста
  • demo:restart — перезагрузка
  • demo:destroy — Остановка процессов(настраивается с помощью before/after) и удаление директории.

Чтобы создать демо-хост нужно просто из рабочей директории набрать команду cap staging demo:create и все. По умолчанию будет предложено вылить текущую ветку.

Заключение


На данный момент самая большая проблема это долгая сборка ассетов, нужно заставить его не пересобирать всё, а только диф. А также, чья-то миграция может сломать чужие хосты, поэтому на staging мы держим чистую базу, на такой случай. Были попытки делать отдельную БД для каждой ветки, но тогда приходилось создавать либо пустую БД, либо копировать. Первый вариант плох тем, что приходилось бы забивать контент для тестирования, а второй — нет универсального средства для копирования данных для нескольких СУБД, но в будущем планируем сделать адаптеры для SqlLite, MySql и Postgres.

Наши наработки мы выложили в открытый доступ, поэтому каждый может ознакомиться и воспользоваться, pull request'ы приветствуются.

PS: В комментариях готов ответить на ваши вопросы, выслушать альтернативные варианты решения и конструктивную критику.
О чём бы вы еще хотели узнать из нашего блога

Проголосовало 30 человек. Воздержалось 16 человек.

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

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


  1. dannote
    31.08.2015 02:50

    Данные в БД можно каждый раз забивать при помощи db/seeds.rb, добавив в deploy.rb для роли demo строчку execute :rake, 'db:seed'. Asset-ы проще собирать локально один раз и синхронизировать rsync-ом. Для этого есть готовый gem.

    А Sidekiq и Resque для чего-то кроме отправки электронной почты используете?


    1. fc_arny
      31.08.2015 03:38

      Да, рассматривался вариант с seed (в том числе и с seedbank'ом), но не устроил нас по одной причине: на проекте сильно накрученный elasticsearch, и для проверки алгоритма(который видоизменялся по требованию заказчика со скоростью света) часто требовался большой объем данных, который хранить в репозитории не хотелось. Также использование одной БД позволило использовать одни и те же uploads-файлы, и как следствие на всех демо-хостах у нас полностью заполнен контент.

      capistrano-local-precompile пытался использовать, но тут тоже не все так просто получалось. Если приходиться деплоить на несколько окружений(демо-хосты + staging + production и т.д.), то для каждого окружения создается своя копия ассетов + магифест (это очень заметно, когда статика лежит на поддоменах) и получается, что за спринт накапливается огромное количество ассетов, хотя возможно я что-то делал не так и наверное надо сделать еще один подход и разобраться лучше.

      Sidekiq/Resque мы используем в основном для синхронизации данных со сторонними сервисами, которые часто могут отвечать очень долго или быть недоступны.


  1. AlexLeonov
    31.08.2015 13:22
    +1

    Вам осталось только использовать что-то вроде TeamCity, чтобы отслеживать изменения в ветках и инициировать сборку автоматически и вы откроете для себя волшебный мир Continious Integration )))


    1. fc_arny
      31.08.2015 13:43

      Уже открыли Jenkins, но пока не на полную мощь -) Скорее всего следующим шагом автоматизации будет создание демо-хоста именно на пуш + обновление тикета в JIRA, с плагином это делать удобнее, т.к. он позволяет — sidekiq запустить, переиндексацию или еще что-то, что в коде уже есть в виде rake-тасков или просто модулей.


      1. AlexLeonov
        31.08.2015 14:27

        Давно хочу описать такую связку. В двух местах уже удачно реализовал. Правда, на PHP, но какая разница?

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


        1. fc_arny
          31.08.2015 14:36

          Да, про shot в терминологии Badoo знаком, но еще до той статьи мы делали для PHP аналогичный механизм, но только на fabric, и оперировали понятием демо-хост, но наверное shot — лаконичнее.


          1. AlexLeonov
            31.08.2015 17:24
            +1

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


  1. leninlin
    31.08.2015 15:53

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