12/08/2018, 14:29

Deploy rails app with unicorn and nginx with Capistrano

Hôm nay mình sẽ hướng dẫn các bạn từng bước autodeploy rails app với capistrano bắt đầu từ con số 0. Trước hết, các bạn cần hiểu rõ vai trò của từng thành phần trong bài viết này: Rails app: là một app được viết bằng ruby on rails Unicorn: là một app server hỗ trợ rails - tương tự puma hay ...

Hôm nay mình sẽ hướng dẫn các bạn từng bước autodeploy rails app với capistrano bắt đầu từ con số 0.

Trước hết, các bạn cần hiểu rõ vai trò của từng thành phần trong bài viết này:

  • Rails app: là một app được viết bằng ruby on rails
  • Unicorn: là một app server hỗ trợ rails - tương tự puma hay passenger. nó nằm giữa rails app và web server.
  • Nginx: là web server
  • Mysql: database cho rails app
  • Capistrano: tool tự động deploy từ local tới server remote

Để làm được thì trước hết chúng ta phải cần 2 server: 1 local và 1 remote.

Chuẩn bị server local:

  • user: deploy
  • environment: ruby on rails, unicorn, nginx, mysql, capistrano

server remote:

  • user: deploy
  • environment: ruby on rails, unicorn, nginx, mysql

1. Đầu tiên mình sẽ đi cấu hình trên server local

Về việc tạo user thì chắc không cần thiết phải viết ở đây vì nó quá đơn giản rồi phải không nào. Chỉ lưu ý với các bạn là phải tạo ssh-key cho user deploy để ta thực hiện các bước ở phía sau nhé. $ ssh-keygen -t rsa

1.1 Cài ruby on rails

  • Cài các lib cần:

    $ 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 libffi-dev nodejs

  • Sử dụng rbenv để cài ruby (các bạn có thể cài bằng rvm hoặc từ source)

$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv

#add environment
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc

$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
$ exec $SHELL
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

#add environment
$ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
$ exec $SHELL

#install ruby 2.3.3
$ rbenv install 2.3.3
$ rbenv global 2.3.3
$ ruby –v

#install bundler
$ gem install bundler

$ rbenv rehash
  • Cài Rails:
#install nodejs
$ curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash –
$ sudo apt-get install -y nodejs

#install rail 5.0.0.1
$ gem install rails -v 5.0.0.1
$ rbenv rehash

1.2 Cài Mysql

`$ sudo apt-get install mysql-server mysql-client libmysqlclient-dev`
  • Khởi tạo 1 rails app “cap_test” mới sử dụng mysql:

    $ rails new cap_test –d mysql

  • Tạo db cho rail: Ở đây mình cho hết code của rails vào folder current:

    $ cd cap_test

    $ mkdir –p current

    $ mv * current

    $ cd current

    $ vim config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password: ******
  socket: /var/run/mysqld/mysqld.sock

#  database: cap_test

development:
  <<: *default
  database: cap_test_development

ở bước này, ta điền username: root và password khi cài mysql lúc nãy. Sau đó tạo db.

    $ rake db:create
    Created database 'cap_test_development'

1.3 Cài unicorn và capistrano

Ta sử dụng Gemfile vừa mới sinh ra cùng rails app để cài unicorn:

`$ vim Gemfile`

Thêm 2 gem vào cuối cùng:

gem 'unicorn'
group :development do
  gem 'capistrano-rails', require: false
  gem 'capistrano-bundler', require: false
  gem 'capistrano-rbenv'
  gem 'capistrano', '~> 3.4.0'
end

Lưu ý: khi chạy với rails 5.0.0.1, trong gemfile mặc định mở thêm puma, vì thế ta cần bỏ dòng này đi#gem 'puma', '~> 3.0'

Và tiến hành cài đặt:

$ Bundle install
Using spring-watcher-listen 2.0.1
Using actionview 5.0.1
Using capistrano-bundler 1.2.0
Using capistrano-rbenv 2.1.0
Using actionpack 5.0.1
Using capistrano-rails 1.2.1
Using actioncable 5.0.1
Using actionmailer 5.0.1
Using railties 5.0.1
Using sprockets-rails 3.2.0
Using coffee-rails 4.2.1
Using jquery-rails 4.2.1
Using web-console 3.4.0
Using rails 5.0.1
Using sass-rails 5.0.6
Bundle complete! 19 Gemfile dependencies, 71 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

Sau khi cài xong unicorn, ta cấu hình:

$ vim config/unicorn.rb

#đây là thư mực app của bạn
root = "/home/deploy/cap_test/current"
working_directory root
#pid của unicorn khi chạy
pid "#{root}/tmp/pids/unicorn.pid"
#log
stderr_path "#{root}/log/unicorn.error.log"
stdout_path "#{root}/log/unicorn.access.log"

#chạy với sock
listen "#{root}/shared/sockets/unicorn.sock"
worker_processes 2
timeout 30

và tạo script start/stop unicorn $ sudo vim /etc/init.d/unicorn

#!/bin/sh
### BEGIN INIT INFO
# Provides:          unicorn
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Manage unicorn server
# Description:       Start, stop, restart unicorn server for a specific application.
### END INIT INFO
set -e

TIMEOUT=${TIMEOUT-60}
APP_ROOT=/home/deploy/cap_test/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="cd $APP_ROOT; ~/.rbenv/bin/rbenv exec bundle exec unicorn -D -c $APP_ROOT/config/unicorn.rb -E development"
AS_USER=deploy
set -u

OLD_PIN="$PID.oldbin"

sig () {
  test -s "$PID" && kill -$1 `cat $PID`
}

oldsig () {
  test -s $OLD_PIN && kill -$1 `cat $OLD_PIN`
}

run () {
  if [ "$(id -un)" = "$AS_USER" ]; then
    eval $1
  else
    su -c "$1" - $AS_USER
  fi
}

case "$1" in
start)
  sig 0 && echo >&2 "Already running" && exit 0
  run "$CMD"
  ;;
stop)
  sig QUIT && exit 0
  echo >&2 "Not running"
  ;;
force-stop)
  sig TERM && exit 0
  echo >&2 "Not running"
  ;;
restart|reload)
  sig HUP && echo reloaded OK && exit 0
  echo >&2 "Couldn't reload, starting '$CMD' instead"
  run "$CMD"
  ;;
upgrade)
  if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
  then
    n=$TIMEOUT
    while test -s $OLD_PIN && test $n -ge 0
    do
      printf '.' && sleep 1 && n=$(( $n - 1 ))
    done
    echo

    if test $n -lt 0 && test -s $OLD_PIN
    then
      echo >&2 "$OLD_PIN still exists after $TIMEOUT seconds"
      exit 1
    fi
    exit 0
  fi
  echo >&2 "Couldn't upgrade, starting '$CMD' instead"
  run "$CMD"
  ;;
reopen-logs)
  sig USR1
  ;;
*)
  echo >&2 "Usage: $0 <start |stop|restart|upgrade|force-stop|reopen-logs>"
  exit 1
  ;;
Esac

Các bạn chỉ cần quan tâm tới các thông tin:

#đường dẫn root của app
APP_ROOT=/home/deploy/cap_test/current

#pid unicorn (như  trong file unicorn.rb vừa thiết lập)
PID=$APP_ROOT/tmp/pids/unicorn.pid

#lệnh chạy unicorn với môi trường development
CMD="cd $APP_ROOT; ~/.rbenv/bin/rbenv exec bundle exec unicorn -D -c $APP_ROOT/config/unicorn.rb -E development"

#chạy với user nào
AS_USER=deploy

Và cuối cùng là phân quyền chạy cho script

$ sudo chmod 755 /etc/init.d/unicorn

1.4 Tiếp theo ta sẽ cài và cấu hình nginx

  • cài nginx

$ sudo apt-get install nginx

  • tạo file cấu hình để nginx gọi tới unicorn

$ sudo vim /etc/nginx/sites-available/cap_test

  upstream unicorn {
  server unix:/home/deploy/cap_test/current/shared/sockets/unicorn.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  server_name _;
  root /home/deploy/cap_test/current/public;

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

  try_files $uri/index.html $uri @unicorn;
  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 10;
}

Trong này cần chú ý tới:

server unix:/home/deploy/cap_test/current/shared/sockets/unicorn.sock fail_timeout=0;
Là dòng nginx kết nối tới unicorn thong qua file .sock

root /home/deploy/cap_test/current/public;
là folder public của rails app

và enable file này:

$ sudo ln -s /etc/nginx/sites-available/cap_test /etc/nginx/sites-enabled/cap_test

$ sudo rm –rf /etc/nginx/sites-enabled/default

  • Vậy là đã xong cấu hình trên local. Chúng ta có thể test thử:

$ sudo /etc/init.d/nginx restart

$ sudo /etc/init.d/unicorn start

và check bằng web: http://<ip_server_local> . Cụ thể ở đây là: http://192.168.13.212/

Screenshot_1.png

1.5 Capistrano

Vậy là chúng ta đã deploy cơ bản 1 server với rails, unicorn, mysql và nginx. Giờ chúng ta sẽ đi tiếp tới Capistrano. Như bước ở trên mình đã hướng dẫn add thêm gem Capistrano vào Gemfile cùng với unicorn. Sau khi cài Capistrano bằng bundle install , ta khởi tạo các file cấu hình cho việc deploy:

`$ Bundle exec cap install`
├── Capfile
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   └── deploy.rb
└── lib
    └── capistrano
            └── tasks

Capistrano sẽ tạo ra các file với cấu trúc trên

  • Capfile: Đây là file định nghĩa các thư viện con của capistrano mà nó sẽ dùng trong việc deploy.
# 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/assets'
 require 'capistrano/rails/migrations'
# 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 }
  • config/deploy.rb Là file cấu hình chung cho việc deploy
# config valid only for current version of Capistrano
lock '3.4.1'

#tên application
set :application, "cap_test"

set :scm, :git

#set repo trên git
set :repo_url, "git@github.com:cuongtv2004/cap_test.git"

#deploy tới folder nào trên server remote
set :deploy_to, "/home/deploy/cap_test"

#branch trên github. Mặc định là master
set :branch, "master"

set :pty, false
set :format, :pretty

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

# Default deploy_to directory is /var/www/my_app_name
# set :deploy_to, '/var/www/my_app_name'

# 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

# Default value for :linked_files is []
# set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')

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

# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }

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

namespace :deploy do

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end

end
  • ok. Tiếp theo là tới thư mục config/deploy: Là thư mục chứa các cấu hình cụ thể cho từng môi trường deploy, mặc định lệnh trên sẽ tạo cho mình 2 môi trường là production và staging. Trong phạm vi loạt bài này, mình chỉ cần môi trường production thôi. Đây là file config/deploy/production.rb của mình
#set phương thức ssh = public key (đây là lý do mà mình cần gen ssh-key cho deploy ngoại trừ việc up lên github)
set :ssh_options, {
  keys: %w(~/.ssh/id_rsa),
  forward_agent: true,
  port: 22,
  user: 'deploy',
}

# role :name, %{[user]@[IP adde.]}
role :app, %w{deploy@192.168.13.216}
role :web, %w{deploy@192.168.13.216}
role :db,  %w{deploy@192.168.13.216}

# Define server(s)
server '192.168.13.216', user: 'deploy', roles: %w{web}

Thư mục lib/capistrano/tasks/ Chứa các custom task của mình. Phần này mình sẽ giới thiệu tới các bạn sau. OK. Vậy là đã setup xong cơ bản phía local server

2. Server remote

Đối với server remote thì cũng cần cấu hình môi trường gần giống với server local: environment: ruby on rails, unicorn, nginx, mysql. Vậy nên các bạn vẫn cứ theo các bước trên tiến hành nhé. Bỏ setup Capistrano đi là được             </div>
            
            <div class=

0