11/08/2018, 22:11

Use Capistrano to deploy Rails app

Summary Tạo một VPS với DigitalOcean (Ubuntu 14.04) Cài đặt VPS cho Rails app để deploy (RVM, Git, Nginx, Passenger/Unicorn) Deploy với Capistrano gem Tạo một VPS với DigitalOcean Hãy bắt đầu tạo VPS với Digital Ocean - dịch vụ với mức giá và chất lượng rất tốt. Chỉ với ...

Summary

  1. Tạo một VPS với DigitalOcean (Ubuntu 14.04)
  2. Cài đặt VPS cho Rails app để deploy (RVM, Git, Nginx, Passenger/Unicorn)
  3. Deploy với Capistrano gem

Tạo một VPS với DigitalOcean

Hãy bắt đầu tạo VPS với Digital Ocean - dịch vụ với mức giá và chất lượng rất tốt.

Chỉ với $10/month($$.015/hour) bạn đã có một droplet với 1GB RAM, 30GB SSD Disk, 2TB transfer...

Chọn plan cho VPS của bạn:

Droplet Name

Chọn region của server:

Droplet Local

Chọn hệ điều hành:

Droplet Create

Cài đặt VPS cho Rails app để deploy

Sau khi tạo VPS thành công bạn có thể dùng ssh hoặc ftp (Filezilla) để truy cập vào VPS, ở đây mình dùng ssh:

Nếu chưa tạo SSH key ở local, hãy đọc bài viết này: https://help.github.com/articles/generating-ssh-keys/

Ở Local Terminal:

ssh root@your_droplet_ip

Nếu bạn gặp trường hợp tương tự như thế này (do identity của host bị thay đổi):

[XX@XX ~]$ ssh root@pong
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
...

Hãy ssh-keygen -R your_droplet_ip để remove identity cũ của host khỏi known_hosts

Sau khi bạn đã đăng nhập vào VPS với root user, hãy tạo một user để bắt đầu làm việc với app:

groupadd -g 2000 dev
adduser --gid 2000 --uid 2100 deploy
sudo visudo

Cấp quyền cho group dev bằng cách thêm vào

%dev ALL=(ALL:ALL) ALL

Login vào deploy user: sudo su - deploy

Tạo SSH key cho user deploy, sau đó add SSH key vào github repo: https://help.github.com/articles/generating-ssh-keys/

Đăng nhập user deploy bằng ssh-key:

Sử dụng ssh-copy-id để làm việc này, nếu bạn đang dùng Mac, chạy brew install ssh-copy-id để cài đặt ssh-copy-id

Chạy ở local: ssh-copy-id deploy@your_droplet_ip

Cài đặt Ruby

Cài đặt dependencies cho Ruby:

sudo apt-get update
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties

Cài đặt RVM

sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev

gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
curl -L https://get.rvm.io | bash -s stable

Update script và cài Ruby:

source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 2.1.0
rvm use 2.1.0 --default
gem install bundler

Cài đặt Nginx + Passenger/Unicorn

Nginx + Passenger

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo apt-get install -y apt-transport-https ca-certificates

Add Passenger APT repository

sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update

sudo apt-get install -y nginx-extras passenger

Sau khi cài đặt thành công passenger, chạy sudo service nginx start hoặc sudo service nginx để xem usage:

Usage: nginx {start|stop|restart|reload|force-reload|status|configtest|rotate|upgrade}

Bạn có thể kiểm tra process bằng cách: ps aux | grep nginx

Cấu hình Nginx đối với Passenger

sudo nano /etc/nginx/nginx.conf

Uncomment hai dòng như bên dưới:

##

# Phusion Passenger config

##

# Uncomment it if you installed passenger or passenger-enterprise

##

include /etc/nginx/passenger.conf;

Edit file sudo nano /etc/nginx/sites-enabled/default như sau:

server {
        listen 80 default_server;

        passenger_enabled on;
        root /var/www/your_app/current/public;
}

hoặc tạo file sau với nội dung như trên

sudo rm /etc/nginx/sites-enabled/default
sudo nano /etc/nginx/sites-available/your_app
sudo ln -s /etc/nginx/sites-available/your_app /etc/nginx/sites-enabled/default

Chạy sudo service nginx restart để khởi động lại Nginx.

Nginx + Unicorn

Nginx: sudo apt-get -y install nginx

Cấu hình Nginx với Unicorn Edit file sudo nano /etc/nginx/sites-enabled/default như sau:

upstream backend-unicorn {
  server unix:/var/www/your_app/current/tmp/sockets/unicorn.sock;
}

server {
  listen 80;

  root /var/www/your_app/current/public;

# Make site accessible from http://localhost/

  server_name localhost;

  location / {
    try_files $uri @webapp;
  }
  location @webapp {
    proxy_redirect     off;
    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_read_timeout 100;
    proxy_pass http://backend-unicorn;
  }
}

Cài đặt MySQL

sudo apt-get install mysql-server mysql-client libmysqlclient-dev

Cài đặt NodeJS (JavaScript runtime)

sudo apt-get install nodejs

Deploy với Capistrano gem

Nếu bạn sử dụng passenger làm Rails server:

Thêm vào Gemfile, sau đó bundle install

gem 'capistrano-rails'
gem 'capistrano-bundler'
gem 'capistrano-rvm'
gem 'capistrano-passenger'

Nếu bạn sử dụng unicorn làm Rails server:

gem 'capistrano-rails'
gem 'capistrano-bundler'
gem 'capistrano-rvm'
gem "unicorn"
gem "capistrano3-unicorn"
# in your_app local folder
bundle exec cap install

More: https://github.com/capistrano/capistrano

Capfile

# Load DSL and set up stages
require 'capistrano/setup'

# Include default deployment tasks
require 'capistrano/deploy'

# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#   https://github.com/capistrano/passenger
#
require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
require 'capistrano/bundler'
require 'capistrano/rails'
require 'capistrano/passenger'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

deploy.rb

# config valid only for Capistrano 3.1
lock '3.4.0'

set :application, "your_app"
set :repo_url, "git@github.com:your_account/your_app.git"

# Default branch is :master
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# Default deploy_to directory is /var/www/my_app
set :deploy_to, "/var/www/your_app"

# Default value for :scm is :git
set :scm, :git

# Default value for :format is :pretty
set :format, :pretty

# Default value for :log_level is :debug
set :log_level, :debug

# Default value for :pty is false
set :pty, true

# Nếu sử dụng unicorn
set :pid_file, "#{shared_path}/tmp/pids/unicorn.pid"
set :unicorn_rack_env, ENV["RAILS_ENV"] || "production"
set :unicorn_config_path, "#{current_path}/config/unicorn.rb"

# Default value for :linked_files is []
# set :linked_files, fetch(:linked_files, []).push('.env')

# Default value for linked_dirs is []
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system',  'public/uploads', 'public/assets')

# Default value for default_env is {}
set :default_env, {
  rails_env: ENV["RAILS_ENV"]
}

# Default value for keep_releases is 5
set :keep_releases, 3

namespace :deploy do

  desc "seed database"
  task :seed do
    on roles(:db) do |host|
      within "#{release_path}" do
        execute :rake, "db:seed"
      end
    end
  end
  after :migrate, :seed
end

Rails.root/config/unicorn.rb

rails_env = ENV["RAILS_ENV"] || "production"
num_workers = ENV["NUM_UNICORN_WORKERS"]
worker_processes (num_workers ? num_workers.to_i : 3)
app_name = "matee"

app_directory = "/var/www/#{app_name}/current"
working_directory app_directory # available in 0.94.0+

listen "#{app_directory}/tmp/sockets/unicorn.sock", backlog: 128

timeout 10000

pid "#{app_directory}/tmp/pids/unicorn.pid"

stderr_path "#{app_directory}/log/unicorn_#{rails_env}.log"
stdout_path "#{app_directory}/log/unicorn_#{rails_env}.log"

preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
  GC.copy_on_write_friendly = true

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "#{app_directory}/Gemfile"
end

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end

  sleep 1
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

Bạn có thể cấu hình file config/deploy/production.rb như sau

role :app, %w{deploy@your_droplet_ip}
role :web, %w{deploy@your_droplet_ip}
role :db,  %w{deploy@your_droplet_ip}

server 'your_droplet_ip', user: 'deploy', roles: %w{web app db}

Let's deploy

Tạo và phân quyền các thư mục cho app của bạn:

# in vps, root user
deploy_to=/var/www/your_app
mkdir -p ${deploy_to}
mkdir ${deploy_to}/{releases,shared}
chown -R deploy:dev ${deploy_to}
chmod 2775 ${deploy_to}

Tao file database.yml

su - deploy
sudo nano /var/www/your_app/shared/config/database.yml

Cấu hình mysql cho app của bạn như sau:

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password: your_root_password

production:
  <<: *default
  database: your_app_production

Tạo file secrets.yml

sudo nano /var/www/your_app/shared/config/secrets.yml

Điền vào file với nội dung như sau:

production:
  secret_key_base: your_production_key_here

Bạn có thể tạo secret key bằng cách chạy rake secret ở your_app local folder

Tạo database:

mysql -u root -p
CREATE USER 'your_user'@'localhost' IDENTIFIED BY 'mypass';
exit
mysql -u your_user -p
create database your_app_production;

In your_app local folder:

RAILS_ENV=production cap production deploy

Nhập vào branch muốn deploy: mặc định là branch hiện tại của bạn

Check your deploy:

# in vps

cd /var/www/your_app
ll

Bạn sẽ thấy thư mục current symlink đến bản releases 20141127094637 vừa deploy xong:

total 28
drwxr-sr-x 6 deploy deploy 4096 Nov 27 04:48 ./
drwxr-xr-x 3 root   root   4096 Nov 27 03:30 ../
lrwxrwxrwx 1 deploy deploy   38 Nov 27 04:48 current -> /var/www/your_app/releases/20141127094637/
drwxrwsr-x 5 deploy deploy 4096 Nov 27 04:46 releases/
drwxrwsr-x 7 deploy deploy 4096 Nov 27 04:33 repo/
-rw-rw-r-- 1 deploy deploy   70 Nov 27 04:48 revisions.log
drwxrwsr-x 2 deploy deploy 4096 Nov 27 03:36 rvm1scripts/
drwxrwsr-x 6 deploy deploy 4096 Nov 27 04:33 shared/

Tiếp tục:

cd current
ll config

Symlink của database.yml và secrets.yml:

total 44
drwxrwsr-x  7 deploy deploy 4096 Nov 27 04:46 ./
drwxrwsr-x 14 deploy deploy 4096 Nov 27 04:48 ../
-rw-rw-r--  1 deploy deploy 1436 Nov 13 09:09 application.rb
-rw-rw-r--  1 deploy deploy  170 Nov 13 09:09 boot.rb
lrwxrwxrwx  1 deploy deploy   41 Nov 27 04:46 database.yml -> /var/www/your_app/shared/config/database.yml
-rw-rw-r--  1 deploy deploy  150 Nov 13 09:09 environment.rb
drwxrwsr-x  2 deploy deploy 4096 Nov 13 09:09 environments/
drwxrwsr-x  2 deploy deploy 4096 Nov 13 09:09 initializers/
drwxrwsr-x  5 deploy deploy 4096 Nov 13 09:09 locales/
drwxrwsr-x  2 deploy deploy 4096 Nov 13 09:09 routes/
-rw-rw-r--  1 deploy deploy 1653 Nov 13 09:09 routes.rb
lrwxrwxrwx  1 deploy deploy   40 Nov 27 04:46 secrets.yml -> /var/www/your_app/shared/config/secrets.yml
drwxrwsr-x  2 deploy deploy 4096 Nov 13 09:09 settings/
-rw-rw-r--  1 deploy deploy    0 Nov 13 09:09 settings.yml

Sau khi deploy xong bạn có thể dùng trình duyệt truy cập vào app: http://your_droplet_ip

Update: Đối với các file chứa các thông tin bí mật như secrets.yml, database.yml và devise.rb (nếu có) các bạn có thể sử dụng biến môi trường thay vì symlink như cách ở trên. Hai gem được sử dụng phổ biến là: dotenv và figaro

Các bài viết tham khảo:

  1. https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-virtual-hosts-server-blocks-on-ubuntu-12-04-lts--3
  2. https://www.phusionpassenger.com/documentation/Users guide Nginx.html#rubygems_generic_install
  3. http://capistranorb.com/documentation/getting-started/authentication-and-authorisation/
  4. https://www.digitalocean.com/community/tutorials/how-to-set-up-automatic-deployment-with-git-with-a-vps
  5. http://capistranorb.com/documentation/getting-started/flow/
0