sinatraとsidekiqを組み合わせて、簡単なWeb APIを作ってみた
sidekiqは、複数のjobを非同期実行させることができるrubyライブラリです。割と簡単にjobの並列処理がrubyで書けて、便利そうです。今回、年末から年始にかけて、ゆる〜くsinatraとsidekiqを組み合わせて、非同期型のjob実行Web APIを作ってみたので、まとめておきます。
背景
GUIしか存在しない、いにしえのツールをWeb API化(poltergeistを使ってブラウザ操作部分を自動化し、sinatraとsidekiqを組み合わせてAPIを作成)したかったからです。
なお、今回作ったものはGitHubにあげました。
開発環境
今回Dockerも触ってみたかったので、ローカル開発環境をDockerで作りました。
- OS X 10.11.1 (15B42)
- Docker version 1.8.2, build 0a8c2e3
- docker-machine version 0.4.1 (e2c88d6)
- docker-compose version: 1.4.2
docker-machine
docker-machineでDocker環境(VirtualBox上にDocker用VM)を作ります。
$ docker-machine start dev
docker-machine envの結果を環境変数に設定します。
$ eval "$(docker-machine env dev)"
application用のDockerfile
sinatraとsidekiqを動かすapplication用コンテナのDockerfileを、以下のように作ります。一度/tmpにGemfileとGemfile.lockをコピーしてbundle installすることで、docker runするたびにgem installが動くことを回避し、うまくDockerのキャッシュを使えるようにしています。また、sinatraで使うポート5000をEXPOSEし、外からアクセスできるようにします。
FROM ruby:2.2.4 RUN gem install bundler WORKDIR /tmp ADD Gemfile Gemfile ADD Gemfile.lock Gemfile.lock RUN bundle install WORKDIR /opt/sinatra-sidekiq-example EXPOSE 5000
docker-compose
sidekiqは、jobのキューをためるのにredisを使っているので、applicationコンテナとは別にredisコンテナを立ち上げます。ただ、applicationコンテナとredisコンテナ、複数のコンテナを立ち上げようとすると、linkオプションを使わないといけなかったり、コンテナの起動順序を意識したりと、dockerコマンドだけで管理しようとすると極端に辛くなってきます。そこでdocker-composeを使います。docker-compose.ymlファイルにコンテナの定義を書くことで、コンテナを管理しやすくします。コンテナが多くなってくると、コマンドラインで短縮化されたオプションを書きまくるより、設定ファイルでより直感的に管理できる方がいいですからね。
docker-composeのヘルプはこんな感じ。
$ docker-compose -h Define and run multi-container applications with Docker. Usage: docker-compose [options] [COMMAND] [ARGS...] docker-compose -h|--help Options: -f, --file FILE Specify an alternate compose file (default: docker-compose.yml) -p, --project-name NAME Specify an alternate project name (default: directory name) --verbose Show more output -v, --version Print version and exit Commands: build Build or rebuild services help Get help on a command kill Kill containers logs View output from containers port Print the public port for a port binding ps List containers pull Pulls service images restart Restart services rm Remove stopped containers run Run a one-off command scale Set number of containers for a service start Start services stop Stop services up Create and start containers migrate-to-labels Recreate containers to add labels version Show the Docker-Compose version information
今回のdocker-compose.ymlファイルはこんな感じにしました。
web:
build: .
command: foreman start
ports:
- "5000:5000"
volumes:
- .:/opt/sinatra-sidekiq-example
links:
- redis
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
redis:
image: redis
buildでカレントディレクトリを指定してますが、そうすることでカレントディレクトリからDockerfileを探してきて勝手に読み込んでくれます。commandは、コンテナを起動するときに実行するコマンドです。portsはポートフォワーディングの設定です。volumesは、コンテナの内部とカレントディレクトリを共有するために設定します。linksは、コンテナ同士の関連付けです。environmentはコンテナ内部の環境変数設定です。redisコンテナは特にカスタマイズせずに公式のredisイメージを立ち上げるだけにしてます。
Dockerfileからコンテナをビルドします。
$ docker-compose build
コンテナを起動させます。
$ docker-compose up -d
コンテナの情報を確認します。
$ docker-compose ps
Name Command State Ports
--------------------------------------------------------------------------------------------
sinatrasidekiqexample_redis_1 /entrypoint.sh redis-server Up 6379/tcp
sinatrasidekiqexample_web_1 foreman start Up 0.0.0.0:5000->5000/tcp
コンテナの中に入ります。
## application用 (web) $ docker-compose run web /bin/bash ## redis用 $ docker-compose run redis /bin/bash
参考
sidekiq
開発環境が整ったら、ようやくsidekiqです。Gemfileにsidekiqを指定し、bundle installします。
gem 'sidekiq'
sidekiqを使った実装は、jobをキューに入れるところと、jobを実行するところの2つです。
redisの接続設定
sidekiqはバックエンドにredisを使うので、redisに接続するための設定を記述します。configure_clientとconfigure_server、両方必要です。
## jobをキューにためるのに必要
Sidekiq.configure_client do |config|
config.redis = { url: "redis://#{ENV['REDIS_HOST']}:#{ENV['REDIS_PORT']}" }
end
## jobをキューから取得するのに必要
Sidekiq.configure_server do |config|
config.redis = { url: "redis://#{ENV['REDIS_HOST']}:#{ENV['REDIS_PORT']}" }
end
workerの実装
jobを実行するworkerの実装は、以下のような感じです。Sidekiq::Workerモジュールをincludeしたclassを作り、performメソッド内にjobを記述します。ドキュメント読む限り、performメソッドの引数は、redisの容量を少なくするためになるべく少なくした方がいいようです。
require 'sidekiq'
class SomeWorker
include Sidekiq::Worker
def perform(id)
thing = $redis.hget('things', id)
sleep 10
p "The job is done: #{thing}"
end
end
キューにためたjobを実行するためには、sidekiqコマンドを使います。
$ bundle exec sidekiq -h
2016-01-02T15:21:45.078Z 7 TID-origl9uvo INFO: sidekiq [options]
-c, --concurrency INT processor threads to use
-d, --daemon Daemonize process
-e, --environment ENV Application environment
-g, --tag TAG Process tag for procline
-i, --index INT unique process index on this machine
-q, --queue QUEUE[,WEIGHT] Queues to process with optional weights
-r, --require [PATH|DIR] Location of Rails application with workers or file to require
-t, --timeout NUM Shutdown timeout
-v, --verbose Print more verbose output
-C, --config PATH path to YAML config file
-L, --logfile PATH path to writable logfile
-P, --pidfile PATH path to pidfile
-V, --version Print version and exit
-h, --help Show help
## 実行例
$ bundle exec sidekiq -r ./app.rb -C ./sidekiq.yml
オプションを色々と指定することもできますが、yml形式の設定ファイルを使うこともできます。sidekiq.ymlはこんな感じ。
:verbose: true
:pidfile: /var/run/sidekiq.pid
:logfile: /var/log/sidekiq.log
:concurrency: 10
:queues:
- default
sidekiqのプロセス数は、concurrencyで指定します。デフォルトは、25です。また、-dオプションでプロセスをデーモン化できますが、本番環境ではsystemdなどを使った方がいいとドキュメントに書いてあります。
jobをキューに登録
非同期で実行したいjobをキューに登録するのには、perform_asyncクラスメソッドを呼びます。
SomeWorker.perform_async(req['id'])
5分後に実行したいjobは以下のように、perform_inを使います。
SomeWorker.perform_in(5.minutes, req['id'])
dashboard

sidekiqは、jobのステータスをブラウザから確認するためのdashboardを提供してます。ドキュメントには、railsと一緒に使う方法しか書いてないんですが、sinatraと一緒に使う場合は、以下のようにすれば使えました。
require './app'
require 'sidekiq/web'
run Rack::URLMap.new('/' => SomeApp, '/sidekiq' => Sidekiq::Web)
参考
foreman
foremanは複数のプロセスを管理するツールです。今回sinatraとsidekiqのプロセス管理をforemanでやってみました。以下のようにProcfileにプロセスの定義を書きます。
web: bundle exec rackup -p $PORT --host 0.0.0.0 worker: bundle exec sidekiq -r ./app.rb -C sidekiq.yml
起動は以下のコマンド。
$ foreman start
参考
まとめ
sinatraとsidekiqを組み合わせることで、簡単に非同期型のjob実行Web APIが書けました。半分くらいDockerで遊んで得た知見ですが、これでひとまずやりたいことが実現できそうなことが分かってよかったです。

プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化
- 作者: WINGSプロジェクト阿佐志保,山田祥寛
- 出版社/メーカー: 翔泳社
- 発売日: 2015/11/20
- メディア: 大型本
- この商品を含むブログ (1件) を見る

Docker実践入門――Linuxコンテナ技術の基礎から応用まで
- 作者: 中井悦司
- 出版社/メーカー: 技術評論社
- 発売日: 2015/09/26
- メディア: Kindle版
- この商品を含むブログを見る

- 作者: 古賀政純
- 出版社/メーカー: インプレス
- 発売日: 2015/12/17
- メディア: Kindle版
- この商品を含むブログを見る