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 Puma
là Unicorn
và Passenger
.
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.enc và master.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 trongcredentials.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 và 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/