【Rails】SideKiqを用いて非同期処理(バックグラウンドジョブ)を実行する
今回、Rails のプロジェクトで、バックグラウンド処理を実装したくなり、SideKiq を用いて実装を行ったので、備忘録として残しておく。
- ジョブ管理
- Sidekiq とは
- Redis をインストールする
- Sidekiq を実装する
- (おまけ)AWS EC2へのデプロイ
- (おまけ)redis と sidekiq を別コンテナで起動する
- まとめ
- 関連記事
ジョブ管理
Rails アプリケーションで非同期的に処理を実行したい場合などがある。
例えば、以下のように、API 経由でメールを送信する場合。
def send_email # recieve request mail.send() # 処理が終わるまで待機する head(:ok) && return end
同期処理の場合、mail.send()
の処理が終わるまで、レスポンスを返すことができず、最悪の場合、リクエストタイムアウトになってしまう。こういう場合に、ジョブ管理を使うと、ジョブをキューに登録する処理だけ実行し、(並列処理をすることで)レスポンスを即座に返すことができる。
Rails5 には、ジョブ管理のフレームワークである Active Job がある。 ただし、Rails 自身が提供するのは、ジョブをメモリに保持するインプロセスのキューイングシステムだけであり、再起動などするとジョブは全て失われてしまう。
そのため、(Railsアプリケーションの起動とは別に)ジョブを処理するためにライブラリ固有のキューイングサービスを起動しておく必要がある。
詳細は、以下の公式ドキュメントを参考にして欲しい。
今回、Active Job とアダプタとして Sidekiq を使ってバックグラウンドジョブを実行することもできたが、Sidekiq 単体でも同様の処理が実行できたため、Sidekiq 単体での実装にすることとした。
Sidekiq とは
Simple, efficient background processing for Ruby.
Sidekiq はスレッドを使用して、同じプロセスで同時に多くのジョブを処理することができる。
Sidekiq を使うためには Client、Redis、Server の 3 つが必要となる。
- Client:Railsアプリケーションサーバー(ジョブを登録する)
- Redis:ジョブをキューイングするために必要(ジョブ管理をする)
- Server:並列処理をする(ジョブを実行する)ために、Railsアプリケーションとは別プロセスとして起動しておく(
bundle exec sidekiq
で起動することができる)
Redis をインストールする
今回、開発環境を Docker としていたので、ruby の環境下に Redis を追加することにした。
FROM ruby:2.6.3-alpine ENV LANG C.UTF-8 ENV WORKSPACE=/app # 基本モジュールインストール RUN apk update && \ apk add --no-cache tzdata libxml2-dev curl-dev make gcc libc-dev build-base libxslt-dev g++ mariadb-dev less git && \ rm -rf /usr/local/bundle/cache/* /usr/local/share/.cache/* /var/cache/* /tmp/* && \ apk del libxml2-dev curl-dev make gcc libc-dev g++ # node + yarn # python2はnodeインストール時の依存関係 RUN apk add --no-cache alpine-sdk \ nodejs-current \ nodejs-npm \ yarn \ python2 # install Redis RUN apk add --update --no-cache redis RUN mkdir -p $WORKSPACE WORKDIR $WORKSPACE ADD Gemfile $WORKSPACE/Gemfile ADD Gemfile.lock $WORKSPACE/Gemfile.lock RUN bundle install COPY . $WORKSPACE
Redis を起動する
$ redis-server --appendonly yes --daemonize yes
Sidekiq を実装する
1. gemを追加する
gem 'sidekiq' gem 'sinatra', require: false # ダッシュボードのため
bundle install
を実行して、gem をインストールする。
2. 起動時の設定を追加する
サーバー側とクライアント側の2種類の設定を追記しておく。
Sidekiq.configure_server do |config| config.redis = { url: 'redis://localhost:6379' } end Sidekiq.configure_client do |config| config.redis = { url: 'redis://localhost:6379' } end
3. 設定ファイルを作成する
:verbose: false :pidfile: ./tmp/pids/sidekiq.pid :logfile: ./log/sidekiq.log :concurrency: 10 :queues: - default
4. Woker作成(ジョブを登録する) 今回は、Testという名前のジョブを作成する。
$ bundle exec rails g sidekiq:worker Test create app/workers/testd_worker.rb create test/workers/test_worker_test.rb
class TestWorker < ApplicationController include Sidekiq::Worker def perform() p 'Hello SideKiq' end end
5. sidekiqを起動する
$ bundle exec sidekiq -C config/sidekiq.yml
6. ジョブを実行してみる Controller などから呼び出して実行することもできるが、試験的に Railsコンソールで実行してみる。(sidekiq を起動しいるシェルとは別に新しいシェルで実行する。)
$ bundle exec rails c > TestWorker.perform_async()
sidekiq 側で、「Hello SideKiq」が表示され、ジョブを実行することが確認できた。
7. 管理画面を追加する
require 'sidekiq/web' mount Sidekiq::Web, at: "/sidekiq"
再度、Rails サーバーを起動して、http://localhost:3000/sidekiq にアクセスすると、管理画面が表示される。
(おまけ)AWS EC2へのデプロイ
最後に、諸々の機能を実装して、本番のAWSにデプロイする際に、追加で実施した処理について残しておく。
Redis のインストール
$ amazon-linux-extras 6 postgresql10 available [ =10 =stable ] 8 redis4.0 available [ =4.0.5 =4.0.10 =stable ] 9 R3.4 available [ =3.4.3 =stable ] $ sudo amazon-linux-extras install redis4.0
Redis と Sidekiq のデーモン化
今回、Redis と Sidekiq のデーモン化は、前回紹介した Supervisor で行うことにした。 (前回の記事:【Linux】Supervisor でプロセス制御(Rails + Puma / Capistrano) 参照)
それぞれの設定ファイルを以下のように準備する。
[program:redis] command=/bin/redis-server /etc/redis.conf autostart=true autorestart=true user=redis
[program:sidekiq] directory=/srv/monoai-ranger-manager/current command=/bin/bash -c "/opt/rbenv/shims/bundle exec sidekiq -e production -C config/sidekiq.yml" autostart=true autorestart=true user=ec2-user startsecs=10 stdout_logfile=/srv/monoai-ranger-manager/shared/log/sidekiq.stderr.log logfile_maxbytes=1MB logfile_backups=10 redirect_stderr=true
(注意)
-e production
のオプションを付けないと、default が development になっているので、本番環境で環境変数の不一致が発生しているエラーが発生した。
supervisord を再起動させて、実行させる。
$ supervisorctl reload Restarted supervisord $ supervisorctl status redis RUNNING pid 17430, uptime 0:00:13 sidekiq RUNNING pid 17431, uptime 0:00:13
Capistrano 設定追加
以前は、Rails アプリケーションのみであったが、今回 Redis と Sidekiq が増えたので、Deploy 前後で全てのプロセスを再起動するように変更した。これで、deploy
コマンドのみでプロセスの再起動まで完結するようにできた。
~~ 省略 ~~ namespace :console do namespace :all do desc 'start service all' task :start do on roles(:app) do |_host| execute 'supervisorctl start all' end end desc 'stop service all' task :stop do on roles(:app) do |_host| execute 'supervisorctl stop all' end end end end before 'deploy:starting', 'console:all:stop' after 'deploy:finished', 'console:all:start'
(おまけ)redis と sidekiq を別コンテナで起動する
Sidekiqは、Railsで非同期処理を行うためのライブラリであり、実行の際にはRailsのプロセスとは別に実行する必要がある。負荷を分散するために、SidekiqをRailsアプリケーションとは別のコンテナで動かしたいことがあるかもしれない。その場合は、以下の設定にすることで、簡単に実現することができる。
version: '3' services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: password TZ: Asia/Tokyo ports: - 3306:3306 command: --default-authentication-plugin=mysql_native_password volumes: - mysql-data:/var/lib/mysql redis: image: redis:alpine ports: - 6379:6379 app: &app_base build: context: . volumes: - .:/app - gem-data:/app/vendor/bundle - log-data:/app/log ports: - 3000:3000 depends_on: - db - redis stdin_open: true tty: true sidekiq: <<: *app_base command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"] ports: - 3001:3001 volumes: mysql-data: gem-data: log-data:
また、以下のライブラリを追加する。
〜省略〜 gem 'redis-namespace'
SideKiq の設定を以下のように変更する。
Sidekiq.configure_server do |config| config.redis = { url: 'redis://redis:6379', namespace: 'sidekiq' } end Sidekiq.configure_client do |config| config.redis = { url: 'redis://redis:6379', namespace: 'sidekiq' } end
これで、コンテナを分離することができた。 (実際に、コンテナを分離することで、ログなどがそれぞれのコンテナに記載されてしまうので、正直ログを確認するのが面倒になってしまったので、そこあたりの設定をしっかりとしていきたい。)
まとめ
今回は、SideKiq を用いてジョブ管理をすることができた。定期処理や日時指定での実行も可能のため、今後も使っていきたいと思えた。
それでは、ステキな開発ライフを。
関連記事
【Linux】Supervisor でプロセス制御(Rails + Puma / Capistrano)
EC2(AmazonLinux2)に構築した Railsアプリケーションのプロセス管理、具体的にはデーモンプロセスとして動かすために、今回 Supervisor を使ったので、備忘録として残しておく。
- Supervisor v4.2.1
Supervisor とは
Supervisor とは、Python 製のプロセスの制御・管理ツールで、常時起動させたいスクリプトなどを簡単にデーモン化することができる。(デーモン化することで、クラッシュした際の再起動などを自動で行ってくれる。)
今回は、Rails + Puma で運用しているWEBアプリケーションの Puma のプロセスの制御を Supervisor で行えるようにした。
しかしながら、Linux であれば、「systemd」に service
として登録することで同様の設定を簡単に行うことができる。ただし、systemd の場合、基本は root ユーザーで起動停止管理を行う必要がある。
今回、systemd ではなく、Supervisor を採用した大きな理由としては、Capistrano を使って、一般ユーザーでも起動・停止のプロセス管理を実施したかったことが挙げられる。
Supervisor のコマンド
Supervisor に関して、使用するコマンドは2種類ある。(プロセス管理を行うには、supervisord
が事前に立ち上がっておく必要がある。)
- supervisord:supervisordの起動に使う
- supervisorctl:プロセスの管理・制御で使う
supervisord
supervisord の起動を行う
$ /usr/bin/supervisord -n -c /etc/supervisord.conf
オプションは複数あるが、上記ができていればいいのではないかと思う。
- -c:設定ファイルを指定する
- -n:フォアグラウンドで起動する(こちらを設定することで、
ctrl + c
で停止が可能となる。試験時には、便利。)
詳細は、公式ドキュメント 参照。
supervisorctl
プロセス全体の管理・制御を行う場合
# プロセス全体の起動 $ supervisorctl start all # プロセス全体のステータス確認 $ supervisorctl status # プロセス全体の停止 $ supervisorctl stop all # プロセス全体の再起動 $ supervisorctl restart all # 構成ファイルを再読み込みする(設定変更時、実施要) $ supervisorctl reread # supervisordを再起動する $ supervisorctl reload
1つのプロセスの管理・制御を行う場合
# 起動 $ supervisorctl start <name> # ステータス確認 $ supervisorctl status <name> # 停止 $ supervisorctl stop <name> # 再起動 $ supervisorctl restart <name>
詳細は、公式ドキュメント 参照。
EC2への設定方法
今回は、Puma のプロセスの制御を行う。
1. Supervisorインストール
python パッケージマネージャ(pip/easy_install)を使う。
$ sudo easy_install supervisor $ supervisord -v 4.2.1
2. デフォルトの設定ファイルを生成する
$ echo_supervisord_conf > /etc/supervisord.conf
3. アプリケーション用の設定ファイルを追加する
$ sudo mkdir /etc/supervisord.d
$ sudo vim /etc/supervisord.d/sample-app.conf
# プロセス名を設定する。今回は、sample-appとする。 [program:sample-app] # 実行ディレクトリを指定する(RailsのCapistranoのcurrentディレクトリ) directory=/app/sample-app/current # 実行コマンド(puma起動) command=/bin/bash -c "/opt/rbenv/shims/bundle exec pumactl start" environment=RAILS_ENV="production" autostart=true autorestart=true startsecs=3 user=ec2-user redirect_stderr=false # logファイルの保存先を指定する stderr_logfile=/app/sample-app/shared/log/sample-app.stderr.log stdout_logfile=/app/sample-app/shared/log/sample-app.stdout.log
詳細な設定項目は、公式ドキュメント を参照。
4. 設定ファイルを変更する
変更内容は、以下。
- アプリケーション用の設定ファイルを include できるようにする
- web の管理コンソールを開放し、同時に root 以外のユーザーからのクライアントコマンド許可を行う(Capistranoで実行できるようにしておく)
$ sudo vim /etc/supervisord.conf
# 以下の3箇所のコメントアウトを外してあげる [inet_http_server] port=127.0.0.1:9001 [supervisorctl] serverurl=http://127.0.0.1:9001 [include] files = /etc/supervisord.d/*.conf
# rootユーザー以外の場合に、以下のエラーが出た error: <class 'socket.error'>, [Errno 13] Permission denied: file: /usr/lib64/python2.7/socket.py line: 228
5. supervisord をデーモン化するために、systemd にサービスを登録する
Supervisor を使用するには supervisord をあらかじめ起動しておく必要があるため、systemd にサービスを登録して、supervisord をデーモン化しておく。
$ sudo vim /lib/systemd/system/supervisord.service
[Unit] Description=Supervisor process control system for UNIX Documentation=http://supervisord.org After=network.target [Service] ExecStart=/usr/bin/supervisord -n -c /etc/supervisord.conf ExecStop=/usr/bin/supervisorctl $OPTIONS shutdown ExecReload=/usr/bin/supervisorctl $OPTIONS reload KillMode=process Restart=on-failure RestartSec=50s [Install] WantedBy=multi-user.target
以下のコマンドを実行して、「supervisord.service」が systemd に登録されているか確認する。
systemctl list-unit-files --type=service
無事に、サービス登録がされていたら、以下のコマンドを実行して起動・ステータス確認・停止ができるかを確認する。
# 起動 sudo systemctl start supervisord # 状態確認 sudo systemctl status supervisord ● supervisord.service - Supervisor process control system for UNIX Loaded: loaded (/etc/systemd/system/supervisord.service; disabled; vendor preset: disabled) Active: active (running) since 2020-12-01 00:00:00 UTC # 停止 sudo systemctl stop supervisord
6. 起動している状態で、サービスの自動起動の設定を行う
$ sudo systemctl enable supervisord.service
Created symlink from /etc/systemd/system/multi-user.target.wants/supervisord.service to /usr/lib/systemd/system/supervisord.service.
7. 最後に、supervisor でプロセス制御を行う
# 起動 $ supervisorctl start sample-app # ステータス確認 $ supervisorctl status sample-app # 停止 $ supervisorctl stop sample-app # 再起動 $ supervisorctl restart sample-app # 設定ファイルを変更した場合には、再読み込みが必要 $ supervisorctl reload
無事に、Supervisor で Puma のプロセスを制御できれば設定は完了である。
きちんとデーモン化されているかを確認するために、以下を実行してみる。
# プロセスIDを確認する $ supervisorctl status sample-app sample-app RUNNING pid 26041, uptime 1:00:00 # プロセスを kill する $ kill -9 26041 # 再度、プロセスIDを確認する # プロセスIDが変わっていれば、デーモン化されていることが確認できたことになる $ supervisorctl status sample-app sample-app RUNNING pid 26045, uptime 1:00:00
これで、設定は完了である。
Capistrano の設定
Capistrano から supervisor を操作する場合は、以下の設定をしておく。
namespace :console do desc 'status service' task :status do on roles(:app) do |_host| execute 'supervisorctl status sample-app' end end desc 'start service' task :start do on roles(:app) do |_host| execute 'supervisorctl start sample-app' end end desc 'restart service' task :restart do on roles(:app) do |_host| execute 'supervisorctl restart sample-app' end end desc 'stop service' task :stop do on roles(:app) do |_host| execute 'supervisorctl stop sample-app' end end end
namespace を console
としているので、以下のようにして実行することができる。
# サービス再起動 bundle exec cap {stage} console:restart # サービス確認 bundle exec cap {stage} console:status
まとめ
無事に Supervisor でプロセス制御を行うことができた。簡単に設定できて、非常に使いやすいのではないかと思えた。
それでは、ステキな開発ライフを。
関連記事
【Rails】Capistrano で Rails アプリケーションを自動デプロイ(サーバー設定/デプロイ編)
先日の【Rails】Capistrano で Rails アプリケーションを自動デプロイ(設定編)に続き、今回は実際にデプロイするにあたり、サーバー側で事前に設定した内容に関して、備忘録として残しておく。
- Capistrano ver.3.14.1
- Rails ver.5.2.4
- AmazonLinux2
サーバーの環境構築に関しては、【EC2】Rails5 環境構築(Ruby + MySQL5.7 + Node.js + Nginx) を参照してほしい。
GitHub の SSH 設定
EC2に特定の人の GitHub のアカウントを紐付けてもいいが、今回は GitHub のリポジトリに公開鍵を登録することによって、SSH でアクセスできるようにする。
1. EC2 で公開鍵・秘密鍵を作成する(パスワードの設定もできるが、今回は設定せずに進めた。)
$ cd ~/.ssh $ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again:
2. 公開鍵を authorized_keys に追加して、この鍵ペアを使用してログインできるようにする
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
(※注意※)
「>>」で追加すること。「>」で上書きしてしまうと、EC2 に SSH 接続できなくなってしまうので、注意。
3. Clone したい GitHub のリポジトリに対して、公開鍵を登録する
GitHub のリポジトリのページにアクセスして、「Settings > Deploy keys」 に先ほど生成した id_rsa.pub
を登録する。
4. git cloneできるか確認する
一度、適当なフォルダを作り、ID/PASSを入力せずに、cloneできるか確認する。
$ mkdir /home/ec2-user/tmp $ cd /home/ec2-user/tmp $ git clone git@github.com:<--Repository Name-->
問題なく、clone できれば設定完了である。(/home/ec2-user/tmp は不要なので、削除しておく。)
Capistrano でデプロイするディレクトリ作成
Capistrano でディレクトリ構造を作成する際に、mkdir: permission denied
のエラーが発生した。解決方法としては、様々な方法がある。
- デプロイ先のディレクトリを誰でも書き込めるようにする
- デプロイ先のディレクトリにec2-userが所属するグループを設定する
- デプロイ先のディレクトリの所有者を変更する
- Capistrano側で、sudo権限でディレクトリを作成できるようにする
1は簡単であるが、ちょっとセキュリティ的に嫌だったので、今回は「2」を実施することにした。
$ sudo mkdir /app $ ls -ld drwxrwxr-x 2 root root 6 12月 18 04:49 . # /app の所有グループをwheelに変更する $ sudo chgrp wheel /app $ ls -ld drwxrwxr-x 2 root wheel 6 12月 18 04:49 # wheelグループに書き込み権限を付与する $ sudo chmod g+w /app
共通ファイルのアップロード
現状の設定ファイルは以下のようになっている。(先日の記事から一部、抜粋している。)
~~省略~~ # 共有ファイル append :linked_files, 'config/database.yml', 'config/master.key', 'config/credentials.yml.enc', 'config/puma.rb', '.env' # 共有フォルダ append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system', 'public/packs'
共有フォルダは自動で作成されるが、共有ファイルがないと、ファイル自体を参照することができないので、エラーが発生する。そのため、事前に config/database.yml
, config/master.key
, config/credentials.yml.enc
, config/puma.rb
, .env
のファイルを作成する。
scp
でのファイル転送、または直接サーバーへのファイル作成及び書き込みを行う。
$ scp [ローカルのファイルパス] [ユーザー名]@[サーバのIPアドレス]:[転送先のファイルパス] $ scp database.yml ec2-user@test-staging:/app/sample-app/shared/config/database.yml
config/database.yml
は、それぞれの環境のDBのアクセス情報を記載しておく必要がある。config/puma.rb
に関しては、次の章を参照してほしい。
Puma + Nginx の設定変更
アプリケーションのディレクトリが Capistrano のディレクトリ構造に変わるので、参照するパスを変更する。(前回の設定は、
【Puma】Rails 5.2 + Puma + Nginx のデプロイ設定を参照。)
# The directory to operate out of. directory '/app/sample-app/current' # Set the environment in which the rack's app will run. The value must be a string. environment ENV.fetch("RAILS_ENV") { "production" } # daemonize daemonize false # Store the pid of the server in the file at "path". pidfile '/app/sample-app/current/tmp/pids/puma.pid' # Use "path" as the file to store the server info state. This is used by "pumactl" to query and control the server. state_path '/app/sample-app/current/tmp/pids/puma.state' # Configure "min" to be the minimum number of threads to use to answer threads_count = ENV.fetch("RAILS_MAX_THREADS") { 16 } threads threads_count, threads_count # Bind the server to "url". "tcp://", "unix://" and "ssl://" are the only accepted protocols. bind 'unix:///app/sample-app/shared/tmp/sockets/puma.sock' on_restart do # Code to run before doing a restart. This code should close log files, database connections, etc. ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base) end # How many worker processes to run. workers 3 # Code to run in a worker before it starts serving requests. on_worker_boot do ActiveSupport.on_load(:active_record) do ActiveRecord::Base.establish_connection end end # Preload the application before starting the workers preload_app! # Additional text to display in process listing tag 'sample-app_puma' # Verifies that all workers have checked in to the master process within the given timeout. worker_timeout 60 # Change the default worker timeout for booting worker_boot_timeout 60
# 各種ログのディレクトリ設定 access_log /app/sample-app/shared/log/nginx/nginx-access.log; error_log /app/sample-app/shared/log/nginx/nginx-error.log; # 処理を受け取る最大許容量 client_max_body_size 2G; upstream app_server { server unix:///app/sample-app/shared/tmp/sockets/puma.sock; } server { listen 80; server_name _; keepalive_timeout 5; # パスの変更 root /app/sample-app/current/public; try_files $uri/index.html $uri.html $uri @app; location @app { 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; location = /500.html { # パスの変更 root /app/sample-app/current/public; } }
ログファイルを作成しておく
$ sudo mkdir -p /app/sample-app/shared/log/nginx $ sudo touch /app/sample-app/shared/log/nginx/nginx-access.log $ sudo touch /app/sample-app/shared/log/nginx/nginx-error.log
設定ファイルに問題がないか確認を行う(successful が表示されれば、OK.)
$ sudo nginx -t nginx: [warn] conflicting server name "_" on 0.0.0.0:80, ignored nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Nginxの設定を書き換えたので、再起動する
$ sudo systemctl reload nginx
デプロイ
これで、全ての準備が完了したので、いよいよデプロイである。
# staging環境の場合 $ bundle exec cap staging deploy # production環境の場合 $ bundle exec cap production deploy
上記のコマンドだけで、それぞれのタスクが実行され、デプロイが完了する。本当にとても便利である。
デプロイしただけでは、設定が反映されなかった。Puma を再起動する必要があったので、注意が必要である。
まとめ
Capistrano で無事に Rails アプリケーションの自動デプロイを実施することができた。手作業で毎回、サーバーにSSH接続→git pull→build→…などをやる必要がなくなって、本当にとても楽である。
それでは、ステキな開発ライフを。
関連記事
【Rails】Capistrano で Rails アプリケーションを自動デプロイ(設定編)
Rails アプリケーションを本番、ステージング環境にデプロイする際に、同じ作業を何回もやるのは正直面倒である。今回、Capistrano というデプロイ自動化ツールを使ったので、備忘録を残しておく。
- Capistrano ver.3.14.1
- Rails ver.5.2.4
サーバーの環境構築に関しては、【EC2】Rails5 環境構築(Ruby + MySQL5.7 + Node.js + Nginx)を参照してほしい。
Capistrano とは
Capistrano とは、Ruby 製のデプロイ自動化ツールである。Rails、Java、PHPなど、あらゆる言語やフレームワークのプロジェクトのデプロイを自動化することができる。
それぞれのタスクに応じた Capistrano のライブラリが用意されているので、それらを組み合わせたり、独自にタスクを作成することで、デプロイの設定や流れを簡単に作ることができる。
今回は、Railsアプリケーションの自動デプロイの設定について紹介する。デプロイの流れは以下のようになる。
- サーバーに SSH 接続する
- Capistrano のディレクトリ構成を構築する(シンボリックリンクの作成)
- git clone
- rbenv の動作確認
- bundle install(gemインストール)
- yarn install(node packageインストール)
- yarn build
(今回のRails アプリケーションには、一部 Vue コンポーネントを組み込んでおり、webpack で build する必要がある。)
Capistrano のフレームワークでは、デプロイ先のディレクトリ構成は以下のようになる。
Deploy.root ├─ current #公開されているアプリケーション(DocumentRoot) ├─ release #デプロイされたアプリケーションが保存されている ├─ shared #バージョンが変わっても共通で参照されるファイル ├─ repo #Capistranoの設定ファイル等 └─ revisions.log #リリースバージョンのログ
・releasesディレクトリ
Capistrano を通じてデプロイされたアプリケーションは、releasesディレクトリにバージョンごとに纏められる。デプロイ時に問題が発生しても、過去のデータが残っていることで、簡単に一つ前のバージョンに戻ることができる。
・currentディレクトリ
releasesディレクトリの中で最新のものが、自動的にcurrentディレクトリ内にコピーされる。(正確には、releasesの最新のディレクトリのシンボリックリンクがcurrentディレクトリに作られ、参照している。)DocumentRootには、current/publicを指定する。
$ ls -l current -> /app/sample-app/releases/20201224144141
・sharedディレクトリ
バージョンが変わっても共通で参照されるファイルやディレクトリが格納される。具体的には、config の設定ファイル、log、public、tmp ディレクトリが格納される。(これらも、シンボリックリンクが作られて、currentディレクトリの各ファイルから参照される形になる。)
インストール方法
まずは、Rails アプリケーションに Capistrano をインストールする。Gemfile に以下を追記する。
group :development do gem 'capistrano', '~> 3.10', require: false gem 'capistrano-rails', '~> 1.4', require: false gem 'capistrano-rbenv' gem 'capistrano-bundler' gem 'capistrano-yarn' end
$ bundle install # Capistrano の設定ファイルをインストールする $ bundle exec cap install
上記のコマンドを実行することで、以下のディレクトリが作成される。
Rails.root ├── Capfile #必要なライブラリ、タスクを読み込む ├── config │ ├─ deploy.rb #共通の設定ファイル │ └─ deploy │ ├─ production.rb #本番の設定ファイル │ └─ staging.rb #ステージングの設定ファイル └── lib └─ capistrano └─ tasks #オリジナルのタスクを作成する(Capfileで読み込ませる)
設定方法
設定する流れは、以下になる。
- Capfile で必要なライブラリ、タスクを記述する
- デプロイの設定を記述する
- 共通の設定:config/deploy.rb
- 環境別の設定:config/deply/{stage}.rb
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/migrations" require "capistrano/yarn" # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
デプロイの設定
今回は、ステージング環境である staging.rb のみを紹介する。
# Capistranoのバージョンを指定 lock "~> 3.14.1" # アプリケーションの名前 set :application, "sample-app" # GitリポジトリのURL set :repo_url, "git@github.com:sample-app.git" # サーバー上でのデプロイ先 set :deploy_to, '/app/sample-app' #default: /var/www/my_app_name # ログの出力設定 set :format, :airbrussh "log/capistrano.log", color: :auto, truncate: :auto # sudoを使う場合は、trueにしておく set :pty, true # 共有ファイル append :linked_files, 'config/database.yml', 'config/master.key', 'config/credentials.yml.enc', 'config/puma.rb', '.env' # 共有フォルダ append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system', 'public/packs' # 5回分のreleasesを保持する set :keep_releases, 5
#======================= # Rails set :rails_env, 'staging' set :migration_role, 'db' # db/migrateに変更がない場合は、Skip migration set :conditionally_migrate, false #======================= # Yarn set :yarn_flags, "--prefer-offline --no-progress" set :yarn_roles, :app set :yarn_env_variables, fetch(:yarn_env_variables, {}) set :yarn_bin, '/opt/nvm/versions/node/v14.15.2/bin/yarn' desc 'yarn build' after 'deploy:updated', :build do on roles :web do within release_path do execute :yarn execute :yarn, 'build' end end end #======================= # Rbenv set :rbenv_type, :system set :rbenv_custom_path, '/opt/rbenv' set :rbenv_ruby, '2.6.3' 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 #======================= # Bundler set :bundle_roles, :all set :bundle_binstubs, -> { shared_path.join('bin') } set :bundle_path, -> { shared_path.join('vendor', 'bundle') } set :bundle_without, %w(development test).join(' ') set :bundle_jobs, 8 #======================= # Server # Capistranoは実行ユーザの ~/.ssh/config に従うので、configのHostを指定する server 'sample-staging', user: 'ec2-user', roles: %w(app web), ssh_options: { forward_agent: true } #=======================
まとめ
今回は、デプロイ自動化ツールである Capistrano の設定方法に関して、まとめを行った。
次回、実際にデプロイする際に、サーバー側で一部設定をした後に、デプロイを行ったので、その部分に関しても、紹介したい。
それでは、ステキな開発ライフを。
関連記事
【Puma】Rails 5.2 + Puma + Nginx のデプロイ設定
先日、【EC2】Rails5 環境構築(Ruby + MySQL5.7 + Node.js + Nginx) について書いた。そして、今回は、アプリケーションサーバーである Puma について色々と調べて設定したので、備忘録を残しておく。(以前、Rails を使っていた時は、Unicorn が主流だったのに、Rails5 からは標準サーバーも Puma に変わったこともあり、今回は Puma を採用してみた。)
Pumaとは
Puma is a simple, fast, multi-threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications.
Puma は、シンプル、高速、マルチスレッドで多数の並列処理を実現できる Ruby/Rack アプリケーションの HTTP 1.1 サーバーである。
ここで、「マルチスレッド」、「Ruby/Rack アプリケーション」という言葉に注目してみる。
マルチスレッド
Puma はマルチスレッドで動いていることが大きな特徴である。対照的に、Unicorn は マルチプロセスで動いている。CPU は一度に1つのプロセスしか実行できず、マルチプロセスの場合、CPU のコア数に応じて並列処理できる数に制限がある。しかしながら、マルチスレッドの場合は、1つのプロセスに複数のスレッドが存在し、並行して処理を行うため、CPU のリソースが少ない中でも効率的に処理を行うことができる。
Ruby/Rack アプリケーション
Rack とは、Rubyにおけるサーバとアプリケーション/フレームワーク間のインターフェースの役割を果たすライブラリである。Python の WSGI に影響されて開発されたらしく、WebサーバとWebアプリケーション/フレームワーク間の標準インターフェースを定める「仕様」である。
rails serverコマンドは、Rack::Serverのオブジェクトを作成し、Webサーバーを起動している。
Railsガイド 参照
簡単な概略図はこのような形になる。
HTTP リクエストを web サーバーである Nginx や Apache で受け取り、Nginx はそのリクエストを Puma に Socket 通信で渡し、Puma はそのリクエストを Rack::Server に渡し、最終的に Rails の router に渡される。
インストールに関して
Rails5 以上であれば、標準Webサーバーなので、最初に生成された Gemfile に含まれているので、新規にインストールをする必要はないが、インストールする場合は、以下の通りである。
$ gem install puma $ puma
Rails で開発をする場合に、rails server
をすることで、Puma が起動している。
$ rails server Puma starting in single mode... * Version 3.9.1 (ruby 2.4.0-p0), codename: Private Caller * Min threads: 5, max threads: 5 * Environment: production * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stop
設定に関して
Puma は、Railsの場合、config/puma.rb
で詳細な設定をすることができる。もちろん、オプションコマンドで設定も可能であるが、毎回コマンドを打つのが面倒なので、設定ファイルに用いる。
開発時は、Default の設定で問題ないので以下の通りである。
# Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. port ENV.fetch("PORT") { 3000 } # Specifies the `environment` that Puma will run in. environment ENV.fetch("RAILS_ENV") { "development" } # Specifies the `pidfile` that Puma will use. pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } # Specifies the number of `workers` to boot in clustered mode. # workers ENV.fetch("WEB_CONCURRENCY") { 2 } # Use the `preload_app!` method when specifying a `workers` number. # preload_app! # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart
本番環境では、Nginx をリバースプロキシとして Puma の前段におき、UNIXドメインソケットを経由して Nginx とPuma が通信を行う。
Puma 側では、Pumaを起動した時に、「puma.sock」を生成するようにする。
#port ENV.fetch("PORT") { 3000 } #portの設置の部分はコメントアウトしておく。そうでないと、puma.sockを参照しないことがある模様。 bind "unix://#{Rails.root}/tmp/sockets/puma.sock"
Nginx 側では、/etc/nginx/conf.d/
に新規に設定ファイルを作成する。
upstream app_server { # for UNIX domain socket setups # 上記で設定したpuma.sockファイルの絶対パスを指定する server unix:///app/sample-app/tmp/sockets/puma.sock; } server { #ポート80番を許可する listen 80; #ホスト名を指定する server_name _; #静的ファイルのパスをドキュメントルートに設定する root /app/sample-app/public; # ドキュメントルート配下を以下の順番で検索する try_files $uri/index.html $uri.html $uri @app; # @appの場合は、以下の設定を読み込ませる location @app { 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; #upstreamで指定した名前で設定する } # エラーページの参照先を設定する error_page 500 502 503 504 /500.html; location = /500.html { root /app/sample-app/public; } }
Nginx の設定ファイルの修正が完了したら、設定に問題がないか確認を行う(successful が表示されれば、OK.)
$ sudo nginx -t nginx: [warn] conflicting server name "_" on 0.0.0.0:80, ignored nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Nginxの設定を書き換えたので、再起動する
$ sudo systemctl reload nginx
Pumaを起動する(どのコマンドでも起動することができる)
$ bundle exec rails s $ bundle exec puma $ bundle exec pumactl start
これで設定は完了である。
http://<---IP address->:80
にアクセスすると、Railsアプリケーションが表示される。
コマンドに関して
Puma には、上記でも示したように、3つの制御方法が存在する。(個人的には、pumactl
が使いやすいと思っている。)
- rails sの場合
- pumaコマンドの場合
- pumactlの場合
rails sの場合
Railsアプリケーションの起動と同時に、Pumaも起動する
$ bundle exec rails s -e production
Puma ではデフォルトの設定ファイル config/puma.rb
以外にも、実行環境に応じたconfig/puma/[実行環境].rb
という設定ファイルを参照させることができる。
-e production
の場合、config/puma/production.rb
参照する。(なかった場合は、デフォルトを参照する。)
停止する場合は、以下のように Puma のプロセスを確認して、kill
する
# process IDの確認をする $ ps aux | grep puma # プロセスを停止させる $ kill -9 <--process ID-->
pumaコマンドの場合
設定ファイルを指定して、起動する(-Cのオプションがない場合は、デフォルトを参照するが、設定することをオススメする)
$ bundle exec puma -C config/puma.rb
停止する場合は、rails s 同様に、プロセスを確認して、kill
する
pumactlの場合
pumactl
を使う場合は、オプションコマンドなどで設定をすることができないので、config/puma.rb
にきちんと設定を書いておく必要がある。
# 起動する $ bundle exec pumactl start # 状態を確認する $ bundle exec pumactl status # 停止する $ bundle exec pumactl stop # 再起動する $ bundle exec pumactl restart
まとめ
今回は、アプリケーションサーバーである Puma についてまとめを行った。
それでは、ステキな開発ライフを。