カテゴリー: BackEnd

Rails5.2+Pumaのデプロイ設定

はじめに

皆さんはRailsで開発する際のアプリケーションサーバは何をつかっていますか?

私は以前からPumaに興味を持っていたのですが、出た当初は他のGemのマルチスレッド対応が心配だったこと、Capistranoの設定を使いまわしていたことから、もっぱらNginx+Unicornな環境ばかり構築してきました。

Pumaが出てからだいぶ経ったのでそろそろ使ってみようということで、新しく開発したサービスでPumaを使ってみることにしました。

そこで今回はデプロイ関連の設定について書いてみようと思います。

環境

本番サーバの環境は下記のとおりです。

  • CentOS 7.5
  • Nginx 1.15.1
  • ruby 2.5.1
  • rails 5.2.0
  • capistrano 3.11.0
  • capistrano3-puma 3.1.1

Pumaとは?

Pumaはスピードと並列性を追求したアプリケーションサーバです。
http://puma.io/

Unicornとの比較

Unicornは複数のworkerプロセスで並列処理を行いますが、Pumaは複数のスレッドで並列処理を行います。

Unicornと比べてPumaの利点としてはスロークライアントを起こしにくい点が挙げられます。
反面、コードの書き方を注意しないとrace conditionを引き起こす危険性があります。

Unicornのスロークライアント問題に関してはNginxなどのリバースプロキシをたてることで解消できます。
herokuには標準ではリバースプロキシが無いようですが、DockerコマンドとHerokuコマンドを使ってたてることができるようです。

Pumaのスレッド

Pumaでは予めスレッドをスレッドプールに用意しておき、リクエストが来たときにスレッドに処理を任せます。

MRIでは同時に1つのスレッドしか動作させられない(グローバルVMロック)ことから、Rubyの処理系はRubiniusやJRubyが良いようです。

Pumaの使い方

Pumaの起動、停止、再起動のコマンドは下記のとおりです。

# 起動
$ bundle exec pumactl start

# 停止
$ bundle exec pumactl stop

# 再起動
$ bundle exec pumactl restart

pumaコマンドでも操作することができますが、オプションをいくつも指定しないといけないのでpumactlを使うことを推奨されています。
pumactlを使う場合、デフォルトでconfig/puma.rbを読みに行ってくれます。

デプロイ設定

Pumaについて簡単に説明したところで、デプロイ設定に移ります。

Gemを追加

以下を追加してbundle installします。

group :development do
  gem 'capistrano', require: false
  gem 'capistrano-bundler', require: false
  gem 'capistrano-rails', require: false
  gem 'capistrano-rbenv', require: false
  gem 'capistrano3-puma', require: false
end

Capfileの設定

まず以下のコマンドで設定ファイルの雛形を生成します。

$ bundle exec cap install

次にCapfileを以下のように修正します。

# Load DSL and set up stages
require 'capistrano/setup'

# Include default deployment tasks
require 'capistrano/deploy'

# Load the SCM plugin appropriate to your project:
require 'capistrano/scm/git'
install_plugin Capistrano::SCM::Git

# Include tasks from other gems included in your Gemfile
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano/puma'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
install_plugin Capistrano::Puma

deploy.rbの設定

共通のデプロイ設定を行います。
config/deploy.rbを以下のように修正します。

# config valid for current version and patch releases of Capistrano
lock "~> 3.11.0"

set :application, 'your_app_name'
set :repo_url, 'git@github.com:your_repository.git'

# Default deploy_to directory is /var/www/my_app_name
set :deploy_to, "/your_app_root/#{fetch(:application)}"

# You can configure the Airbrussh format using :format_options.
# These are the defaults.
# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto
set :format, :pretty
set :log_level, :debug

set :puma_threads,  [4, 16]
set :puma_workers, 0

# Default value for :pty is false
set :pty, true

# Default value for linked_dirs is []
# append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle}

# credentials.yml.encのdecryptに必要。今回は手動で転送する。
set :linked_files, fetch(:linked_files, []).push("config/master.key")

# rbenvをシステムにインストールしたか? or ユーザーローカルにインストールしたか?
set :rbenv_type, :user # :system or :user
set :rbenv_ruby, File.read('.ruby-version').strip
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w{rake gem bundle ruby rails}
set :rbenv_roles, :all # default value

# pumaの設定
set :puma_bind,       "unix://#{shared_path}/tmp/sockets/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.access.log"
set :puma_error_log,  "#{release_path}/log/puma.error.log"
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true  # Change to false when not using ActiveRecord
# rbenvをユーザローカルにインストールする場合に必要
append :rbenv_map_bins, 'puma', 'pumactl'

# Default value for keep_releases is 5
set :keep_releases, 3

# bundle installの並列実行数
set :bundle_jobs, 4

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`
      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

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

  after  :migrate,      :seed
  before :starting,     :check_revision
  after  :finishing,    :compile_assets
  after  :finishing,    :cleanup
end

注意点は下記の2点です。

  • shared/config/にローカルのmaster.keyをアップロードする
  • rbenvをユーザローカルにインストールする場合、「append :rbenv_map_bins, ‘puma’, ‘pumactl’」を追加する

production.rbの設定

本番環境のデプロイ設定を行います。
config/deploy/production.rbを以下のように修正します。

set :stage, :production
set :rails_env, 'production'

set :branch, ENV['BRANCH_NAME'] || 'master'

set :migration_role, 'db'

# server-based syntax
server 'xxx.xxx.xxx.xxx', user: 'your_user_name', roles: %w{web app db}

# Custom SSH Options
set :ssh_options, {
    keys: %w{~/.ssh/your_ssh_key},
    forward_agent: true,
    auth_methods: %w(publickey)
}

今回はサーバにアクセスするための鍵とGitHubにアクセスするための鍵が異なるので、forward_agentを有効にしています。
この場合、予め鍵をssh-agnetに登録しておく必要があります。

# 鍵の登録
$ ssh-add ~/.ssh/your_id_rsa

デプロイコマンド

初回のみ以下のコマンドを実行してディレクトリを作成します。

$ bundle exec cap production deploy:initial

デプロイは以下のコマンドで行なえます。

$ bundle exec cap production deploy

おまけ

nginx.confのサンプルを記載しておきます。

user  your_user_name;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;
    gzip                on;
    gzip_types text/css text/javascript application/javascript image/svg+xml;

    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

    upstream puma {
        server unix:///your_app_root/shared/tmp/sockets/puma.sock;
    }

    server {
        listen 80;
        listen [::]:80;
        server_name your_server_name.com;
        root  /your_app_root/current/public;

        include /etc/nginx/default.d/*.conf;

        location ~* \.(html|jpeg|jpg|gif|png|css|js|ico|woff|svg)$ {
             access_log off;
             expires 7d;
             break;
        }

        location ~ ^/assets/ {
            root /your_app_root/shared/public;
        }

        location / {
            try_files $uri $uri/index.html $uri.html @webapp;
        }

        location @webapp {
            proxy_read_timeout 300;
            proxy_connect_timeout 300;
            proxy_redirect off;
            proxy_set_header X-Forwarded-Host  $host;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass http://puma;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

さいごに

実際の環境構築ではDBスキーマの管理をridgepoleで行っていたり、パッケージの管理をyarnで行っていたり、wheneverでcronの設定を管理したりしているので、deploy.rbはもっと長くなっています。

今回の環境構築では以前と比べて変更点が多く(secret_key_baseの管理の仕方が変わっているなど)苦労しましたが、なんとか解決できました。

現状ではデプロイのたびにパッケージのインストールが走ってしまいデプロイに時間がかかっているので、手が空いたら見直していきたいと思います。

参考

Hiroki Ono

シェア
執筆者:
Hiroki Ono
タグ: Rails

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前