+6

Deploy Multiple Rails application on VPS

Chuẩn bị

Chọn mua server

Bạn có thể đăng ký cho riêng mình một dịch vụ mà bạn thích. Với mình đã từng sử dụng dịch vụ của Digital Ocean (DO) và Linode.

Hiện giờ thì mình chuyển sang sử dụng Linode. Các bạn đọc có thể đăng ký theo link này để cả mình và bạn đều được cộng thêm $ nhé! 😄 Signup Linode

Sau khi đăng ký tài khoản trên, mình tiến tới việc chọn mua một server (VPS). Có rất nhiều lựa chọn các gói, cả hai dịch vụ trên đều tính phí sử dụng theo giờ và ở DO thì có gói nhỏ nhất với giá chỉ từ 5$ / tháng. Còn ở Linode thì gói nhỏ nhất là 10$/tháng.

Giao diện chọn gói VPS ở Linode như hình sau:

Đối với DO thì khi lựa chọn gói, sau đó cần chọn OS và DO tự động install hệ điều hành đó. Tuy nhiên ở Linode thì khác, phần lựa chọn chỉ chọn gói sản phẩm và Region. Sau đó mới bắt đầu lựa chọn hệ điều hành và deploy image. Ở Linode cũng có thêm lựa chọn ở giao diện cho chúng ta tạo luôn bộ nhớ swap khi install hệ điều hành. Sau khi cài đặt hệ điều hành xong chúng ta bấm nút khởi động VPS và kể từ đây chúng ta đã có thể thao tác trên chiếc máy tính này.

Cấu hình server cơ bản

Sau khi đã chuẩn bị xong một chiếc VPS như phía trên, phần này mình tiến hành cài đặt cấu hình một số phần cơ bản cho một VPS hoàn toàn mới.

Đầu tiên mình sẽ ssh vào bằng account root và tạo một sudo user .

Root login

$ ssh root@<your_server_ip>

Với lần đầu login chưa sử dụng ssh thì mình cần nhập vào mật khẩu của người dùng root. Mật khẩu mình đặt khi cài đặt hệ điều hành cho VPS.

Tạo một sudo user

Sau khi login bằng quyền root, mình tạo một user bằng lệnh sau

# adduser sammy

Nhập vào mật khẩu và một số thông tin cho user này và mình đã tạo xong một user là sammy.

Gán quyền root cho user vừa tạo

Phần này mình tiến hành gán cho sammy trở thành sudo user.

# usermod -aG sudo sammy

Sử dụng public key authentication

Việc thêm public key authentication để giúp chúng ta không cần nhập mật khẩu bằng tay mỗi khi ssh tới server vps.

Tạo key gen ở máy local

$ ssh-keygen

Lệnh trên sẽ tạo ra các ssh keygen ở máy local của mình. Và mình sẽ sử dụng public key để gửi cho server vps để sử dụng cho mỗi lần ssh sau.

Copy public key

$ cat ~/.ssh/id_rsa.pub

Mình copy output của lệnh trên.

Tiếp theo, chuyển qua người dùng sammy ở server mà mình đã tạo ở trên.

# su - sammy

Tạo thư mục ./ssh và gán quyền cho thư mục

$ mkdir ~/.ssh
$ chmod 700 ~/.ssh

Tạo file chứa public key mà mình đã copy ở máy local

$ vim ~/.ssh/authorized_keys

Dán nội dung file id_rsa.pub vào và lưu lại.

Sửa lại quyền cho file vừa tạo.

$ chmod 600 ~/.ssh/authorized_keys

Disable Password Authentication

Để tăng cường bảo mật cho sever mình tiến hành disable ssh login bằng password cho server vps như sau

$ sudo nano /etc/ssh/sshd_config

Sửa các dòng sau thành

PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no

Chạy lệnh sau để restart sshd

$ sudo systemctl reload sshd

Kiểm tra lại.

Thoát ra khỏi vps server và ssh lại bằng user sammy mình không cần nhập password bằng tay nữa.

Thử ssh vào trực tiếp bằng user root

Lưu lại và thoát ra. Vậy mình đã tiến hành cài đặt cơ bản cho một vps mới.

Chuẩn bị sample code

Phần này mình sẽ chuẩn bị trước 2 rails app để bước tiếp theo deploy 2 app này lên server của mình.

Mình sẽ tạo 2 app tên là example.comexample.org và tạo trang chủ với 1 dòng là Welcome example.com!Welcome example.org!

Tạo static_pages controller với 1 trang index

$ rails new example.com

$ cd example.com

$ rails g controller static_pages index

Sửa file root.rb trỏ trang chủ về static_pages#index

Rails.application.routes.draw do
  root "static_pages#index"
end

Thêm vào views/static_pages#index có nội dung như dòng chữ trên.

<h1>Welcome example.com!</h1>

Chạy rails s và truy cập vào localhost:3000

Làm tương tự với example.org

Cài đặt

Cài đặt Ruby

Phần này mình sẽ tiến hành cài đặt Ruby, phiên bản mới nhất hiện tại khi mình đang viết bài là Ruby 2.4.0

Trước tiên là cập nhật hệ thống lại và cài đặt các gói phần mềm phụ thuộc 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 libffi-dev nodejs

Tiếp theo mình sẽ cài đặt Ruby. Có một vài lựa chọn cách cài ruby khác nhau, ở đây mình sử dụng rvm.

$ sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
$ curl -sSL https://get.rvm.io | bash -s stable
$ source ~/.rvm/scripts/rvm
$ rvm install 2.4.0
$ rvm use 2.4.0 --default

$ ruby -v

Cài thêm bundler

$ gem install bundler

Cài đặt và cấu hình cơ sở dữ liệu

Phần này mình tiến hành cài đặt cơ sở dữ liệu. Có nhiều hệ quản trị cơ sở dữ liệu mà bạn có thể chọn, mình lựa chọn MySQL để lưu trữ dữ liệu.

Với Rails, có thể thêm tùy chọn để sử dụng MySql trong cấu hình mặc định sau khi tạo app.

$ rails new example.com -d mysql

$ rails new example.org -d mysql

Cài đặt MySql bằng lệnh sau:

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

Với Ubuntu 16.04 thì khi cài bằng lệnh trên sẽ cài phiên bản MySql mới nhất hiện giờ là 5.7

Sau khi cài đặt, mình sẽ tạo một database và một mysql user mới để sử dụng. Không sử dụng user root.

$ mysql -u root -p

mysql> create user 'demo_user'@'localhost' identified by '12345678';
mysql> CREATE DATABASE `demo_db` CHARACTER SET utf8 COLLATE utf8_general_ci;
mysql> grant all privileges on `demo_db`.* to 'demo_user'@'localhost';

mysql> flush privileges;

Cài đặt và cấu hình web server

Phần này mình tiến hành cài đặt và cấu hình Nginx.

Cài đặt

Phần này mình sử dụng Nginx làm web server và passenger làm app server.

Cài đặt nginx

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 xenial main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update

# Install Passenger & Nginx
sudo apt-get install -y nginx-extras passenger
sudo service nginx start

Sửa cấu hình Nginx cho phép sử dụng passenger

sudo vim /etc/nginx/nginx.conf

Bỏ comment như sau

##
# Phusion Passenger
##
# Uncomment it if you installed ruby-passenger or ruby-passenger-enterprise
##

include /etc/nginx/passenger.conf;

Tiếp theo, trỏ passenger về ruby mà mình đã cài đặt

sudo vim /etc/nginx/passenger.conf
passenger_ruby /home/deploy/.rbenv/shims/ruby; # If you use rbenv
# passenger_ruby /home/deploy/.rvm/wrappers/ruby-2.1.2/ruby; # If use use rvm, be sure to change the version number
# passenger_ruby /usr/bin/ruby; # If you use ruby from source

Để biết Ruby version, mình cũng nên sử dụng version default nếu cài nhiều phiên bản ruby trên cùng hệ thống.

Với rvm thì sử dụng lệnh rvm use <version> --default.

$ ruby -v
sudo service nginx restart

Cấu hình

Tiếp theo mình sẽ cấu hình để kiểm tra thử trước khi deploy app. Mình sẽ tạo ra 2 virtual host để chạy 2 app riêng.

Tạo document root mới riêng.

Bởi mặc định, ngix sử dụng thư mục gốc là var/www/html. Nhưng ở phần này mình sử dụng virtual hosts nên không dùng thư mục này mà tạo ra thư mục khác cho các virtual hosts khác nhau.

sudo mkdir -p /var/www/example.com/html
sudo mkdir -p /var/www/example.org/html

Sau đó mình sẽ gán lại quyền cho thư mục vừa tạo và các thư mục con. Ở đây biến $USER chính là user đang đăng nhập.

sudo chown -R $USER:$USER /var/www/example.com/html
sudo chown -R $USER:$USER /var/www/example.org/html
sudo chmod -R 755 /var/www

Như vậy mình đã tạo ra cấu trúc thư mục cho 2 virtual hosts. Giờ mình sẽ tạo ra 2 trang ví dụ để kiểm tra cấu hình.

Tạo index.html trong từng domain như sau. Với domain example.com như sau

vim /var/www/example.com/html/index.html

Với nội dung như sau:

<html>
    <head>
        <title>Welcome to Example.com!</title>
    </head>
    <body>
        <h1>Success!  The example.com server block is working!</h1>
    </body>
</html>

Tương tự với example.org

cp /var/www/example.com/html/index.html /var/www/example.org/html/index.html
vim /var/www/example.org/html/index.html

Với nội dung như sau:

<html>
    <head>
        <title>Welcome to Example.org!</title>
    </head>
    <body>
        <h1>Success!  The example.org server block is working!</h1>
    </body>
</html>

Tiếp theo,

Tạo server block file cho mỗi domain.

Với domain đầu tiên là example.com chúng ta làm như sau:

Copy file cấu hình mặc định của Nginx thành file với tên là tên của domain

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/example.com

Sửa nội dung file example.com như sau

sudo vim /etc/nginx/sites-available/example.com

Với nội dung ban đầu mặc định có sẵn như sau

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

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                try_files $uri $uri/ =404;
        }
}

Mình sẽ sửa một số giá trị phù hợp với cấu hình cho domain example.com của mình

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

        root /var/www/example.com/html;
        index index.html index.htm index.nginx-debian.html;

        server_name example.com www.example.com;

        location / {
                try_files $uri $uri/ =404;
        }
}

Ở trên mình chú ý giá trị default_server, giá trị này sẽ sử dụng nếu tất cả các request tới server của mình không trùng với bất kỳ một server_name nào. Và chúng ta chỉ có 1 giá trị default_server trong server gồm nhiều virtual hosts.

Để biết có bao nhiêu default_server đang được dùng ta chạy lệnh sau:

grep -R default_server /etc/nginx/sites-enabled/

Như vậy mình đã tạo xong server block file cho example.com. Làm tương tự với example.org như sau:

sudo cp /etc/nginx/sites-available/example.com /etc/nginx/sites-available/example.org
sudo vim /etc/nginx/sites-available/example.org

Nội dung như sau

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

        root /var/www/example.org/html;
        index index.html index.htm index.nginx-debian.html;

        server_name example.org www.example.org;

        location / {
                try_files $uri $uri/ =404;
        }
}

Xong rồi mình sẽ enable your Server Blocks and Restart Nginx

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/example.org /etc/nginx/sites-enabled/

Cấu hình thêm cho nginx

sudo nano /etc/nginx/nginx.conf

Bỏ comment dòng sau

http {
    . . .

    server_names_hash_bucket_size 64;

    . . .
}

Lưu file lại và kiểm tra cấu hình có sai cú pháp hay không.

sudo nginx -t

Khởi động lại nginx

sudo systemctl restart nginx

Kiểm tra

Tại máy local, sửa cấu hình hosts file

sudo nano /etc/hosts

Sửa giá trị 203.0.113.5 là IP của vps của mình.

127.0.0.1   localhost
. . .

203.0.113.5 example.com www.example.com
203.0.113.5 example.org www.example.org

Truy cập vào example.com ở browsers

Deploy

Capistrano

Cài đặt capistrano cho Rails app, thêm đoạn sau vào Gemfile

gem 'capistrano', '~> 3.7', '>= 3.7.1'
gem 'capistrano-rails', '~> 1.2'
gem 'capistrano-passenger', '~> 0.2.0'

# Add this if you're using rbenv
# gem 'capistrano-rbenv', '~> 2.1'

# Add this if you're using rvm
gem 'capistrano-rvm'
cd example.com

bundle install

Chạy lệnh dưới đây để generate ra các file cấu hình capistrano

cap install STAGES=production

Thêm vào Capfile một số thông tin cấu hình sau

require 'capistrano/rails'
require 'capistrano/passenger'

# If you are using rbenv add these lines:
# require 'capistrano/rbenv'
# set :rbenv_type, :user
# set :rbenv_ruby, '2.4.0'

# If you are using rvm add these lines:
# require 'capistrano/rvm'
set :rvm_type, :user
set :rvm_ruby_version, '2.4.0'

Sửa config/deploy.rb, thay đổi các giá trị của application, repo_url, deploy_to là các giá trị của mình cài đặt.

Với repo example.com thì là example.com, example.com và user của mình là sammy.

set :application, "example.com"
set :repo_url, "git@github.com:cuongnguyen2503/example.com.git"

set :deploy_to, '/var/www/example.com/html'

append :linked_files, "config/database.yml", "config/secrets.yml"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "vendor/bundle", "public/system", "public/uploads"

Sửa config/deploy/production.rb

# Replace 127.0.0.1 with your server's IP address!
server '127.0.0.1', user: 'sammy', roles: %w{app db web}

Thay đổi nội dung file cấu hình /etc/nginx/sites-available/example.com như sau

server {
        listen 80;
        listen [::]:80 ipv6only=on;

        server_name  example.com;
        passenger_enabled on;
        rails_env    production;
        root         /var/www/example.com/html/current/public;

        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
}

/current/public là bản deploy mới nhất.

Kết nối với cơ sở dữ liệu

Đầu tiên mình cho database.ymlsecrets.yml vào gitignore, và push lại lên git.

echo "config/database.yml\nconfig/secrets.yml" >> .gitignore
git add .gitignore
git mv config/secrets.yml config/secrets.yml.example
git mv config/database.yml config/database.yml.example
git commit -m "Only store example secrets and database configs"
cp config/secrets.yml.example config/secrets.yml
cp config/database.yml.example config/database.yml

Và bây giờ mình có thể deploy lên vps bằng lệnh dưới đây, nhưng trước khi deploy mình cần tạo ssh_keys cho server với github để có thể fetch code về deploy.

Mình copy ~/.id_rsa.pub từ vps server và dán vào github ở mục ssh trong phần settings như hình dưới.

Deploy lên server bằng lệnh cơ bản:

cap production deploy

Tuy nhiên lúc này sẽ có lỗi như sau:

linked file /var/www/example.com/shared/config/database.yml does not exist on IP_ADDRESS

Và mình cần tạo ra file database.yml, secrets.yml trên server như sau:

Tạo file /var/www/example.com/shared/config/database.yml/var/www/example.com/shared/config/secret.yml lần lượt như

# /var/www/example.com/shared/config/database.yml
production:
  adapter: mysq2
  host: 127.0.0.1
  database: demo_db
  username: demo_user
  password: 12345678
  encoding: utf8
  pool: 5

và chú ý thay đổi một số thông tin phía trên phù hợp với thiết lập của bạn.

Sinh ra secret key với lệnh rake secret

# /var/www/example.com/shared/config/secret.yml
production:
  secret_key_base: YOUR_SECRET_KEY

Tới đây mình chạy lại lệnh deploy

$ cap production deploy

Sau khi deploy xong, mình truy cập vào example.com và được kết quả như mong muốn.

Tương tự với repo example.org.

Với lệnh deploy cơ bản trên thì mỗi lần deploy capistrano sẽ tự động migrate database cho mình. Để thêm một số công việc như chạy seeds cho lần deploy đầu tiên, restart application server sau deploy mình sẽ viết thêm một số tasks.

Thêm vào deploy.rb

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

Hoặc nếu không muốn tự động seed mà muốn chạy khi cần thì config như sau:

# Add this in config/deploy.rb
# and run 'cap production deploy:seed' to seed your database
desc 'Runs rake db:seed'
task :seed => [:set_rails_env] do
  on primary fetch(:migration_role) do
    within release_path do
      with rails_env: fetch(:rails_env) do
        execute :rake, "db:seed"
      end
    end
  end
end

Trên đây là bài viết ghi lại quá trình deploy một ứng dụng Rails lên VPS. Cảm ơn bạn đọc tới đây.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.