[RUBY] Easy deployment with Capistrano + AWS (EC2) + Rails

Introduction

I used Capistrano to deploy my own Rails app.

Basically, I referred to the following article. (Capistrano edition) The most polite AWS commentary in the world. Until you bring your Rails app to AWS using EC2

In this article, I've summarized the parts that were filled with errors along with the reference article, so I hope you find it helpful.

File to create

Local side

Server side (EC2)

Source code

Gemfile List of gems to install.

(local)Gemfile


group :development, :test do
 gem 'capistrano'
 gem 'capistrano-bundler'
 gem 'capistrano-rails'
 gem 'capistrano-rbenv'
end

group :production, :staging do
  gem 'unicorn'
end

Other literature may use the gem'capistrano3-unicorn', but it is not used here for detailed settings.

Capfile

This is the configuration file for the entire capistrano.

(local)Capfile


require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
#Set to read the file that describes the task. Specify the location and extension.
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

In the reference article, the last line is "~ / *. Rb", but in the current version, rake is the default, so use that.

Note that the file format of lib / capistrano / tasks / unicorn.rake that you will create later is **. Rake **. Let's make the set file format the same file format. (I got a little stuck here.)

config/deploy/production.rb

This file describes the settings of the production environment.

(local)config/deploy/production.rb


#Describe the IP of the EC2 server, the user name to log in to the EC2 server, and the role of the server.
server '**.***.***.***', user: 'yuki', roles: %w[app db web]

#Describe the key information to ssh login to the server to deploy
set :ssh_options, keys: '~/.ssh/app_key_rsa'

config/deploy.rb

Describe the settings that apply to both the production environment and the stading environment.

(local)config/deploy.rb


#Fixed version of capistrano
lock '3.14.1'

#Application name to deploy
set :application, 'golfour'

#Git repository to clone
set :repo_url, '[email protected]:app/app_aws.git'

#The branch to deploy. The default does not have to be master.
set :branch, 'master'

#The directory to deploy to.
set :deploy_to, '/var/www/rails/app'

# secret_base_Added to read the key
set :linked_files, %w[config/master.key]

#A file with a symbolic link.
set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/settings.yml', '.env')

#A folder with symbolic links.
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

#The number of versions to keep. Save up to 5 history.
set :keep_releases, 5

#version of ruby
set :rbenv_ruby, '2.5.1'

#The level of the log to output.
set :log_level, :debug

namespace :deploy do
  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart'
  end

  desc 'Create database'
  task :db_create do
    on roles(:db) do |_host|
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:create'
        end
      end
    end
  end

  desc 'Run seed'
  task :seed do
    on roles(:app) do
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:seed'
        end
      end
    end
  end

  after :publishing, :restart

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
    end
  end
end

The following two codes are added in addition to the reference article.

Here, for symbolic links (files that you don't want to open, like setting gitignore)

# secret_base_Added to read the key
set :linked_files, %w[config/master.key]

#A file with a symbolic link.
set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/settings.yml', '.env')

lib/capistrano/tasks/unicorn.rake

A file that describes the unicorn setup task.

(local)lib/capistrano/tasks/unicorn.rake


#Specify the unicorn pid file and configuration file directory
namespace :unicorn do
  task :environment do
    set :unicorn_pid,    "#{current_path}/tmp/pids/unicorn.pid"
    set :unicorn_config, "#{current_path}/config/unicorn/production.rb"
  end

  #Method to start unicorn
  def start_unicorn
    within current_path do
      execute :bundle, :exec, :unicorn, "-c #{fetch(:unicorn_config)} -E #{fetch(:rails_env)} -D"
    end
  end

  #Method to stop unicorn
  def stop_unicorn
    execute :kill, "-s QUIT $(< #{fetch(:unicorn_pid)})"
  end

  #Method to restart unicorn
  def reload_unicorn
    execute :kill, "-s USR2 $(< #{fetch(:unicorn_pid)})"
  end

  #Method to kill unicron
  def force_stop_unicorn
    execute :kill, "$(< #{fetch(:unicorn_pid)})"
  end

  #task to start unicorn
  desc 'Start unicorn server'
  task start: :environment do
    on roles(:app) do
      start_unicorn
    end
  end

  #task to stop unicorn
  desc 'Stop unicorn server gracefully'
  task stop: :environment do
    on roles(:app) do
      stop_unicorn
    end
  end

  #If unicorn is already running, restart it, if not, start it task
  desc 'Restart unicorn server gracefully'
  task restart: :environment do
    on roles(:app) do
      if test("[ -f #{fetch(:unicorn_pid)} ]")
        reload_unicorn
      else
        start_unicorn
      end
    end
  end

  #task to kill unicorn
  desc 'Stop unicorn server immediately'
  task force_stop: :environment do
    on roles(:app) do
      force_stop_unicorn
    end
  end
end

As I explained in Capfile, please note that it is unicorn.rake. If it is set in .rb in Capfile, it will be unicorn.rb.

config/unicorn/production.rb This is the unicorn configuration file.

(local)config/unicorn/production.rb



#Number of workers. See below
$worker = 2
#Decide how many seconds have passed before removing the worker
$timeout = 30
#Note that your application name, current, is added.
$app_dir = '/var/www//rails/golfour/current'
#Specify the port number to receive the request. See below
$listen = File.expand_path 'tmp/sockets/.unicorn.sock', $app_dir
#PID management file directory
$pid = File.expand_path 'tmp/pids/unicorn.pid', $app_dir
#Directory of files that spit out error logs
$std_log = File.expand_path 'log/unicorn.log', $app_dir

#Defined so that what is set above is applied
worker_processes  $worker
working_directory $app_dir
stderr_path $std_log
stdout_path $std_log
timeout $timeout
listen  $listen
pid $pid

#Set whether to hot deploy
preload_app true

#Define what to do before fork. See below
before_fork do |server, _worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill 'QUIT', File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

#Define what to do after fork. See below
after_fork do |_server, _worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

shared/config/settings.yml

(server)shared/config/settings.yml


production:
  secret_key_base: jr934ugr89vwredvu9iqfj394vj9edfjcvnxii90wefjc9weiodjsc9o i09fiodjvcijdsjcwejdsciojdsxcjdkkdsv 
(#Paste the generated random number)

shared/config/database.yml

(server)shared/config/database.yml



default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: golfour_development
  username: yuki
  password: password
  host: db
  socket: /tmp/mysql.sock

test:
  <<: *default
  database: golfour_test
  username: yuki
  password: password
  host: db-test
  socket: /tmp/mysql.sock

production:
  <<: *default
  database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  host: <%= ENV['DB_HOSTNAME'] %>

shared/.env

Describe the environment variables used in datacbase.yml here.

(server)shared/.env


DB_NAME=*****
DB_USERNAME=*****
DB_PASSWORD=*****
DB_HOSTNAME=*****

/etc/nginx/conf.d/app.conf

conf:(server)/etc/nginx/conf.d/app.conf


#Directory settings for various logs
  error_log  /var/www/rails/mumu/current/log/nginx.error.log;
  access_log /var/www/rails/mumu/current/log/nginx.access.log;
#Maximum capacity to receive processing
  client_max_body_size 2G;
  upstream app_server {
#Unicorn socket path to work with
    server unix:/var/www/rails/mumu/current/tmp/sockets/.unicorn.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 127.0.0.1; #Change it to your own IP address!
#Waiting time for the next request (seconds)
    keepalive_timeout 5;
#Directory to read static files
    root /var/www/rails/mumu/current/public;
#Cache directory
    try_files $uri/index.html $uri.html $uri @app;
    location @app {
      # HTTP headers
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass http://app_server;
    }
#Where to place the error page
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/mumu/current/public;
    }
  }

Deploy command

bundle exec cap production deploy

Error list

Insufficient memory

Occurs after deploying. It was caused by insufficient memory of EC2.

console


virtual memory exhausted

Solution: Create a swap file

This is a method of amplifying the memory so that it can be used by temporarily moving the unused memory to another location. If you refer to the following article, you can easily implement it, so please do.

reference: What is a swap file [Rails] Deploy to EC2 with Capistrano: Troubleshoot EC2 Out of Virtual Memory

Missing secret_key_base for 'production' environment

This is also an error that occurred when executing the deployment.

The cause was that the master.key file was missing. The secret_key_base can be found in the credentials.yml.enc file in the first place. However, this file was encrypted and I needed master.key to read it, but I was getting an error because I didn't have it.

solution

・ Create shared / master.key -Added master.key to symbolic links

By doing so, the error disappeared.

Reference: Automatically deploying with Capistrano gives "Missing secret_key_base for'production'environment" error

Recommended Posts

Easy deployment with Capistrano + AWS (EC2) + Rails
I tried automatic deployment with CircleCI + Capistrano + AWS (EC2) + Rails
For beginners! Automatic deployment with Rails6 + CircleCI + Capistrano + AWS (EC2)
[Rails] Nginx, Puma environment deployment & server study [AWS EC2]
[Docker] ECS automatic deployment --Docker edition-
[Terraform] ECS automatic deployment --Terraform edition-
I tried automatic deployment with CircleCI + Capistrano + AWS (EC2) + Rails
For beginners! Automatic deployment with Rails6 + CircleCI + Capistrano + AWS (EC2)
Deploy to EC2 with CircleCi + Capistrano
Launch Rails on EC2 (manual deployment)
rails AWS deployment is not reflected
[EC2 / Vue / Rails] EC2 deployment procedure for Vue + Rails
[Rails] AWS EC2 instance environment construction
[Rails] AWS deployment error encounter summary
Deployed using Docker + Rails + AWS (EC2 + RDS)
Using Java with AWS Lambda-Implementation-Stop / Launch EC2
[Rails] Image posting by CarrierWave [AWS EC2]
[AWS] Publish rails app with nginx + puma
Deploy Rails to ECS Fargate with AWS Copilot
Recipe for deploying Rails apps on AWS EC2
Create an EC site with Rails5 ⑤ ~ Customer model ~
Create an EC site with Rails 5 ⑩ ~ Create an order function ~
EC2 ✕ Manual deployment with Docker / docker-compose / Asset compilation
Easy to display hello world with Rails + Docker
Pass User credential to deployment destination with Jenkins + Capistrano
[Heroku] Associate AWS S3 with the deployed Rails app
Create an EC site with Rails 5 ⑨ ~ Create a cart function ~
Create an EC site with Rails5 ④ ~ Header and footer ~
Create an EC site with Rails5 ⑥ ~ seed data input ~
Launch Rails on EC2
Rails deploy with Docker
[Rails 6] RuntimeError with $ rails s
Rails Heroku deployment procedure
Handle devise with Rails
[Rails] Learning with Rails tutorial
Deploy RAILS on EC2
[Rails] Heroku deployment flow
[Rails] Test with RSpec
[Rails] Development with MySQL
Supports multilingualization with Rails!
Double polymorphic with Rails
Created the first LINEbot on AWS Cloud9 (Rails + Heroku deployment)
[AWS] What I'm glad I did with my first deployment
Gcc error solution with "pip install annoy" on AWS EC2
What to do if an SSH key authentication error occurs during automatic deployment to EC2 with Capistrano