はじめに
皆さんは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の起動、停止、再起動のコマンドは下記のとおりです。
1 2 3 4 5 6 7 8 | # 起動 $ bundle exec pumactl start # 停止 $ bundle exec pumactl stop # 再起動 $ bundle exec pumactl restart |
pumaコマンドでも操作することができますが、オプションをいくつも指定しないといけないのでpumactlを使うことを推奨されています。
pumactlを使う場合、デフォルトでconfig/puma.rbを読みに行ってくれます。
デプロイ設定
Pumaについて簡単に説明したところで、デプロイ設定に移ります。
Gemを追加
以下を追加してbundle installします。
1 2 3 4 5 6 7 | 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の設定
まず以下のコマンドで設定ファイルの雛形を生成します。
1 | $ bundle exec cap install |
次にCapfileを以下のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 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を以下のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | # 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を以下のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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に登録しておく必要があります。
1 2 | # 鍵の登録 $ ssh-add ~/.ssh/your_id_rsa |
デプロイコマンド
初回のみ以下のコマンドを実行してディレクトリを作成します。
1 | $ bundle exec cap production deploy:initial |
デプロイは以下のコマンドで行なえます。
1 | $ bundle exec cap production deploy |
おまけ
nginx.confのサンプルを記載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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の管理の仕方が変わっているなど)苦労しましたが、なんとか解決できました。
現状ではデプロイのたびにパッケージのインストールが走ってしまいデプロイに時間がかかっているので、手が空いたら見直していきたいと思います。
参考
- http://arakaji.hatenablog.com/entry/2015/08/03/200502
- https://blog.willnet.in/entry/2015/02/24/155006
- https://casualdevelopers.com/tech-tips/how-to-create-reverse-proxy-with-nginx-on-heroku/
- https://qiita.com/himatani/items/33cbbe84815534f8b479
- https://morizyun.github.io/ruby/rails-capistrano3-deploy-rbenv-puma.html
- https://qiita.com/himatani/items/3d4293b964255b774769