Hướng dẫn Deploy ứng dụng Ruby on Rails lên Server AWS EC2 Sử dụng Gem Capistrano, Puma và Nginx

Đối với các developer nói chung mục đích cuối cùng của của chúng ta khi phát triển ứng dụng, sản phẩm là những ứng dụng, sản phẩm ấy đến được với người dùng và deploy là bước cuối cùng mà chúng ta cần thực hiện. Nếu là một web developer thì sớm hay muộn bạn cũng cần phải biết về công đoạn deploy này. Trên mạng thì có rất nhiều bài viết hướng dẫn deploy ứng dụng Rails lên Server, khi tìm hiểu thì có thể các bạn cũng sẽ thấy rất hoan mang không biết nên bắt đầu ở đâu thì tốt, các bước sẽ như thế nào. Ngoài ra những câu hổi như nginx là gì, apache là gì, tại sao đã có server puma rồi lại còn cần cài thêm nginx nữa, web server là gì, app server là gì, các bước để deploy một Ruby on Rails app là như thế nào,...v.v đây có thể là những câu hỏi mà các bạn sẽ gặp phải khi muốn thực hiện deploy Rails app. Nên hôm nay sau khi đã deploy được một vài lần Ruby on Rails app lên server nhờ việc nhiều lần tìm hiểu mò mẫm trên mạng + nhờ sự chỉ giáo của một a leader hồi trước ở HN :v, dựa vào một chút kiến thức còn ít ỏi của bản thân mình xin hướng dẫn các bạn các bước từ A-Z để deploy một ứng dụng Ruby on Rails lên Server AWS(Amazon Web Service) EC2 sử dụng Gem Capistrano, Puma và Nginx.

Công thức deploy app Ruby on Rails với Capistrano, Puma và Nginx

Sau nhiều thời gian nghiên cứu các bài viết về deploy app Ruby on Rails, đồng thời cũng trải qua kinh nghiệm khi deploy vài lần mình có rút ra công thức deploy cho bản như sau:

Deploy = Config Server(giống như 1 máy local) + Config Capistrano + Config Nginx

Nếu các bạn nhớ công thức này thì các bước lằng nhằng bên dưới cũng chỉ để phục vụ 3 mục đích chính ở trên mà thôi, nhưng công thức chung là như vậy. Các bạn cứ nhớ 3 yếu tố chính trên rồi sẽ tự suy ra được công việc cần làm khi deploy, như vậy sẽ rất ít bị xảy ra thiếu trước hụt sau :v

OK như vậy thôi, giờ triển nào :v

Chuẩn bị Server AWS EC2

Muốn deploy ứng dụng thì việc đầu tiên cần làm là phải kiếm một nhà cung cấp và thuê lấy một cái Server đã :v Hiện nay có rất nhiều dịch vụ cho thuê Server(VPS Vitual Private Server), các bạn có thể thuê Server từ các nhà cung cấp bất kỳ, đây là một trong những dịch vụ cung cấp nổi tiếng như: AWS - Amazon Web Service, DigitalOcean, Linode, A2Hosting VPS, Azdigi - trang này là của Việt Nam,...các bạn có thể tham khảo và thuê lấy một cái :v

Trong bài viết này mình sẽ sử dụng thằng AWS - Amazon Web Service với thằng này khỏi phải bàn cãi đây là nhà cung cấp VPS được sử dụng phổ biến nhất hiện nay không chỉ cung cấp VPS mà nó còn hỗ trợ rất nhiều dịch vụ khác như Storage(S3, EFS,..), Simple Notification Service SNS,..., mấy ông Khách hàng trên công ty mình cũng thấy toàn dùng của AWS mà thôi :v.

Tạo tài khoản AWS

Đầu tiên chúng ta cần tạo một cái tài khoản AWS ở đây trước đã. Trên mạng cũng có rất nhiều bài hướng dẫn tạo tài khoản AWS các bạn có thể dễ dàng tham khảo nếu gặp khó khăn trong quá trình đăng ký. Sau khi đăng ký được một cái tài khoản AWS thì nó sẽ cho chúng ta ở chế dộ Free-Tier một năm, nên có thể thoải mái test thử các dịch vụ của nó các kiểu mà không lo phải trả tiền - quá đã (lol) Vì mình hiện tại đang có sẵn một tài khoản AWS nên chúng ta sẽ đến bước tiếp theo tạo một cái Server thông qua dịch vụ EC2 mà AWS nó cung cấp.

Tạo 1 Instance EC2

  • Đăng nhập vào phát đã nhỉ

Screenshot from 2017-10-11 21-56-01

  • Sau khi vào thì giao diện trang quản lý sẽ như này

Screenshot from 2017-10-11 21-56-01

  • Chúng ta click vào phần EC2

Screenshot from 2017-10-11 21-56-01

  • Click Launch Instance nó sẽ xuất hiện các loại instance như bên dưới

Screenshot from 2017-10-11 21-56-01

  • Vì để deploy ứng dụng Ruby on Rails nên chúng ta sẽ chọn OS là Ubuntu, hiện tại thì Ubuntu đã ra phiên bảng 16.04 LTS nên cứ phiên bản mới nhất mà quất thôi :v, nên chúng ta sẽ chọn thằng Ubuntu Server 16.04 LTS (HVM), SSD Volume Type - ami-10547475 nhấn Select Các bạn để ý bên dưới loại instance này nó có dòng chữ Free tier eligible có nghĩa là đối với các tài khoản Free Tier vừa mới đăng ký xong như chúng ta sẽ được free khi đăng ký nó. :v

  • Nhấn Select

Screenshot from 2017-10-11 21-56-01

  • Chọn Review and Launch nó sẽ hiện ra thông tin cấu hình của Instance

Screenshot from 2017-10-11 21-56-01

  • Nhấn Launch Sau đó hệ thống sẽ yêu cầu chúng ta phải tạo một cái Key Pair cái này là Private Key chúng ta sẽ giữ, còn cái Public key thằng AWS sẽ giữ thông qua cái Key Pair chúng ta sẽ có thể kết nối đến SSH mà chúng ta đang tạo đây. Do đó nên lưu cái Key Pairnày lại vào một nơi nào đó mà có thể nhớ được, nếu mất thì toi đấy (lol)

Screenshot from 2017-10-11 21-56-01

  • Chọn Create a new key pair Sau đó nhập tên Key Pair vào, tên này thì chúng ta có thể điền vào tên bất kỳ mình thì thường hay đặt cái tên này theo tên của project sẽ deploy lên cho dễ nhớ :v.

    Nhấn Download Key Pair để download nó về, nên lưu nó vào một nơi nào đó dễ nhớ, tránh bị mất. Nếu người khác có cái Key pair này thì cũng có thể truy cập vào cái instance của chúng ta đấy nên trong trường hợp cần bảo mật thì nên giữ cái Key pair này cẩn thận nhé (lol)

  • Nhấn Launch Instance để start cái instance của chúng ta nào, sau khi nhấn có thể mất vài phút để nó khởi động nên cứ thoải mái nhé :v

Screenshot from 2017-10-11 21-56-01

Kết nối với Instance EC2

Để kết nối máy local của chúng ta với Instance EC2 chúng ta cần thực hiện vài thao tác như bên dưới:

  • Chúng ta cần cấp quyền view cho Key pair mà chúng ta vừa download chmod 400 iorder_server.pem

  • Chạy lệnh dưới để connect đến instance ssh -i "ioder_server.pem" [email protected]

    trong đó ec2-13-58-225-21.us-east-2.compute.amazonaws.com là Public DNS (IPv4) instance của mình, các bạn có thể thay thế nó bằng địa chỉ của các bạn.

    Screenshot from 2017-10-11 21-56-01

    Nó nằm ngay trên thông tin instance ở hình phía trên. Hoặc các bạn cũng có thể click phải chuột vào cái instance ấy, nhấn connect hướng dẫn thông tin connect đến instance sẽ hiện ra bao gồm cả Public DNS (IPv4) cứ việc copy và paste vào terminal thôi. Nếu nó báo permission denied thì thêm sudo vào trước nhé

    Screenshot from 2017-10-11 21-56-01

    Xong xuôi thì ta sẽ có được kết nối như này :v

    OK như vậy là chúng ta đã xong phần tạo instance và kết nối đến nó (good) Nêú chúng ta truy cập bằng cách trên sẽ vào được user ubuntu(root) trong trường hợp nếu chúng ta cần share instance này cho những người khác nữa thì chúng ta nên tạo thêm một thằng user mới rồi truy cập vào nó thông qua SSH public-key tăng tính bảo mật khi không phải share file Key pair ở trên và nhìn cho nó chuyên nghiệp (lol)

    Tiếp theo chúng ta sẽ tạo một thằng user cho mục đích trên và phục vụ luôn cho việc deploy

Tạo 1 user để deploy

[email protected]:~$ sudo adduser <new_user>

Chạy lệnh trên để add thêm một user mới dùng cho deploy nhé. Mình thường hay đặt tên là www các bạn có thể đặt tên là deploy hay gì đấy tùy thích, miễn dễ nhớ là được :v

[email protected]:~$ sudo passwd <new_user>

Trong trường hợp nó không báo set password cho user ngay sau khi bạn chạy lện adduser thì các bạn cần chạy lệnh trên để add password cho user của mình nhé. Còn nếu đã set password rồi thì bỏ qua bước này.

Cấp quyền sudo cho user

Có một vấn đề phát sinh khi các bạn tạo user mới đó là, khi chuyển sang user mới ví dụ user của mình là www chẳng hạn. (sau khi dùng lệnh sudo su - www sẽ chuyển sang user www) Thì khi các bạn dùng đến các lệnh cần dùng sudo như:

[email protected]:~$ sudo apt-get update, ...

thì khi đấy nó bắn ra cái lỗi này

www is not in the sudoers file. This incident will be reported.

Đúng như thế vì thằng user của chúng ta vừa tạo, chưa được cấp quyền sudo :v

Thế nó muốn cấp quyền thì nhích thôi, ta có toàn quyền với thằng user root ubuntu mà (lol)

Ở thằng user ubuntu chúng ta thực hiện như bên dưới:

Mở file /etc/sudoers

sudo nano /etc/sudoers

Thêm đoạn này vào:

%www ALL=(ALL) ALL

Screenshot from 2017-10-11 21-56-01

OK lưu lại là xong, thằng user www đã có quyền sudo và bây giờ thoải mái cài đặt trên thằng này :v

Giờ chuyển sang làm việc với thằng user www nhé, gõ lệnh sau:

sudo su - www

Add ssh key authentication

Để có thể kết nối trực tiếp đến thằng user www từ máy local mà không cần dùng password hay thông qua user ubuntu chúng ta cần thiết lập ssh authentication

mkdir .ssh
sudo chmod 700 .ssh
nano ~/.ssh/authorized_keys
sudo chmod 600 ~/.ssh/authorized_keys

Như thế là đã tạo được file authorized_keys việc tiếp theo là tạo ra ssh public key/ ssh private key rồi đôi thằng public key lên file authorized_keys là ok.

Để sinh ssh key ta gõ lệnh dưới ssh-keygen -t rsa -C <local_name> local_name các bạn muốn nhập gì cũng được miễn nó dễ gợi nhớ cho cái key này là được :v

Nhấn enter liên tục, bỏ qua phần set password đến khi nó ra như này là ok:

....
+--[RSA 2048]----+
|+E%.. . o .      |
|.O * . . +       |
|  * .   +        |
|   .   o +       |
|        S .      |
|         o       |
|        .        |
|                 |
|                 |

cat ~/.ssh/id_rsa.pub để lấy nội dung của ssh public_key sau đó copy và paste lên vào file authorized_keys như này

Screenshot from 2017-10-11 21-56-01

Save lại thế là OK.

Và bây giờ các bạn có thể kết nối đến Server của chúng ta thông qua thằng user www mà không cần password nữa.

Ở máy local:

ssh [email protected]

với 18.221.235.25 là địa chỉ IPv4 Public IP mà các bạn có thể thấy ở trang thông tin instance.

Screenshot from 2017-10-11 21-56-01

Sau đó ta sẽ có như này. OK như vậy là chúng ta đã có 1 con server với user đã cấp đủ quyền root rẹt các kiểu, cũng đã kết nối được thằng Server với user www thông qua ssh key. Sau này có member nào trong team muốn kết nối đến Server thì chỉ việc bảo nó sinh cái ssh key ra rồi quăng lên file ~/.ssh/authorized_keys là được.

Đến bước tiếp theo nào :v

Cài đặt môi trường trên Server

Cài đặt môi trường

Theo như mình đã nói ở công thức trên: Sau khi đã có một server ngon lành, việc tiếp theo chúng ta cần làm là cấu hình cho con Server ấy giống như máy local có thể chạy được project của chúng ta, các cấu hình cơ bản như:

  • RVM
  • Ruby
  • Rails
  • MySQL
  • Git
  • Redis (optional)
  • ...

Nói chung cần cài đặt đầy đủ các componence cần thiết như đối với máy local để chạy được project của chúng ta lên Bên dưới mình cũng có một bài hướng dẫn cài đặt môi trường đối với một ứng dụng Ruby on Rails các bạn có thể tham khảo.

https://viblo.asia/p/series-huong-dan-lap-trinh-ruby-on-rails-phan-1-mrDkMrrNvzL

Sau khi cài đặt MySQL chúng ta cần tạo database sẵn cho ứng dụng như sau:

mysql -u root -p
Nhập password vào
Gõ lệnh này
CREATE DATABASE <your_database_name> CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Tên database các bạn cần đế để lát nữa còn config vào file database.yml nhé OK như thế là xong.

Cài đặt Git

Thường thì thằng Instance EC2 sau khi lauch đã cài đặt sẵn git cho chúng ta.

Nếu chưa có thì quất thôi:

sudo apt-get install git

OK như vậy là xong phần cấu hình server, giờ đến bước tiếp theo nào.

Cấu hình project

Cài đặt gem capistrano:

Chúng ta sẽ sử dụng gem Capistrano để hỗ trợ việc deploy. Đôi cái này vào Gemfile:

gem "capistrano"
gem "capistrano3-puma"
gem "capistrano-rails", require: false
gem "capistrano-bundler", require: false
gem "capistrano-rvm"

Sau đó bundle install

Rồi chạy lệnh để cài đặt Capistrano: cap install

Sau khi chạy lệnh trên nó sẽ sinh ra cho chúng ta các file: deploy.rb, deploy/staging.rb, deploy/production.rb,...

Các file staging.rb, production.rb chẳng qua chỉ là chia ra để dễ phân biệt các môi trường. Ở đây chúng ta chọn môi trường deploy là production nên sẽ set giá trị config vào thằng production.rb

Mở thằng deploy.rb lên và copy đoạn bên dưới vào

# config valid only for current version of Capistrano
lock "3.5.0"

set :application, "iorder_server"
set :repo_url, "[email protected]:duc11t3bk/iorder_server.git"
set :pty, true
set :linked_files, %w(config/database.yml config/application.yml)
set :linked_dirs, %w(log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads)
set :keep_releases, 5
set :rvm_type, :user

set :puma_rackup, -> {File.join(current_path, "config.ru")}
set :puma_state, -> {"#{shared_path}/tmp/pids/puma.state"}
set :puma_pid, -> {"#{shared_path}/tmp/pids/puma.pid"}
set :puma_bind, -> {"unix://#{shared_path}/tmp/sockets/puma.sock"}
set :puma_conf, -> {"#{shared_path}/puma.rb"}
set :puma_access_log, -> {"#{shared_path}/log/puma_access.log"}
set :puma_error_log, -> {"#{shared_path}/log/puma_error.log"}
set :puma_role, :app
set :puma_env, fetch(:rack_env, fetch(:rails_env, "staging"))
set :puma_threads, [0, 8]
set :puma_workers, 0
set :puma_worker_timeout, nil
set :puma_init_active_record, true
set :puma_preload_app, false

Ở đây các bạn chỉ cần quan tâm 2 dòng đó là:

set :application, "iorder_server"
set :repo_url, "[email protected]:duc11t3bk/iorder_server.git"

set :application : đặt tên cho ứng dụng của bạn, nên điền tên ứng dụng vào đây. set :repo_url: link đến repository chứa source code, của mình hiện tại đang là trên github Các bạn cần sửa trỏ đến project của các bạn là ok.

production.rb

set :stage, :production
set :rails_env, :production
set :deploy_to, "/deploy/apps/iorder_server"
set :branch, :config_deploy
server <server_ipv4_public_ip>, user: <your_user>, roles: %w(web app db)

Đối với mình sẽ là server "18.221.235.25", user: "www", roles: %w(web app db)

Các bạn lưu ý thêm một điểm đó là nơi mà chúng ta sẽ deploy app đó là ở đây : /deploy/apps/iorder_server

Các bạn có thể thay đổi nơi để deploy app tùy thích, tuy nhiên nếu là người deploy lần đầu thì cứ tạo theo mình iorder_server là tên của app các bạn có thể đổi theo tên app của các bạn /deploy/apps/<your_app_name>

Đôi cái này vào file .gitignore để tránh public những thông tin không cần thiết như database.yml, chúng ta sẽ set nó sau trên server.

# Ignore bundler config.
/.bundle

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Other
config/database.yml
public/*
.env
coverage/
.rspec

config/settings.local.yml

config/settings.local.yml
config/settings/*.local.yml
config/environments/*.local.yml

Cấp quyền ssh cho Server vào Git Repository

Hồi nãy chúng ta có config đường dẫn đến Git repository project của chúng ta có đúng không

set :repo_url, "[email protected]:duc11t3bk/iorder_server.git"

câu hỏi đặt ra là, bây giờ làm sao thằng Server của chúng ta có thể lấy source từ trên này về mỗi khi chúng ta thực hiện deploy được.

Câu trả lời là: đơn giản chỉ cần add ssh public key của thằng Server chúng ta lên Github là được.

Việc gen ssh-keygen làm giống như đối với ở máy local

ssh-keygen -t rsa -C "iorder_server"

Sau đó copy ssh public key đôi vào phần Deploy Keys trên Git Repository là được.

Screenshot from 2017-10-11 21-56-01

Screenshot from 2017-10-11 21-56-01

Như này là OK

Cấu hình nginx

Bước cuối cùng là chúng ta cầu hình thằng Nginx: Đầu tiên cài nó cái đã :v

sudo apt-get install nginx

Sau đó remove giá trị mặc định của nó đi, chúng ta sẽ tạo giá trị khác sau:

sudo rm /etc/nginx/sites_enabled/default

Tạo file default.conf

sudo nano /etc/nginx/conf.d/default.conf  

Sau đó copy nội dung bên dưới vào:

 upstream app {
   # Path to Puma SOCK file, as defined previously
   server unix:/deploy/apps/iorder_server/shared/tmp/sockets/puma.sock fail_timeout=0;
 }

 server {
   listen 80;
   server_name localhost;

   root /deploy/apps/iorder_server/current/public;

   try_files $uri/index.html $uri @app;

   location / {
     proxy_set_header X-Forwarded-Proto $scheme;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header Host $host;
     proxy_redirect off;
     proxy_set_header Connection '';
     proxy_pass http://app;
     proxy_read_timeout 150;
   }

   location ~ ^/(assets|fonts|system)/|favicon.ico|robots.txt {
     gzip_static on;
     expires max;
     add_header Cache-Control public;
   }

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

Đoạn trên trông có vẻ khó hiểu nhỉ, thật ra mình cũng không hiểu nhiều lắm (lol)

Chúng ta chỉ cần quan tâm 3 dòng này

listen 80;

App của chúng ta sẽ chạy ở port 80, có thể thay đổi tùy các bạn.

unix:/deploy/apps/iorder_server/shared/tmp/sockets/puma.sock fail_timeout=0;

Nó sẽ trỏ đến thằng puma của chúng ta app chúng ta sẽ được deploy ở đây /deploy/apps/iorder_server/ như hồi nãy mình có giải thích còn thằng shared chúng ta sẽ tạo nó sau, nên nếu có thay đổi về folder deploy thì lần sau các bạn chỉ cần chỉnh sửa đoạn này /deploy/apps/iorder_server/shared cho phù hợp là ok.

root /deploy/apps/iorder_server/current/public;

Chúng ta sẽ chỉ định thằng Nginx gửi các request đến project của chúng ta đang được deploy ở đây, thằng /current/public là mặc định của Capistrano nó sinh ra khi chúng ta deploy.

Ok lưu lại.

Và restart lại thằng nginx phát nào

sudo service nginx restart

Screenshot from 2017-10-11 21-56-01

Tạo cấu trúc thư mục deploy

Như hồi nãy chúng ta đã đề cập sẽ deploy app của chúng ta vào đây /deploy/apps/iorder_server trên server nên chúng ta sẽ tạo các thư mục tương ứng như trên

mkdir /deploy
mkdir /deploy/apps
mkdir /deploy/apps/iorder_server
sudo chown -R www:root /deploy/apps/iorder_server
mkdir /deploy/apps/iorder_server/shared
mkdir /deploy/apps/iorder_server/shared/config
nano /deploy/apps/iorder_server/shared/config/database.yml

File này sẽ điền thông tin database của các bạn, copy đoạn dưới rồi dán vào thôi, điền các thông tin tương ứng. database.yml

production:
  adapter: mysql2
  encoding: utf8mb4
  pool: 5
  database: <your_database_name>
  username: root
  password: <your_mysql_password>
  socket: /var/run/mysqld/mysqld.sock

tiếp đến

nano /deploy/apps/iorder_server/shared/config/application.yml

Copy cái này vào:

SECRET_KEY_BASE: "<your_secret_key_base>"

Để sinh secret key base ở local các bạn gõ RAILS_ENV=production rake secret rồi copy dán vào là ok.

Ta cũng cần add cái secret_key_base này vào biến môi trường luôn.

sudo nano ~/.bashrc

Rồi nhận cái này vào ở dòng đầu tiên

export SECRET_KEY_BASE="<your_secret_key_base>"

Screenshot from 2017-10-11 21-56-01

Sau đó source lại file này

source `/.bashrc`

Mở port 80

Bước cuối cùng mở port 80 cho Server của chúng ta: Quay trở lại trang quản lý Instance và thực hiện các bước sau;

Go to the "Network & Security" -> Security Group settings in the left hand navigation Find the Security Group that your instance is apart of Click on Inbound Rules Use the drop down and add HTTP (port 80) Click Apply and enjoy Screenshot from 2017-10-11 21-56-01 Screenshot from 2017-10-11 21-56-01 Screenshot from 2017-10-11 21-56-01 OK như thế là xong bước cuối cùng.

Và giờ thì tận hưởng thành quả nào, deploy thôi chứ chờ gì nữa

ở máy local, thư mục project gõ: cap production deploy và xem kết quả thôi nó trông sẽ như này Screenshot from 2017-10-11 21-56-01 Screenshot from 2017-10-11 21-56-01

Và đây là thành quả: Screenshot from 2017-10-11 21-56-01

  OK như vậy là đã deploy xong.
  Chúc các bạn thành công (lol)