Procedure for publishing an application using AWS (7) Automatic deployment by Capistrano

Introduction

I will describe the procedure to publish the application using AWS. This article uses Capistrano to automate the deployment process.

Introducing Capistrano

Capistrano is written in Ruby and Gem is open to the public. This article describes the procedure for introducing Capistrano to rails, but it seems that it can be used with other frameworks such as PHP.

Install Capistrano related gems

Edit the Gemfile as follows.

Gemfile


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

Execute the following command in the terminal to read the Gemfile.

bundle install

Execute the following command to generate Capistrano related files.

bundle exec cap install

The following files are generated. Details of each file will be described later.

Edit Capfile

Some libraries (Gems) need to be loaded for Capistrano to work. Capfile is a file for specifying which of the Capistrano-related libraries to load.

Edit the Capfile as follows. This will read the directory containing the file that describes the behavior required for deployment. Reference

Capfile


require "capistrano/setup"
require "capistrano/deploy"
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'

Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

Edit production.rb

Production.rb and staging.rb are created in the config / deploy directory. These files are files that describe the settings for deployment. production.rb is the production environment configuration file, and staging.rb is the staging environment configuration file. Describe the following contents in production.rb (staging.rb).

--Server host name --AWS server login username --Server role --Ssh settings --Other settings associated with the server

Modify production.rb as follows. (When the application's Elastic IP is 12.345.67.890)

config/deploy/production.rb


server '12.345.67.890', user: 'ec2-user', roles: %w{app db web}

What are development environment, test environment, staging environment, and production environment?

Edit deploy.rb

In deploy.rb created in the config directory, describe the settings common to the production environment and staging environment. Specifically, the following is described.

--Application name --git repository --SCM (Software Configuration Management) to be used --Task --Commands to be executed in each task

Delete the description of deploy.rb and change it as follows. (Here, as an example, Capistrano version is "3.11.0", application name is "testapp", Github user name is "test1234", repository name is "testapp", ruby version is "2.5.1", local PC The path to the SSH key (pem) of the EC2 instance in is "~ / .ssh / xxx.pem".)

config/deploy.rb


# config valid only for current version of Capistrano
#Described the version of capistrano. Continue to use the fixed version and prevent troubles due to version change
lock '3.11.0'

#Used to display Capistrano logs
set :application, 'testapp'

#Specify from which repository the app should be pulled
set :repo_url,  '[email protected]:test1234/testapp.git'

#Specify a directory to be referenced in common even if the version changes
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads')

set :rbenv_type, :user
set :rbenv_ruby, '2.5.1'

#Which public key to use for deployment
set :ssh_options, auth_methods: ['publickey'],
                  keys: ['~/.ssh/xxx.pem'] 

#Location of the file containing the process number
set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }

#Location of Unicorn configuration files
set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" }
set :keep_releases, 5

#Description for restarting Unicorn after the deployment process is finished
after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end
end

How to check the version of Capistrano

It is described in a file called gemfile.lock.

About DSL (Domain Specific Language)

A DSL is a program that is prepared in a pseudo manner in order to improve the efficiency of a specific process. For example, there is a description of set: name,'value'. At this time,'value' can be fetched by setting fetch name. The set value can also be retrieved from deploy.rb and production.rb. Also, there is a description of task: xx do ~ end. This adds a task in addition to the one required by Capfile. What is described here is executed at the time of cap deploy.

Directory structure after automatic deployment by Capistrano

When the automatic deployment by Capistrano is executed, the directory structure of the production environment changes. Multiple directories are created, such as Capistrano backing up applications. For example, the following directory is created.

--releases directory --Applications deployed through Capistrano are grouped in a directory called releases. Since the past application remains here, it is possible to revert to the previous version if something goes wrong during deployment. The description of set: keep_releases in deploy.rb specifies the number to be saved, and this time it is set to save the version for 5 times. --current directory --The latest in the releases directory is automatically copied to this directory. In other words, the contents of the application in this directory are the contents of the currently deployed application. --shared directory --The directories that are commonly referenced are stored even if the version changes. Specifically, log, public, tmp, vendor directories are stored.

Edit unicorn.rb

With the introduction of Capistrano, the directory structure of the production environment will change, so the description of unicorn.rb will also change accordingly.

Modify unicorn.rb as follows.

config/unicorn.rb


#Put the directory where the application code on the server is installed in a variable
#Change: Make the hierarchy one deeper
app_path = File.expand_path('../../../', __FILE__)

#Determine application server performance
worker_processes 1

#Specify the directory where the application is installed
#Change: specify current
working_directory "#{app_path}/current"

#Specify the location of the files required to start Unicorn
#Changed: Added shared directory
pid "#{app_path}/shared/tmp/pids/unicorn.pid"

#Specify the port number
#Changed: Added shared directory
listen "#{app_path}/shared/tmp/sockets/unicorn.sock"

#Specify a file to log errors
#Changed: Added shared directory
stderr_path "#{app_path}/shared/log/unicorn.stderr.log"

#Specify the file to record the normal log
#Changed: Added shared directory
stdout_path "#{app_path}/shared/log/unicorn.stdout.log"

#Set maximum time to wait for Rails application response
timeout 60

preload_app true
GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true

check_client_connection false

run_once = true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) &&
    ActiveRecord::Base.connection.disconnect!

  if run_once
    run_once = false # prevent from firing again
  end

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exist?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH => e
      logger.error e
    end
  end
end

after_fork do |_server, _worker|
  defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection
end

Modify the Nginx configuration file (rails.conf)

Since the directory structure has changed, change rails.conf as follows. (The case where the application name is "testapp" and the Elastic IP is "12.345.67.890" is described as an example)

rails.conf


upstream app_server {
  #Changed to refer to in shared
  server unix:/var/www/testapp/shared/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name 12.345.67.890;

#Set the maximum size of files uploaded from the client to 2 giga. The default is 1 mega, so keep it large
  client_max_body_size 2g;

  #Changed to refer to in current
  root /var/www/testapp/current/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
    #Changed to refer to in current
    root   /var/www/testapp/current/public;
  }

  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    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;
  }

  error_page 500 502 503 504 /500.html;
}

After changing the Nginx settings, SSH into the EC2 instance and Execute the following command to reload and restart.

sudo service nginx reload
sudo service nginx restart

Restart MySQL

Since it can not be deployed unless MySQL is started, Execute the following command and restart MySQL just in case.

sudo service mysqld restart

kill the unicorn master process

Kill the unicorn master process before performing the automatic deployment. First, execute the following command to check the process ID of unicorn master.

ps aux | grep unicorn

Kill the process confirmed by the following command. (This time, the process ID of unicorn master is 17877)

kill 17877

Push local fixes to master

Push all the files edited this time to the master branch.

Perform automatic deployment

Execute the following command in the local environment to perform automatic deployment. Success if there are no errors.

bundle exec cap production deploy

Check when an error occurs

--Run again --Are there any mistakes in the description? --Are you skipping the procedure?

Check with a browser

You can access the application by entering the Elastic IP in the URL field of your browser (no need to add: 3000).

Check when an error occurs

--Are there any errors in the development environment? --Are there any errors in /var/www/testapp/current/log/unicorn.stderr.log (if the repository name is "testapp") -Did you forget to push or pull? --Try restarting MySQL or Nginx --Try restarting the EC2 instance

Related article

Procedure to publish application using AWS (1) Create AWS account Procedure to publish application using AWS (2) Create EC2 instance [How to publish an application using AWS (3) EC2 instance environment construction] (https://qiita.com/osawa4017/items/8dc09203f84e04bf0e66) [Procedure to publish application using AWS (4) Create database] (https://qiita.com/osawa4017/items/7dba25f4fa30ab0b1246) [Procedure to publish application using AWS (5) Publish application] (https://qiita.com/osawa4017/items/6f3125fcc21f73024311) [Procedure to publish application using AWS (6) Install Nginx] (https://qiita.com/osawa4017/items/9b707baf6ddde623068c)

Recommended Posts

Procedure for publishing an application using AWS (7) Automatic deployment by Capistrano
Procedure for publishing an application using AWS (5) Publish an application
Procedure for publishing an application using AWS (6) Introduce Nginx
Procedure for publishing an application using AWS (4) Creating a database
For beginners! Automatic deployment with Rails6 + CircleCI + Capistrano + AWS (EC2)
[Circle CI] Procedure for automatic deployment when pushing to GitHub
I tried automatic deployment with CircleCI + Capistrano + AWS (EC2) + Rails
How to publish an application using AWS (3) EC2 instance environment construction
Procedure for building an authorization server using Authlete (CIBA compatible version)
[EC2 / Vue / Rails] EC2 deployment procedure for Vue + Rails