Деплой Ruby on Rails приложения в Docker при помощи Mina

  • Tutorial


Введение


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

Итак, что же такое Mina? Это инструмент для деплоя и автоматизации выполнения операций на удаленном сервере.Преимущество этого решения, в первую очередь, заключается в быстроте выполнения. Mina работает очень быстро, поскольку деплоит bash скрипт, который генерируется на удаленном сервере из вашего deploy.rb файла и в последствии выполняется.

Capistrano, к примеру, выполняет каждую команду отдельно, в своей ssh сессии, и поэтому уступает по скорости в разы, mina выполняет все в одном bash скрипте, который требует только одну сессию.

Требования


В этом руководстве, предполагается, что вы используете Ubuntu и у вас уже установлено следующее программное обеспечение:


Подготовка Rails приложения


Я не буду рассматривать подробно шаги создания Rails приложения, поскольку самым лучшим вариантом будет наличие у вас своего приложения, которое вы хотели бы задеплоить. Либо вы можете воспользоваться тестовым примером github.com/rails-guides/mina-deploy-example

Установка SSH / Rbenv / Ruby & Rails


Итак, у вас установлен Docker и имеется образ Ubuntu. Если все сделано верно, то при вызове команды docker images вы должны увидеть следующую информацию:

root@root:~$ docker images | grep ubuntu

ubuntu      latest      0ef2e08ed3fa      6 weeks ago      130MB

Запускаем данную последовательность операций в консоли:

docker run -d -it -p 2222:22 ubuntu:16.04
docker ps      //Скопируйте container_id
docker attach container_id

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

После того, как вы вошли в систему под пользователем root, вам необходимо получить последние обновления, установить ssh-сервер и запустить его.

apt-get update
apt-get install openssh-server
service ssh start

Добавляем в систему пользователя deployer, мы будем использовать его при настройке mina.

adduser deployer

Система попросит заполнить вас некоторые данные (first_name и т.д), эти шаги можно пропустить. В качестве пароля используйте 123. Далее подключаемся по ssh чтобы удостовериться, что все сделано правильно.

ssh -p 2222 deployer@0.0.0.0

! Если у вас появится предупреждение `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED`
Выполните удаление ключей из файла known_host с указанным сокетом (0.0.0.0:2222).

ssh-keygen -f "/home/USER/.ssh/known_hosts" -R [0.0.0.0]:2222

После этого переподключитесь еще раз и вы увидите следующее:

deployer@e2cad98fb69d:~$

SSH работает, отлично! Вернемся к нашему пользователю root, нам все еще необходимо установить rbenv / ruby / rails. В первую очередь нам потребуется git для установки rbenv.

apt-get install git

Проследуйте шагам описанным в официальной документации для установки.
github.com/rbenv/rbenv#installation

Для работы с ruby нам нужно предустановить следующие библиотеки:

apt-get install bzip2
apt-get install -y libssl-dev libreadline-dev zlib1g-dev
apt-get install build-essential

Мне понадобится ruby версии 2.3.1 и RoR 4.2.7.1. Не забудьте определить вашу Ruby версию глобально.

rbenv install -v 2.3.1
rbenv global 2.3.1
gem install rails -v 4.2.7.1

Установка Postgres / Nginx


apt-get install postgresql postgresql-contrib
service postgresql start

Определим пароль для пользователя postgres (все тот же 123)

su - postgres
psql
\password
create database mina_deploy_example; //заранее создадим базу данных

Nginx — веб-сервер и почтовый прокси-сервер, работающий на Unix-подобных операционных системах.

apt-get install nginx

После установки измените конфигурационный файл на нижеследующий.

nano /etc/nginx/sites-available/default

upstream mysite {
  server unix:///home/deployer/mina-deploy-example/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
  listen 80;
  listen [::]:80;

  root /home/deployer/mina-deploy-example/current/public;

  location ~ ^/assets/ {
      expires max;
      gzip_static on;
      gzip_vary on;
      add_header Cache-Control public;
      break;
  }

  location ~ ^/system/ {
      expires max;
      gzip_static on;
      gzip_vary on;
      add_header Cache-Control public;
      break;
  }

  location / {
      proxy_pass http://mysite; # match the name of upstream directive which is defined above
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  location ~ ^/(500|404|422).html {
      root /home/deployer/mina-deploy-example/current/public;
  }

  error_page 500 502 503 504 /500.html;
  error_page 404 /404.html;
  error_page 422 /422.html;

  client_max_body_size 4G;
  keepalive_timeout 10;
}

Настройка git репозитория


Для деплоя через mina вам необходимо создать репозиторий на github, настроить git пользователя, сгенерировать ssh ключ и добавить его в ваш репозиторий. Для примера я буду использовать github

ssh -p 2222 deployer@0.0.0.0
git config --global user.name "Some User"
git config --global user.email "someuser@example.com"
ssh-keygen -t rsa -b 4096 -C "someuser@example.com"
ssh -T git@github.com

Скопируйте публичный ключ

cat ~/.ssh/id_rsa.pub

Переходим на github → ваш репозиторий → settings → слева выбираем Deploy keys → жмем Add deploy key и вставляем скопированное значение.

Deploy при помощи mina


Переходим в директорию проекта, добавляем в Gemfile

gem 'mina'
gem 'mina-puma',  require: false
gem 'mina-nginx', require: false

В папке config создайте файл deploy.rb, он будет использоваться mina при деплое приложения. Файл конфигурации состоит из нескольких основных частей.

require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rbenv'
require 'mina/nginx'
require 'mina/puma'

set :application_name, 'mina-deploy-example'
set :domain, '127.0.0.1'
set :port, '2222'
set :user, 'deployer'
set :shared_dirs,  fetch(:shared_dirs, []).push('tmp', 'log', 'public/uploads', 'public/system')
set :shared_files, fetch(:shared_files, []).push('config/puma.rb', 'config/database.yml', 'config/secrets.yml')
set :deploy_to, '/home/deployer/mina-deploy-example'
set :repository, 'git@github.com:rails-guides/mina-deploy-example.git'
set :branch, 'master'

set :rails_env, 'production'

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

Данная часть определяет команды, которые будут выполненны при вызове mina setup.

task :environment do
  invoke :'rbenv:load'
end

task setup: :environment do
  command %{mkdir -p "#{fetch(:shared_path)}/log"}
  command %{chmod g+rx,u+rwx "#{fetch(:shared_path)}/log"}
  
  command %{mkdir -p "#{fetch(:shared_path)}/config"}
  command %{chmod g+rx,u+rwx "#{fetch(:shared_path)}/config"}

  command %{touch "#{fetch(:shared_path)}/config/puma.rb"}
  command %{touch "#{fetch(:shared_path)}/config/database.yml"}
  command %{touch "#{fetch(:shared_path)}/config/secrets.yml"}
  
  command %{mkdir -p "#{fetch(:shared_path)}/tmp/sockets"}
  command %{chmod g+rx,u+rwx "#{fetch(:shared_path)}/tmp/sockets"}
  command %{mkdir -p "#{fetch(:shared_path)}/tmp/pids"}
  command %{chmod g+rx,u+rwx "#{fetch(:shared_path)}/tmp/pids"}
end

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

task deploy: :environment do
  deploy do
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    
    on :build do
        invoke :'bundle:install'
        invoke :'rails:db_migrate'
        invoke :'rails:assets_precompile'
        invoke :'deploy:cleanup'
    end

    on :launch do
        invoke :'puma:restart'
    end
  end
end

В директории проекта, в папке config, вам необходимо создать файл puma.rb.

directory '/home/deployer/mina-deploy-example/current'
rackup '/home/deployer/mina-deploy-example/current/config.ru'
environment 'production'
daemonize true

pidfile '/home/deployer/mina-deploy-example/shared/tmp/pids/puma.pid'
state_path '/home/deployer/mina-deploy-example/shared/tmp/sockets/puma.state'

bind 'unix:///home/deployer/mina-deploy-example/shared/tmp/sockets/puma.sock'

activate_control_app 'unix:///home/deployer/mina-deployexample/shared/tmp/sockets/pumactl.sock'

Данный конфиг позволяет запустить сервер в фоновом режим и production окружении привязывая сервер к unix сокету. Хорошее объяснение команд для настройки puma вы можете найти в этом репозитории и официальной документации github.com/puma/puma/blob/master/examples/config.rb.

Создайте database.yml и secrets.yml в папке /home/deployer/mina-deploy-example/shared/config

production:
  host: localhost
  database: mina_deploy_example
  adapter: postgresql
  encoding: unicode
  username: postgres
  password: 123

production:
  secret_key_base: SECRET_KEY_BASE

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

gem install bundler
apt-get install ruby-dev
gem install json -v '1.8.6'
gem install pg -v '0.20.0'

apt-get install postgresql postgresql-contrib libpq-dev
gem install uglifier
apt-get install nodejs
apt-get install imagemagick

Далее деплоим приложение и запускаем puma

mina deploy
mina puma:start

Прокидываем ssh тонель на порт nginx’a

sudo ssh -f -N -L 80:localhost:80 deployer@0.0.0.0 -p 2222

При переходе по адресу http://localhost:80 вы увидите надпись New article. Если все шаги выполнены верно, то при создании новых статей и добавлении изображений все будет корректно отображаться.
Метки:
Поделиться публикацией
Комментарии 6
  • +4
    Capistrano, к примеру, выполняет каждую команду отдельно, в своей ssh сессии, и поэтому уступает по скорости в разы, mina выполняет все в одном bash скрипте, который требует только одну сессию.

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


    Какой смысл деплоить в контейнер? Лучше построить image. Похоже вы не разобрались с docker.


    Рекомендую либо выбросить докер и остаться на мине/капистране. Либо выкинуть мину и разобраться в докере.

    • 0
      Какой смысл деплоить в контейнер? Лучше построить image. Похоже вы не разобрались с docker.


      Спасибо за ваш отзыв, а вы точно правильно поняли суть руководства? Я использовал docker для имитации production окружения локально. Image кстати есть, https://hub.docker.com/r/dmitriystrukov2011/mina-deploy-tutorial/, если правильно понял вас.
      • 0

        @Elfet имеет в виду, что деплоить в докер-контейнер с целью получения image нерационально. Гораздо лучше подготовить к деплою все файлы, а затем единомоментно скопировать их при помощи Dockerfile.

        • +1
          внимание, у админов статья вызывает нервный смех и тик, в особенности, если используется какой-нибудь passenger и есть ассеты. Хотя хз, мож у кого прод без оного на пуме какой-нить, оно же с каждым годом всё асинхроннее_быстрее_выше_сильнее :)
          Дмитрий, docker, как бы принято считать stateless, зачем вы так жестоко стереотипы ломаете? Отдельным слоем «принято» код добавлять, и чтобы версия image менялась ;)
          • 0
            neumeika
            Я думаю, что Дмитрий пытался с помощью докера симитировать удаленный сервер. Docker в статье заканчивается ровно там, где открывается ssh-порт. Со следующего шага этот Docker container уже ведет себя как «обыкновенный» сервер, в который Дмитрий, собственно, деплоит приложение.

            В статье не идет речь о настройке Docker архитектуры для разворачивания Rails приложения.@dmitry-strukov думаю Вам стоит указать на это в начале вашей статьи.
    • +1
      В моем понимании делать как написано в статье нельзя, так как не понятно зачем здесь докер.
      По феншую нужно 3 контейнера:
      • с нужной версией руби
      • с нужной версией постгреса
      • с нужной версией nginx-а

      На основе контейнера с руби делается новый простым копированием исходников и приложение стартует через ENTRYPOINT.
      Все 3 контейнера вместе запускаются через docker-compose на девелоперской машине.

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