Деплой проектов на Capistrano 3

Обновить проект на боевом сервере и ничего не сломать!

Деплой проектов на Capistrano 3

Обновить проект и ничего не сломать

Александр Паньшин

Проблема

Надо обновлять проект на боевом сервере (или серверах)

Это надо делать часто.

Дедовский способ

            Остановить какие-нибудь воркеры
            Обновить код
            Установить зависимости
            Смигрировать БД
            Запустить какие-нибудь воркеры
            Пересобрать какие-нибудь картинки и стили
        

Чем это плохо?

Большое время простоя

Все то время, пока выполняется обновление, сервер простаивает или отвечает 500 ошибками.

Чем это плохо?

Люди ошибаются

Если посадить человека выполнять 1000 раз одну и ту же последовательность команд, он ошибется. И не один раз. А если у вас несколько серверов? А если сотни?

Надо бы все это дело как-нибудь автоматизировать...

Все уже давно автоматизировано, берите и пользуйтесь:)

Встречайте, Capistrano

Capistrano — это утилита для автоматизации серверных команд и деплоя, написанная на Ruby.

Подходит для деплоя проектов на любом языке.

Установка

По правильному, нам надо будет установить rvm, ruby 2.2, bundler и создать gemset для capistrano.

Но мы пойдем простым путем.

Установка. Ruby

            aptitude install ruby
        

Убедитесь, что установится ruby версии не ниже 1.9.3 !

Установка. Bundler.

            gem install bundler
        

Убедитесь, что установится bundler версии не ниже 1.9.0

Установка. Gemfile.

source 'https://rubygems.org'
ruby '~> 1.9.3'
#ruby-gemset=capistrano
gem 'capistrano', '~> 3.3.5'
gem 'capistrano-composer'
gem 'bundler', '~> 1.9.0'
gem 'rake', '~> 10.0.0'
        

Установка. Capistrano

bundle

Эта команда установит все зависимости из Gemfile. Да-да, с bundler целиком и полностью слизан Composer.

Структура файлов и папок

bundle exec cap install
            Capfile
            config
              deploy
                  production.rb
                  development.rb
              deploy.rb
        

Файл настроек Capistrano

            Capfile
            config
              deploy
                  production.rb
                  development.rb
              deploy.rb
        

Описание процесса деплоя

            Capfile
            config
              deploy
                  production.rb
                  development.rb
              deploy.rb
        

Stages

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

Это бывает полезно, если у вас есть сервер разработки, сервер для тестирования и боевой сервер.

Все это можно разрулить при помощи capistrano stages.

Настройки конктреного stage

            Capfile
            config
              deploy
                  production.rb
                  development.rb
              deploy.rb
        

Настраиваем Capistrano. Capfile

            set :deploy_config_path, 'application/config/deploy.rb'
            set :stage_config_path, 'application/config/deploy'
            require 'capistrano/setup'
            require 'capistrano/deploy'
            require 'capistrano/composer'
            Dir.glob('lib/capistrano/tasks/*.rake').each {
             |r| import r
            }
        

Путь до deploy.rb

            set :deploy_config_path, 'application/config/deploy.rb'
            set :stage_config_path, 'application/config/deploy'
            require 'capistrano/setup'
            require 'capistrano/deploy'
            require 'capistrano/composer'
            Dir.glob('lib/capistrano/tasks/*.rake').each {
             |r| import r
            }
        

Путь до папки с stages

            set :deploy_config_path, 'application/config/deploy.rb'
            set :stage_config_path, 'application/config/deploy'
            require 'capistrano/setup'
            require 'capistrano/deploy'
            require 'capistrano/composer'
            Dir.glob('lib/capistrano/tasks/*.rake').each {
             |r| import r
            }
        

Подключаем стандартные таски

            set :deploy_config_path, 'application/config/deploy.rb'
            set :stage_config_path, 'application/config/deploy'
            require 'capistrano/setup'
            require 'capistrano/deploy'
            require 'capistrano/composer'
            Dir.glob('lib/capistrano/tasks/*.rake').each {
             |r| import r
            }
        

Подключаем специфичные задачи

            set :deploy_config_path, 'application/config/deploy.rb'
            set :stage_config_path, 'application/config/deploy'
            require 'capistrano/setup'
            require 'capistrano/deploy'
            require 'capistrano/composer'
            Dir.glob('lib/capistrano/tasks/*.rake').each {
             |r| import r
            }
        

Проверяем

cap -T

Должен выпасть список доступных команд.

Структура папок на сервере

            current -> releases/201505131954600
            releases
              201505131954600
            repo (VCS-related data)
            revisions.log
            shared
        

Симлинк на текущую версию

            current -> releases/201505131954600
            releases
              201505131954600
            repo (VCS-related data)
            revisions.log
            shared
        

Папка с релизами

            current -> releases/201505131954600
            releases
              201505131954600
            repo (VCS-related data)
            revisions.log
            shared
        

Репозиторий

            current -> releases/201505131954600
            releases
              201505131954600
            repo (VCS-related data)
            revisions.log
            shared
        

Лог релизов

            current -> releases/201505131954600
            releases
              201505131954600
            repo (VCS-related data)
            revisions.log
            shared
        

Папка с данными, общими для релизов

            current -> releases/201505131954600
            releases
              201505131954600
            repo (VCS-related data)
            revisions.log
            shared
        

Capistrano создает символические ссылки на папки и файлы из shared в current.

Роли

Capistrano умеет деплоить сразу на несколько как равнозначных, так и разных серверов.

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

application/config/production.rb

            server 'example.com', roles: %w(web app)
            server 'mysql.example.com', roles: %w(db)
        

Процесс деплоя

cap production deploy
  1. deploy:starting. Убедимся, что все хорошо.
  2. deploy:updating. Создаем папку с релизом, копируем туда код.
  3. deploy:publishing. Меняем симлинк current на новый релиз.
  4. deploy:finishing. Подчищаем старые релизы, пишем в лог.

После каждого действия выполняется хук deploy:*ed (started, updated, published, finished).

Процесс выполнения действия

  1. before deploy:starting. Событие перед началом шага.
  2. deploy:starting. Шаг.
  3. after deploy:starting. Событие после шага.

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

Процесс деплоя Вачанги

Что из этого умеет Capistrano из коробки?

  1. git pull

Что-то не густо...

Ну да, capistrano из коробки не умеет почти ничего. И это хорошо:)

Зато ее можно научить чему угодно.

Попробуем научить?

application/config/deploy.rb

            namespace :workers do
              task :stop do
                  on roles(:web) do execute '/var/www/stopall' end
              end
            end
        

cap production workers:stop

Внедряем задачу в процесс деплоя

application/config/deploy.rb

            after "deploy:starting", "workers:stop"
        

Короткий синтаксис

            after "deploy:finished", :notify_finish do
              run_locally do
                  execute "curl -X POST --data-urlencode 'payload={\"text\": \"Чот задеплоилось!\", \"channel\": \"#general\", \"username\": \"Capistrano\", \"icon_url\": \"http://alex-panshin.me/CapistranoLogo.png\"}' https://hooks.slack.com/services/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"
              end
            end
        

on roles и run_locally

Запустить что-нибудь внутри папки с релизом

            namespace :test do
              task :phpunit do
                  on roles(:web) do 
                      execute("cd #{release_path} && vendor/bin/phpunit -c phpunit.xml.dist")
                  end
              end
            end
        

Больше подробностей про execute

Можно найти в документации к SSHKit.

Конфигурация деплоя

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

Попробуем пробежаться по самым важным.

Конфигурация деплоя

  1. :application. Название приложения.
  2. :repo_url. URL вашего репозитория.
  3. :branch. Ветка, откуда получать код.
  4. :scm. Ваша VCS. По умолчанию, git.
  5. :deploy_to. Путь, куда выкладывать проект
  6. :keep_releases. Количество релизов, которые capistrano будет хранить.

Конфигурация деплоя

  1. :ssh_options. Параметры подключения по ssh.
  2. :composer_install_flags. Параметры запуска Composer.

Разумеется, это далеко не все. Подробности в официальной документации.

Сохраняем файлы между релизами

            set :linked_files, fetch(:linked_files).push(
             'application/config/amqp.php',
             'application/config/apn.php'
            )
        

Сохраняем папки между релизами

            set :linked_dirs, fetch(:linked_dirs).push(
             'application/certificates',
             'application/cache',
             'application/backups'
            )
        

Авторизация

У нас две большие проблемы:

Авторизуемся на сервере

В своем любимом терминале выполняем команды:

            ssh-keygen -t rsa -C 'me@example.com'
            ssh-add
        

Авторизуемся на сервере

application/config/production.rb

            set :ssh_options {
              key: %w(~/.ssh/id_rsa),
              auth_methods: %w(publickey password)
            }
        

Авторизуем сервер на гитхабе

Воспользуемся SSH Agent forwarding. Мы даем право серверу воспользоваться тем же ключом для авторизации на третьей стороне.

SSH это умеет из коробки, ничего настраивать не надо.

Авторизуем сервер на гитхабе

application/config/production.rb

            set :ssh_options {
              key: %w(~/.ssh/id_rsa),
              forward_agent: true,
              auth_methods: %w(publickey password)
            }
        

Подробности про авторизацию

Можно почитать в официальной документации.

Запускаем процесс!

ssh-add && cap %stage-name% %command:name%

Примеры

Больше примеров

Чтиво на ночь:

Документация на capistrano.