+2

Deploy Rails project bằng Capistrano lên AWS EC2

Nếu bạn để ý bất kỳ project đang trong thời gian phát triển, bạn là một developer và bạn muốn test chức năng chắc chắn bước đầu là sẽ chạy test trên local nhưng có nhiều bug không phát sinh dưới local mà trên server.

Vậy làm sao để phát hiện được những vấn đề đấy. Tất nhiên ta phải deploy code lên server để chạy thử sau khi chạy dưới local rồi.

Là một developer công việc deploy bạn chắc chắn phải làm dù sớm hay muộn.

Hôm nay mình muốn hướng dẫn cơ bản cách deploy một ứng dụng Rail lên server EC2 của AWS sử dụng Capistrano

I/ Server EC2 của AWS

Amazon EC2 (Elastic Cloud) là một trong những gói dịch vụ của AWS cung cấp giải pháp điện toán đám mây cho phép người dùng có thể tạo, thiết lập và sử dụng một server một cách dễ dàng. Nó giảm thiểu việc cài đặt và cấu hình server đi rất nhiều so với việc bạn tự dựng 1 server vật lý.

Amazon EC2 Instance là một cloud server. Với một tài khoản bạn có thể tạo và sử dụng nhiều Amazon EC2 Instance. Các Amazon EC2 Instance được chạy trên cùng một server vật lý và chia sẻ memory, CPU, ổ cứng... Tuy nhiên do tính chất của cloud service nên mỗi một Instance được hoạt động giống như một server riêng lẻ.

Tạo tài khoản AWS

Bạn vào đây để tạo tài khoản AWS. Tài khoản của bạn mặc định sẽ được sử dụng gói Free-Tier một năm.

CHÚ Ý:

  • Gói Free-Tier thường sẽ tính thời gian sử dụng của bạn là 750h/tháng với mỗi loại instance và cấu hình thường sẽ là t2.micro. Nên nếu bạn chọn tạo sai cấu hình hay tạo quá nhiều instance mà không dùng đến sẽ bị tính phí.
  • Trường hợp chẳng may bạn không để ý và bị tính phí, bạn nên feedback cho bên AWS để nhờ hỗ trợ loại bỏ hoặc giảm trừ khoản phí đó (Kinh nghiệm 1 người suýt bị mất 100$ :v)

Tạo EC2 Instance

Bạn đăng nhập account AWS và chọn Service -> EC2

Sau đó bạn chọn Launch Instance để bắt đầu tạo EC2 Instance

Bạn chú ý đến Free tier eligible là những OS bạn có thể cài đặt. Ở đây mình chọn AWS Linux AMI 2018 đã được cài sẵn Ruby

Tiếp đến sẽ chọn cấu hình máy mặc định sẽ là Free-Tier t2.micro

Chọn Review and LaunchLaunch

Bạn sẽ cần tạo ra một Key Pair: đây là key để ta ssh vào server, không có ta sẽ không thể truy cập server. Bạn chọn Create a new key pair đặt tên key rồi Download về máy Chú ý: Key Pair bạn sẽ chỉ được cấp 1 lần duy nhất khi khởi tạo instance nên nếu mất bạn sẽ không có cách nào cấp lại và buộc phải tạo instance và Key Pair mới để sử dụng

Chọn Lauch Instances để hệ thống bắt đầu khởi tạo EC2

SSH server EC2

Bạn đã tạo xong EC2 bây giờ ta sẽ ssh vào nó thử xem sao nha Đây là lúc dùng đến Key Pair mà ta đã tạo và tải xuống ở trên

  • Bạn cần chuyển đổi quyền truy cập của Key Pair thành 400 vì file key sẽ chỉ được quyền read nếu bạn cho thêm quyền sẽ thông báo lỗi khi ssh vào server
sudo chmod 400 key.pem
  • SSH vào server: IP server của bạn xem ở trường IPv4 Public IP và user mặc định sẽ ec2-user Đây là lệnh để ssh vào server của bạn
ssh -i key.pem ec2-user@IP

Đây là kết quả sau khi ssh

Tạo user để deploy

  • Tạo user mới trên server: sudo adduser <new_user>
  • Cài password cho user mới tạo: sudo passwd <new_user>

Để deploy được user của bạn cần có quyền sudo để có thể cài đặt các ứng dụng như Rails,... và khởi chạy được các service khi deploy như puma, nginx,...

Để cấp quyền sudo cho user ta làm như sau:

  • Truy cập file:
sudo vi /etc/sudoers
  • Sau đó thêm dòng này vào %user ALL=(ALL) ALL

Bây giờ ta đăng nhập sang user mới tạo: sudo su user

Thêm ssh key để ta có thể truy cập thẳng vào user mới tạo từ máy a

  • Tạo file ssh authentication trên server
mkdir .ssh
sudo chmod 700 .ssh
vi ~/.ssh/authorized_keys
sudo chmod 600 ~/.ssh/authorized_keys
  • Dưới máy local của bạn bạn cần tạo ra key ssh-keygen -t rsa. Bạn đặt tên cho file key (nếu cần) rồi enter đến hết bỏ qua password
  • Tên mặc định của file nếu bạn không đặt sẽ là id_rsa.pub. Bạn copy nội dung trong file cat ~/.ssh/id_rsa.pub
  • Bạn paste nội dung key file vào file ~/.ssh/authorized_keys đã tạo ở trên server

Bây giờ bạn có thể ssh qua user mới rồi : ssh user@IP

II/ Deploy

Trên server bạn cần cài những cấu hình cơ bản sau: RVM, Ruby, Rails, Git, MySQL,....

Cài đặt Capistrano

Trở về với máy local của ta, trong ứng dụng Rails mà ta muốn deploy sẽ cần thêm vào Gemfile những gem sau:

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

Sau đó chạy bundle install để cài đặt gem

Cài đặt xong Capistrano gem ta chạy cap install để khởi tạo cái file cần thiết cho việc deploy gồm:

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

Bạn để ý các file được tạo ra có:

  • deploy.rb: đây là các lệnh và setting chung cho ứng dụng Rails trên tất cả các môi trường
  • thư mục deploy: đây bao gồm các file có chức năng giống như file deploy.rb nhưng nó sẽ thực hiện riêng cho các môi trường Sửa file deploy.rb và thêm đoạn sau:
lock "~> 3.11.0"

set :application, "g22-rails-training"
set :repo_url, "git@github.com:DuongDo317/g22-rails-training.git"

set :pty, true
set :linked_files, %w(config/database.yml config/application.yml config/puma.rb config/master.key)
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}/config/puma.rb"}
set :puma_access_log, -> {"#{release_path}/log/puma_access.log"}
set :puma_error_log, -> {"#{release_path}/log/puma_error.log"}
set :puma_role, :app
# set :puma_env, fetch(:rack_env, fetch(:rails_env, "staging"))
set :puma_threads, [4, 8]
set :puma_workers, 0
set :puma_worker_timeout, nil
set :puma_init_active_record, true
set :puma_preload_app, false

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 '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
  
  after  :finishing,    :cleanup
  after  :finishing,    :restart
end

Và thêm vào file deploy/production.rb:

server "54.169.193.207", user: "duong_deploy", roles: %w(web app db), primary: true

set :stage, :production
set :rails_env, :production
set :deploy_to, "/home/duong_deploy/deploy/apps/"
set :branch, ENV['BRANCH'] if ENV['BRANCH']

Bây giờ mình sẽ giải thích những phần bạn cần chú ý trong 2 file trên:

  • repo_url: link github repo project của bạn
  • deploy_to: thư mục mà bạn deploy trên server.

Đặc biêt bạn phải để đường dẫn trực tiếp chứ không phải những đường dẫn gián tiếp như ~/... vì nó sẽ ảnh hưởng đến hai biến current_pathshared_path. Nếu bạn để đường dẫn gián tiếp khi deploy sẽ thông báo thành công thư mục được tạo xong nhưng các file service sẽ không thể tạo được dẫn đến dù trên log deploy ghi start service thành công nhưng thực chất service chưa từng chạy

  • shared_path: là thư mục shared được tạo ra bạn deploy trông đấy sẽ bao gồm các file service và config
  • branch: đây là đoạn mình thêm vào để bạn có thể deploy dựa vào tên nhánh trên repo của bạn nếu bạn không nhập mặc định sẽ deploy master
  • server, user: chính là user deploy và IP server của bạn
  • rolespuma_role: bạn có thể đặt tên roles tùy ý nhưng khi gọi pumarole phải là một trong những roles bạn đã khai báo không sẽ không thể start puma được
  • linked_files: đây sẽ là những file config của ứng dụng Rails của bạn mà bạn phải tạo trước trên server. Những file này sẽ không thay đổi khi deploy mà phải sửa trên server

Setting puma

Thêm vào Capfile để khởi chạy puma khi deploy:

require "capistrano/puma"
install_plugin Capistrano::Puma

Sửa file config/puma.rb:

threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
app_dir = File.expand_path("../..", __FILE__)
environment "production"

bind "unix:///home/duong_deploy/deploy/apps/shared/tmp/sockets/puma.sock"
pidfile '/home/duong_deploy/deploy/apps/shared/tmp/pids/puma.pid'
state_path '/home/duong_deploy/deploy/apps/shared/tmp/pids/puma.state'
workers ENV.fetch("WEB_CONCURRENCY") { 2 }

plugin :tmp_restart

Chú ý: Hạn chế việc cài puma bằng tay trên server vì có thể dẫn đến puma khai báo trong rails và trên server không đồng nhất. Khi đó sẽ phát sinh hiện tượng khi start puma sẽ tạo ra các zombie process

Setting nginx

Cài nginx trên server: sudo apt-get install nginx Xóa đi file trỏ mặc định của nginx: sudo rm /etc/nginx/sites_enabled/default Tạo file app.conf sudo vi /etc/nginx/conf.d/app.conf và copy đoạn sau vào:

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

server {
   listen 80 default_server deferred;
   # server_name localhost;

   root /home/duong_deploy/deploy/apps/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 404 /404.html;
     location /404.html {
       root /home/duong_deploy/deploy/apps/current/public/;
       try_files /404.html =404;
     }

   error_page 500 502 503 504 /50x.html;

   client_max_body_size 4G;
   keepalive_timeout 10;
}
  • listen 80: là port chạy ứng dụng
  • server unix:///home/duong_deploy/deploy/apps/shared/tmp/sockets/puma.sock: đường dẫn file puma.sock được tạo ra khi deploy
  • error_page: bạn có thể setting hiện thị khi server errors

Start nginx trên server: sudo service nginx restart Sau khi chaỵ nginx ta cần lên AWS console để mở cổng 80 cho server:

  • Vào Securiry group:
  • Chọn Edit inbound rules:
  • Thêm một rules mới và chọn HTTP:

Một số setting thêm trước khi chuẩn bị deploy

Trước khi deploy lên server mình cần tạo cấu trúc thư mục như mình đã khai báo trong file deploy.rb Dưới local mình cần tạo master.key như sau

EDITOR=vi bin/rails credentials:edit

Khi đó sẽ tạo ra 2 file master.keycredentials.yml.enc:

  • credentials.yml.enc: dùng để lưu các setting secret key của bên thứ 3 như AWS key, Github, .... File này mặc định mã hõa thành một chuỗi random mỗi khi bạn thay đổi nội dung file một chuỗi mới được tạo ra
  • master.key: dùng để mã hóa file credentials.yml.enc để lấy thông tin setting. File này mặc định được đưa vào .gitignore

Sau khi tạo xong master.key bạn sẽ phải đưa lên server vào thư mục shared/config bằng cách scp

scp master.key IP:/home/user/deploy/apps/shared/config/

Tiến hành deploy

cap production deploy BRANCH=deploy_test

Của mình là api và đây là kết quả sẽ xuất hiện error page của nginx: Đã có thể bắn api bình thường rồi:


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí