Deploy Multiple Rails application on VPS
This post hasn't been updated for 3 years
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.com
và example.org
và tạo trang chủ với 1 dòng là Welcome example.com!
và 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.yml
và secrets.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
và /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