Deploy một dự án Rails tự động với Capitrano, Puma và Nginx

Có nhiều cách để tự động hóa việc deploy, từ các rsync-scripts đơn giản đến các tools phức tạp. Capistrano nằm ở đâu đó ở giữa.


8 min read
Deploy một dự án Rails tự động với Capitrano, Puma và Nginx

Giới thiệu Capistrano 3

Việc deploy là một việc việc làm thủ công bằng tay rất tốn công sức và còn phụ thuộc vào:

  • Các yếu tố cấu thành nên một ứng dụng (ngôn ngữ lập trình, framework,...)
  • Môi trường và quy mô deploy
  • Thời hạn cho phép deploy
  • Giá thành

Có nhiều cách để tự động hóa việc deploy, từ các rsync-scripts đơn giản đến các tools phức tạp. Capistrano nằm ở đâu đó ở giữa. Capistrano giúp chúng ta deploy ứng dụng lên nhiều server cùng một lúc chỉ bằng việc thực hiện trình tự các command đã được xây dựng sẵn theo kịch bản Capistrano.

Capitrano cho phép clone code từ (SVN hoặc Git) đến nhiều server cùng 1 lúc thông qua SSH và thực hiện chức năng trước và sau khi deploy như kiểm tra thông tin của file config, đổi tên tập tin, migration, kiểm tra log ... một cách tự động hóa chỉ bằng việc thực hiện một số câu lệnh cap

Đọc thêm các tính năng của capitrano ở đây

Puma

Puma là một web server cực kỳ nhanh và tốn ít bộ nhớ, được xây dựng cho các ứng dụng Rails.

Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications in development and production.

Dưới đây là một số tính năng của Puma

  • Puma xử lý tốt những users có kết nối mạng chậm
  • Chạy đa luồng
  • Miễn phí và hơn thế

Xem thêm ở đây

Có hai lựa chọn thay thế cho PumaUnicornPassenger.

Chuẩn bị

Trong bài viết này, chúng ta sẽ đi triển khai một Project Ruby on Rails trên Server Linux (Ubuntu 18.04 LTS)

Trước khi đi đến phần cài đặt phải chuẩn bị sẵn sàng một số thứ sau:

  • Ruby, version 2.6.1 (Huặc version mới hơn)
  • Rails, version 5.2.3 (Huặc version mới hơn)
  • RVM
  • Nginx
  • Puma
  • Capistrano
  • Git
  • Một project RoR ở local

Cài đặt SSH keys

Quá trình deploy sẽ pull code từ một repository (ở đây dùng GitHub). Để truy cập nhanh chóng và an toàn chúng ta sẽ thiết lập các SSH keys.

Nếu chưa tạo SSH key, tạo mới như sau:

ssh-keygen -t rsa

Có thể nhập password huặc để trống, quá trình sẽ tạo ra một public key tại đường dẫn mặc định: ~/.ssh/id_rsa.pub

Xem cách thêm ssh-key cho GitHub ở đây.

Đăng nhập vào github: ssh -T [email protected], chúng ta sẽ nhân được thông báo thành công như dưới:

ssh -T [email protected]
# Hi thai-dn! You've successfully authenticated

Thêm SSH-key cho Server, có nhiều cách để thêm, ví dụ:

ssh-copy-id -i ~/.ssh/id_rsa user@host

Truy cập vào Server:

ssh user@host

Cài đặt Capistrano

Thêm một số gem sau vàoGemfile.

group :development do
    gem 'capistrano',         require: false
    gem 'capistrano-rvm',     require: false
    gem 'capistrano-rails',   require: false
    gem 'capistrano-bundler', require: false
    gem 'capistrano3-puma',   require: false
end

gem 'puma'
bundle intall

Chạy lệnh sau để  Capistrano tạo ra các files và thư mục cần thiết.

cap install

Dưới đây là cấu trúc cây thư mục của capistrano.

├── Capfile
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   └── deploy.rb
└── lib
    └── capistrano
            └── tasks

Trong đó:

  • Capfile, file này nằm ở thư mục gốc của project nhằm mục đích tải các tasks cần thiết để khởi chạy ứng dụng.
  • config/deploy.rb, Chứa các cấu hình và biến môi trường

Thư mục config/deploy chúng ta chưa cần dùng đến . Các files này chứa cấu hình dành riêng cho các môi trường staging và production.

Cập nhật file Capfile với nội dung như sau:

# Load DSL and Setup Up Stages
require 'capistrano/setup'
require 'capistrano/deploy'

require 'capistrano/rails'
require 'capistrano/bundler'
require 'capistrano/rvm'
require 'capistrano/puma'
install_plugin Capistrano::Puma

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

Tiếp theo, chỉnh sửa lại file config/deploy.rb với các tham số như sau:

  • <PRODUCTION_IP>   : IP Server deploy
  • <REPO_URL>            : Địa chỉ (SSH) GitHub repo
  • <USER_NAME>          : User để deploy App (không nên dùng userroot)
  • <APP_NAME>            : Tên App (ví dụ: 'my-rails-app' lưu ý: viết liền, không sử dụng khoảng trắng)
# Change these
server '<PRODUCTION_IP>', port: 22, roles: [:web, :app, :db], primary: true

set :repo_url,        '<REPO_URL>'
# Example: [email protected]:username/appname.git

set :application,     '<APP_NAME>'
set :user,            '<USER_NAME>'
set :puma_threads,    [4, 16]
set :puma_workers,    0

# Don't change these unless you know what you're doing
set :pty,             true
set :use_sudo,        false
set :stage,           :production
set :deploy_via,      :remote_cache
set :deploy_to,       "/home/#{fetch(:user)}/apps/#{fetch(:application)}"
set :puma_bind,       "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
set :puma_state,      "#{shared_path}/tmp/pids/puma.state"
set :puma_pid,        "#{shared_path}/tmp/pids/puma.pid"
set :puma_access_log, "#{release_path}/log/puma.error.log"
set :puma_error_log,  "#{release_path}/log/puma.access.log"
set :ssh_options,     { forward_agent: true, user: fetch(:user), keys: %w(~/.ssh/id_rsa.pub) }
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true  # Change to false when not using ActiveRecord

## Defaults:
# set :scm,           :git
# set :branch,        :master
# set :format,        :pretty
# set :log_level,     :debug
# set :keep_releases, 5

## Linked Files & Directories (Default None):
set :linked_files, %w{config/database.yml config/master.key}
# set :linked_dirs,  %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

namespace :puma do
  desc 'Create Directories for Puma Pids and Socket'
  task :make_dirs do
    on roles(:app) do
      execute "mkdir #{shared_path}/tmp/sockets -p"
      execute "mkdir #{shared_path}/tmp/pids -p"
    end
  end

  before :start, :make_dirs
end

namespace :deploy do
  desc "Make sure local git is in sync with remote."
  task :check_revision do
    on roles(:app) do
      unless `git rev-parse HEAD` == `git rev-parse origin/master`
        puts "WARNING: HEAD is not the same as origin/master"
        puts "Run `git push` to sync changes."
        exit
      end
    end
  end

  desc 'Initial Deploy'
  task :initial do
    on roles(:app) do
      before 'deploy:restart', 'puma:start'
      invoke 'deploy'
    end
  end

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      invoke 'puma:restart'
    end
  end

  before :starting,     :check_revision
  after  :finishing,    :compile_assets
  after  :finishing,    :cleanup
  after  :finishing,    :restart
end

# ps aux | grep puma    # Get puma pid
# kill -s SIGUSR2 pid   # Restart puma
# kill -s SIGTERM pid   # Stop puma

Tiếp theo là phần cấu hình Nginx để tương tác với Puma.

Tại sao phải cần dùng đến 2 web server ?

Nginx là một web server, còn Puma là một App server. Nginx tiếp nhận các luồng web traffics sau đó chuyển hướng qua Puma để có thể chạy ứng dụng Rails . Xem cách giải thích chi tiết ở đây

upstream puma {
  server unix:///home/<USER_NAME>/apps/<APP_NAME>/shared/tmp/sockets/<APP_NAME>-puma.sock;
}

server {
  listen 80 default_server deferred;

  # If you're planning on using SSL (which you should), you can also go ahead and fill out the following server_name variable:
  # server_name example.com;

  # Don't forget to update these, too
  root /home/<USER_NAME>/apps/<APP_NAME>/current/public;
  access_log /home/<USER_NAME>/apps/<APP_NAME>/current/log/nginx.access.log;
  error_log /home/<USER_NAME>/apps/<APP_NAME>/current/log/nginx.error.log info;

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

  try_files $uri/index.html $uri @puma;
  location @puma {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;

    proxy_pass http://puma;
  }

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

Xong phần thiết lập cấu hình, chúng ta sẽ đi đến phần triển khai ứng dụng.


Triển khai ứng dụng

Từ version 4.1, Rails đã giúp chúng ta lưu trữ các thông tin bí mật ở file: secret.yml trong thư mục config. Theo mặc định, nó chứa SECRET_KEY_BASE, để tìm hiểu rõ hơn về mục đích của SECRET_KEY_BASE xem ở đây

Tuy nhiên từ version Rails 5.1 trở đi, nó được thay thế bằng file secrets.yml.enc

Để thêm secret credentials vào credentials.yml.enc , ở dưới máy local chạy lệnh sau đây

EDITOR="mate --wait" bin/rails credentials:edit

Trong thư mục config sẽ tạo ra 2 files: credentials.yml.encmaster.key

config
  ├── credentials.yml.enc
  └── master.key
  • credentials.yml.enc là một file được mã hóa chứa tất cả các thông tin bí mật. Các API keys và passwords sẽ được lưu trữ ở đây. Giúp việc push nội dung lên git repository hoặc server trở nên an toàn hơn.
  • master.key Chứa encryption key, được sử dụng để giải mã credentials.yml.enc  Nếu không có file này or đã được sửa đổi, Rails sẽ không thể đọc thông tin đăng nhập lưu trong credentials.yml.enc.  (không đưa file này lên github huặc server)

Để an toàn, chúng ta nên loại trừ folders/files chứa thông tin nhạy cảm như config vào .gitignore, (Không nên push lên github)

Tiếp theo, chúng ta sẽ tạo cấu trúc cây thư mục ở phía server như sau, với một số files cần thiết cho qúa trình deploy.

$HOME/apps/<APP_NAME>
    └── shared
          └── config
                ├── database.yml
                └── master.key

Tạo thư mục như sau:

mkdir -p $HOME/apps/<APP_NAME>/shared/config
touch $HOME/apps/<APP_NAME>/shared/config/master.key
touch $HOME/apps/<APP_NAME>/shared/config/database.yml

Copy dụng nội dung: master.key database.yml trong thư mục config dưới local sau đó paste vào: master.key và  database.yml tương ứng vừa tạo ở Server, sau đó lưu lại.

Như đã đề cập từ trước, chúng ta sẽ deploy ứng dụng trực tiếp từ github repository, ở dưới local nhập lệnh sau:

cap production deploy:initial

initial sẽ tạo và cấu hình cho việc deploy lên môi trường production (chỉ cần chạy lần đầu tiên).

Cấu hình Nginx

Cuối cùng, để Nginx sử dụng file cấu hình nginx.conf đã taọ trước đó.

sudo rm /etc/nginx/sites-enabled/default # Remove old config file

# Create a symlink to our new one
sudo ln -nfs "/home/<USER_NAME>/apps/<APP_NAME>/current/config/nginx.conf" "/etc/nginx/sites-enabled/<APP_NAME>"

Để kiểm tra cấu hình đã chính xác hay chưa:

sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

Sau đó khởi động lại dịch vụ Nginx

sudo service nginx restart

Refresh lại trình duyệt.

Lỗi trong qúa trình deploy

Nếu nhận được lỗi "An unhandled lowlevel error occurred. The application logs may have details." có thể liên quan đến SECRET_KEY_BASE.

Chạy rake secret và copy chuỗi kĩ tự đó vào mục production ở config/secrets.yml.

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

Xem ở here.

Release một tính năng mới

Việc depoy đã hoàn tất, khi muốn release một bản cập nhật, chúng ta chỉ cần commit code lên github, sau đó chạy:

cap production deploy

Tham khảo

http://nguyenanh.github.io/2016/03/28/cau-hinh-deploy-du-an-rails-voi-capistrano-unicorn-va-nginx
https://matthewhoelter.com/2018/09/18/deploying-ruby-on-rails-for-ubuntu-1804.html
https://scoutapm.com/blog/which-ruby-app-server-is-right-for-you
https://gorails.com/deploy/ubuntu/18.04
https://github.com/capistrano/capistrano
https://waiyanyoon.com/deploying-rails-5-2-applications-with-encrypted-credentials-using-capistrano/
https://www.viget.com/articles/storing-secret-credentials-in-rails-5-2-and-up/



Deploy một dự án Rails với Passenger và Nginx
Previous article

Deploy một dự án Rails với Passenger và Nginx

Phusion passenger được hiểu như một công cụ kết nối tự động giữa webserver và một ứng dụng web app

The queue data structure
Next article

The queue data structure

Queue là một kiểu cấu trúc dữ liệu tuyến tính, tuân theo quy luật FIFO (First In First Out). Hay “first-come, first-served”, nghĩa là: “ai đến trước thì được phục vụ trước”


Related Articles

RESTful: Phần 4 - API Rate Limiting
10 min read
RESTful: Phần 3 - API Caching
12 min read
The queue data structure
3 min read

GO TOP

🎉 You've successfully subscribed to itplusX!
OK
]