1. Giới thiệu
AWS (Amazon Web Services) là một trong những nền tảng cloud phổ biến nhất hiện nay. Trong bài viết này, chúng ta sẽ hướng dẫn cách triển khai một ứng dụng Ruby on Rails lên EC2 với Capistrano, PostgreSQL, Nginx và Puma.
2. Tạo tài khoản AWS
-
Truy cập AWS và tạo 1 tài khoản cho riêng mình.
-
Điền thông tin cá nhân, địa chỉ email và thiết lập mật khẩu.
-
Nhập thông tin thanh toán (AWS cung cấp Free Tier trong 12 tháng).
-
Xác minh danh tính bằng số điện thoại.
-
Chọn gói Basic Support - Free.
-
Hoàn tất đăng ký và đăng nhập vào AWS Management Console.
3. Chuẩn bị server EC2
Khởi tạo EC2 Instance
-
Truy cập EC2 Dashboard từ AWS Console.
-
Nhấn Launch Instance để truy cập trang tạo Instance.
-
Tiếp theo chúng ta sẽ điền 1 số thông tin cần thiết như sau:
-
Name: Đặt tên cho instance (ví dụ: RailsApp).
-
Amazon Machine Image (AMI): Chọn Ubuntu 22.04.
-
Instance Type: Chọn t2.micro (Free Tier).
-
Key Pair: Tạo hoặc chọn key pair để đăng nhập SSH.
-
Security Group: Bạn hãy tạo security group với inbound rules là HTTP và SSH để cho phép truy cập port 22 (SSH), 80 (HTTP).
-
-
Nhấn Launch Instance và chờ instance khởi động.
Kết nối EC2 Instance
-
Sau khi Instance ở trạng thái Running thì bây giờ chúng ta có thể Connect được vào Instance bằng cách click button Connect
-
Tại màn hình Connect to Instance, AWS đã hướng dẫn cách chúng ta có thể connect được vào Instance
-
Đầu tiên chúng ta phải cấp quyền cho file key pem đã tạo trước đó bằng cách chạy lệnh
chmod 400 "rails-key.pem"
-
Tiếp theo chúng ta sẽ chạy lệnh bên dưới để connect vào Instance
ssh -i "rails-app.pem" ubuntu@ec2-13-208-183-82.ap-northeast-3.compute.amazonaws.com
-
Sau khi chạy lệnh trên, chúng ta sẽ connect được vào Instance
Tạo user để deploy
-
Tiếp theo chúng ta sẽ tạo 1 user để deploy, ở đây mình sẽ đặt tên luôn là
deploy
cho dễ nhớubuntu@ip-172-31-43-219:~$ sudo adduser deploy
-
Sau khi tạo user deploy thành công, bạn có thể access bằng cách chạy lệnh bên dưới
ubuntu@ip-172-31-43-219:~$ sudo su - deploy
Tuy nhiên có một vấn đề phát sinh là user deploy chúng ta vừa tạo chỉ có thể truy cập bằng cách dùng quyền sudo, khi chúng ta deploy thì không thể chạy sudo được, vì vậy chúng ta cần phải cấp quyền sudo cho deploy user.
-
Chúng ta sẽ quay lại user
ubuntu
và thực hiện như bên dưới:Mở file
/etc/sudoers
sudo vi /etc/sudoers
Và thêm đoạn bên dưới vào file
%deploy ALL=(ALL) ALL
-
Sau khi lưu file, user deploy bây giờ đã có quyền sudo
Add ssh key authentication
-
Câu chuyện đặt ra là làm thế nào để connect trực tiếp với user
deploy
từ máy local mà không cần dùng password hay thông qua userubuntu
. Giải pháp ở đây là chúng ta sẽ thiết lập ssh authentication -
Đầu tiên trên máy local các bạn hãy chạy lệnh bên dưới để sinh ra ssh key
ssh-keygen -t rsa -C rails-app
-
Sau đó gõ lệnh
cat ~/.ssh/id_rsa.pub
để lấy nội dụng public_key của ssh rồi copy -
Tiếp theo chúng ta hãy connect vào user
deploy
và chạy các lệnh bên dướimkdir .ssh sudo chmod 700 .ssh vi ~/.ssh/authorized_keys # paste nội dung file public_key của ssh trên local bạn vào đây sudo chmod 600 ~/.ssh/authorized_keys
-
Sau khi lưu thì giờ đây, chúng ta có thể connect tới user
deploy
mà không cần password nữa -
Trên máy local các bạn hãy gõ
ssh deploy@15.152.49.239
,15.152.49.239
ở đây chính là Public IPv4 của EC2 Instance của bạn
4. Cài đặt môi trường
Giờ đây server của chúng ta giống như 1 máy local bình thường, các cài đặt cơ bản như Rbenv, Ruby, Rails, PostgreSQL,... các bạn có thể tham khảo trang này để cài nhé.
5. Cấu hình Project
-
Chúng ta sẽ sử dụng gem Capistrano để hỗ trợ cho việc deploy, trong Gemfile, hãy thêm những gem bên dưới và chạy
bundle install
gem 'capistrano' gem 'capistrano3-puma' gem 'capistrano-bundler', require: false gem 'capistrano-rails', require: false gem 'capistrano-rbenv' group :development, :test do gem 'capistrano-local-precompile', '~> 1.2.0', require: false end
-
Tiếp theo cần phải cài đặt Capistrano, chúng ta hãy chạy lệnh:
cap install
, sau khi chạy thì nó sẽ tự động sinh cho chúng ta những file nhưdeploy.rb
,deploy/staging.rb
,deploy/production.rb
,Capfile
Đầu tiên các bạn hãy mở file
Capfile
và sửa thành nội dung bên dưới:require 'capistrano/setup' require 'capistrano/deploy' require 'capistrano/scm/git' install_plugin Capistrano::SCM::Git require 'capistrano/rbenv' require 'capistrano/bundler' require 'capistrano/local_precompile' require 'capistrano/puma' install_plugin Capistrano::Puma install_plugin Capistrano::Puma::Systemd set :rbenv_type, :user set :rbenv_ruby, '3.1.2' Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
Đây là phần cấu hình Capistrano để triển khai việc deploy Rails, các bạn hãy sửa ruby version sao cho phù hợp với project của mình. Ở đây mình chú ý tới config liên quan tới
local_precompile
, config này giúp biên dịch các file assets trên máy local thay vì trên server, sẽ phù hợp với những server yếu dùng free tier như nàyrequire 'capistrano/local_precompile'
-
Bây giờ chúng ta sẽ mở file
deploy.rb
và sửa như bên dưới:# frozen_string_literal: true # config valid only for current version of Capistrano lock '3.19.2' set :application, 'rails-app' set :repo_url, 'git@github.com:dangxuanphuc/rails-app.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 :rbenv_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, 'production')) 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 set :precompile_env, 'production' set :assets_dir, 'public/assets'
-
Ở đây các bạn chỉ cần quan tâm đến 2 dòng bên dưới, hãy đổi tên application và repo theo đúng project mà các bạn muốn deploy
set :application, 'rails-app' set :repo_url, 'git@github.com:dangxuanphuc/rails-app.git'
-
Tiếp theo là file
production.rb
, các bạn hãy sửa thành nội dung bên dưới, riêng phầndeploy_to
,IPv4
vàuser
thì các bạn hãy đổi thành thông tin server của mìnhset :stage, :production set :rails_env, :production set :deploy_to, '/var/www/rails_app' set :branch, 'master' server '15.152.49.239', user: 'deploy', roles: %w[web app db], ssh_options: { keys: ['~/.ssh/id_rsa'], forward_agent: true, auth_methods: %w[publickey] }
-
Một vấn đề phát sinh ở đây là làm thế nào để server EC2 của chúng ta có thể lấy source code từ repo trên github về mỗi khi chúng ta deploy. Giải pháp ở đây là ssh authentication, chúng ta chỉ cần tạo ssh key cho thằng server EC2 rồi lấy public key thêm vào github là được.
Chúng ta hãy connect tới user
deploy
và chạy lệnh bên dưới để generate ssh keyssh-keygen -t rsa -C "rails-server"
Sau đó lấy public key thêm vào phần Deploy keys
OKe như vậy là xong, giờ con server EC2 này đã có thể lấy source code mỗi khi deploy
6. Cấu hình nginx
-
Connect vào user deploy và tiến hành cài đặt Nginx
sudo apt-get install nginx
-
Chạy 2 câu lệnh bên dưới để xóa đi file default config và tạo 1 file
default.conf
sudo rm /etc/nginx/sites-enabled/default sudo vi /etc/nginx/conf.d/default.conf
-
Bên dưới là nội dung file config của mình, các bạn có thể tham khảo
upstream app { server unix:/var/www/rails_server/shared/tmp/sockets/puma.sock fail_timeout=0; } server { listen 80; listen [::]:80; server_name _; root /var/www/rails_server/current/public; try_files $uri $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; }
-
Trong đoạn config trên, chúng ta sẽ chỉ cần để ý một số thông tin sau:
server unix:/var/www/rails_server/shared/tmp/sockets/puma.sock fail_timeout=0;
Ứng dụng Rails của chúng ta sử dụng máy chủ Puma, đây là đoạn config giúp Nginx kết nối và chuyển tiếp request tới Puma thông qua Unix socket thay vì cổng TCP
listen 80; listen [::]:80;
Server của chúng ta sẽ lắng nghe trên cổng 80 (HTTP) và cả IP6
root /var/www/rails_server/current/public;
Đây là thư mục chứa ứng dụng Rails, thư mục
public
là nơi chứa assets, favicon và các file tĩnhtry_files $uri $uri/ @app;
Dòng này sẽ kiểm tra nếu
$uri
tồn tại thì trả về file đó,$uri/
tồn tại thì trả về thư mục đó, nếu không thì sẽ chuyển tiếp request tới@app
-
Lưu file và khởi động lại nginx
sudo service nginx restart
7. Cấu hình Puma
-
Tiếp theo chúng ta cần phải cấu hình puma, các bạn hãy chạy lệnh bên dưới để tạo ra file
puma.service
sudo vi /etc/systemd/system/puma.service
-
Sau đó hãy copy nội dung config bên dưới, chú ý là các bạn hãy đổi một số thông tin như
WorkingDirectory
hayExecStart
theo đúng thông tin server của các bạn nhé[Unit] Description=Puma HTTP Server for rails_server After=network.target [Service] Type=simple User=deploy WorkingDirectory=/var/www/rails_server/current ExecStart=/bin/bash -lc 'export PATH="$HOME/.rbenv/shims:$PATH"; RAILS_ENV=production bundle exec puma -C /var/www/rails_server/current/config/puma.rb' Restart=always [Install] WantedBy=multi-user.target
-
Sau khi config xong, hãy chạy 1 vài lệnh bên dưới để xác nhận lại xem puma đã chạy thành công hay chưa
sudo systemctl daemon-reload sudo systemctl enable puma sudo systemctl start puma sudo systemctl status puma
-
Như vậy là puma đã chạy thành công
-
Trong trường hợp status của puma là failed, thì chúng ta có thể check log bằng cách chạy lệnh bên dưới
journalctl -u puma --no-pager --lines=50
8. Tạo thư mục deploy
-
Tiếp theo chúng ta cần phải tạo thư mục deploy trên server, mục đích là sẽ deploy ứng dụng vào thư mục này
cd /var/www mkdir rails-server sudo chown -R deploy:root rails-server cd rails-server mkdir shared mkdir shared/config
-
Chúng ta sẽ config database của mình ở đây, các bạn hãy chạy lệnh
vi /var/www/rails-server/shared/config/database.yml
và tham khảo nội dung bên dưới, ở đây mình dùng PostgreSQL, các bạn dùng MySQL thì hãy đổi về config tương ứng nhéproduction: adapter: postgresql encoding: unicode database: rails_app_production username: postgres password: Aa@123456
-
Tiếp theo, chúng ta cần phải config secret key base, trên local các bạn hãy tạo key bằng cách chạy
RAILS_ENV=production rails secret
rồi thêm vào trong filevi /var/www/rails-server/shared/config/application.yml
SECRET_KEY_BASE: "<your_secret_key_base>"
9. Deploy
-
Trước khi deploy, chúng ta cần phải cài đặt puma, tuy nhiên do việc cài đặt này nó sẽ yêu cầu chạy ở quyền sudo, nên trước tiên chúng ta cần phải thêm config ko yêu cầu password khi chạy với quyền sudo. Các bạn hãy vào file
/etc/sudoers
và sửa lại như saudeploy ALL=(ALL) NOPASSWD:ALL
-
Sau đó hãy chạy lệnh bên dưới để cài đặt puma, sau khi cài đặt thành công thì các bạn hãy vào file
/etc/sudoers
và xóa phầnNOPASSWD
với user deploy đi nhébundle exec cap production puma:install
-
Tiếp theo chúng ta sẽ chạy lệnh để precompile trên local trước khi deploy
bundle exec cap production deploy --dry-run
-
Cuối cùng hãy chạy lệnh bên dưới để deploy
bundle exec cap production
-
Sau khi deploy thành công, bạn hãy khởi động lại nginx, puma bằng câu lệnh bên dưới và truy cập vào IPv4 để xác nhận thành quả
sudo systemctl restart puma nginx
-
Lưu ý có một số trường hợp làm mất file
master.key
thì các bạn phải thực hiện tạo lạimaster.key
trên server bằng cách saucd /var/www/rails-server/current rm config/credentials.yml.enc RAILS_ENV=production VISUAL="vim" bin/rails credentials:edit
-
Sau đó paste secret key base đã tạo trên local vào với cú pháp như sau:
production: SECRET_KEY_BASE: "<your_secret_key_base>"
Trên đây mình đã giới thiệu về cách deploy ứng dụng Rails lên EC2 sử dụng Capistrano, Nginx, Puma. Bài viết có thể còn nhiều hạn chế, rất mong nhận được góp ý từ mọi người để mình cải thiện trong những bài sau. Cảm ơn các bạn đã theo dõi và ủng hộ!