Sidekiqをモニタリングする
最終更新 2019/10/31 編集者 RKushnir
運用中のSidekiqプロセスを監視するツールを使用して、常に稼働しており、メモリやCPUを過度に使用していないことを確認することをお勧めします。
Inspeqtorを作ったのは、利用可能な既存のツール(たとえば、monit、god、bluepill)が気に入らなかったためです。
私のおすすめは、
-
UpstartまたはSystemdを使用してSidekiqを起動/停止する。 こうすることで、Ruby VMがクラッシュした場合、プロセスはすぐに再起動します。
-
Inspeqtorを使用してCPUとメモリの使用量を監視し、必要に応じてSidekiqを再起動する。
Web UI
Sidekiqには、Sidekiqの現在の状態を表示できるWebアプリケーションが付属しています。
Rails
次のコードを config/routes.rb
に追加してください。
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'
Forbidden エラー
フォームを送信しようとしたときに Forbidden
エラーを受け取った場合、有効なセッションが構成されていないことが原因です。
CSRF攻撃を防ぐために、このような設定がなされています。
Railsと同じセッションを共有するようにWebアプリケーションを構成する必要があります。
require文の後にこのコードを routes.rb
に入れてみてください。
# Rails < 4:
Sidekiq::Web.set :session_secret, Rails.configuration.secret_token
# 5.2 > Rails >= 4:
Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]
# Rails >= 5.2:
Sidekiq::Web.set :session_secret, Rails.application.credentials[:secret_key_base]
上記のコードがうまくいかない場合は、rack-protection
のどの部分が問題を引き起こしているのかをデバッグするために下記のコードが役に立ちます。
セッションを設定するコードの直後に、次のコードを追加してください。
Sidekiq::Web.use(::Rack::Protection, { use: :authenticity_token, logger: Rails.logger, message: "Didn't work!" })
rack-protection
がアクションを妨げている場合、「Forbidden」の代わりに「Didn't Work」と表示されるはずです。
そうでなかった場合は、Sidekiq::Web.use
の呼び出しが起動プロセスのできるだけ早い段階でWebアプリケーションに追加されていることを確認してください。
デフォルトでは、ログはアプリサーバーのstderrに記録されます。ログをより細かく制御するために :logger
オプションを追加できます。
詳細については、rack-protectionを参照してください。
ヒント:以前は動作していたのに、アップグレード後に突然動作しなくなったという場合は、Cookieをクリア してみてください。
互換性のないCSRFトークンが、古いバージョンの rack-protection によって使用されている可能性があります。
セッションをクリアすると、新しい有効なトークンが生成されます。
ヒント:ロードバランサー -> リバースプロキシ -> Webアプリケーションサーバーという構成の場合は、X-Forwarded-Proto
ヘッダーが適切に設定されていることを確認してください。
ロードバランサーがSSLターミネーションポイントである場合、内部的な値ではなく、ロードバランサーから転送された値に設定されていることを確認する必要があります。(そうでない場合、rack-protection
は、httpsではなくhttp経由でのリクエストを使用します)
セッションが失われる問題
Web UIを使用しているときにセッションが上書きされている場合、初期化のタイミングでSidekiqのセッションを完全に無効にできます。
require 'sidekiq/web'
Sidekiq::Web.set :sessions, false
認証
本番アプリケーションでは、認証情報へのアクセスを保護する必要があります。これを実現するには、ルーティングの制約機能(config/routes.rbファイル内)を使用できます。
Devise
認証された User
を許可する。
# config/routes.rb
authenticate :user do
mount Sidekiq::Web => '/sidekiq'
end
上記に加えて、User#admin?
が true を返すことを確認する。
# config/routes.rb
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web => '/sidekiq'
end
Clearance
Clearanceは特定のルーティングへのアクセスを制限するためにルーティング制約機能を提供しています。
Blog::Application.routes.draw do
# すべての認証されたユーザーのアクセスを制限する
constraints Clearance::Constraints::SignedIn.new do
mount Sidekiq::Web, at: '/sidekiq'
end
# すべての認証されたアドミンのアクセスを制限する
constraints Clearance::Constraints::SignedIn.new { |user| user.admin? } do
mount Sidekiq::Web, at: '/sidekiq'
end
end
Authlogic
# lib/admin_constraint.rb
class AdminConstraint
def matches?(request)
return false unless request.cookie_jar['user_credentials'].present?
user = User.find_by_persistence_token(request.cookie_jar['user_credentials'].split(':')[0])
user && user.admin?
end
end
# config/routes.rb
require "admin_constraint"
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new
Authlogic 4+
Authlogic 4.0.0からセキュアCookieが有効になったため、AdminConstraintのCookieからのuser_credentialsの読み取りはデフォルトのセットアップでは失敗します。
代わりに、セッションから直接読み取ることができます。
# lib/admin_constraint.rb
class AdminConstraint
def matches?(request)
return false unless request.session.has_key?(:user_credentials)
user = User.find_by_persistence_token(request.session.fetch(:user_credentials))
user && user.admin?
end
end
Restful Authentication or Sorcery
User
モデルのインスタンスが admin?
に応答するか確認するコードは下記の通りです。
# lib/admin_constraint.rb
class AdminConstraint
def matches?(request)
return false unless request.session[:user_id]
user = User.find request.session[:user_id]
user && user.admin?
end
end
# config/routes.rb
require 'sidekiq/web'
require 'admin_constraint'
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new
独自にカスタマイズされた認証
class AuthConstraint
def self.admin?(request)
return false unless (cookie = request.cookie_jar['auth'])
Rails.cache.fetch(cookie['user'], :expires_in => 1.minute) do
auth_data = JSON.parse(Base64.decode64(cookie['data']))
response = HTTParty.post(Auth.validate_url, :query => auth_data)
response.code == 200 && JSON.parse(response.body)['roles'].to_a.include?('Admin')
end
end
end
# config/routes.rb
constraints lambda {|request| AuthConstraint.admin?(request) } do
mount Sidekiq::Web => '/admin/sidekiq'
end
Rails with Google authentication
@jonhyman がGoogle認証をSidekiqに適用する方法について書いてくれています。
warden/github
を使用したGithub認証
Rackベースの Sidekiq::Web UI
(since Sidekiq 4.2.0) の開発者による Gist を参照してください。
routes.rbにベーシック認証のコードを直接書く
# config/routes.rb
require "sidekiq/web"
Sidekiq::Web.use Rack::Auth::Basic do |username, password|
# Protect against timing attacks:
# - See https://codahale.com/a-lesson-in-timing-attacks/
# - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
# - Use & (do not use &&) so that it doesn't short circuit.
# - Use digests to stop length information leaking (see also ActiveSupport::SecurityUtils.variable_size_secure_compare)
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(username), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_USERNAME"])) &
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_PASSWORD"]))
end if Rails.env.production?
mount Sidekiq::Web, at: "/sidekiq"
ActionDispatch::Request::Session
のエラーが起きたなら、その原因はRailsとRackの互換性がないことです。
回避策としてはこのコメントを見てください。
Wardenを使っており failure_app
をグローバルにセットしている(Railsの application.rb
などで)なら、ブラウザのベーシック認証のプロンプトが表示されません。その理由は、Wardenがステータスコード 401に対して failed_app
を呼び出すためです。
この挙動をバイパスするためには、一例として下記のようなコードを使ってください。
class AllowBasicAuthPrompt
def initialize(app); @app = app; end
def call(env)
# Tell Warden to not intercept the 401
env['warden'].custom_failure!; @app.call(env)
end
end
Sidekiq::Web.use AllowBasicAuthPrompt
Sidekiq::Web.use Rack::Auth::Basic do |username, password|
...
end
単体で(Railsなしで)動作させる
選択したRackサーバーで Sidekiq::Web
を起動するための config.ru
の例を次に示します。
require 'sidekiq'
Sidekiq.configure_client do |config|
config.redis = { :size => 1 }
end
require 'sidekiq/web'
run Sidekiq::Web
Herokuでワンクリックデプロイをする方法もあります。
Sidekiqを既存のRackアプリケーションにもマウントできます。
require 'your_app'
require 'sidekiq/web'
run Rack::URLMap.new('/' => Sinatra::Application, '/sidekiq' => Sidekiq::Web)
RackセッションとWeb攻撃に対する保護
Sidekiq::Web
が機能するには、有効なRackセッションが必要であることに注意してください。Web UIでボタンをクリックしたときに Forbidden
エラーが表示される場合は、Rackセッションが正しく構成されていないことが原因です。
Sidekiqはセッションを設定できません。システムで有効なセッションを設定する方法がわからない場合、最善のオプションは、StackOverflowを検索するか、Web UIの実行に使用しているコードで質問を投稿することです。
Sidekiq::Web
は Rack::Protection
を使用して、一般的なWeb攻撃(CSRF、XSSなど)からアプリケーションを保護します。
Rack::Protection
は、リクエストがセキュリティ要件を満たさないと判断した場合、セッションを無効にし、Forbidden
エラーを発生させます。
考えられる状況の1つは、アプリケーションがリバースプロキシの背後で動作し、重要なヘッダーを渡さないことです(X-Forwarded-For
、X-Forwarded-Proto
)。
このような状況と解決策は、この記事とイシュー #2560に記載されています。
Railsアプリにワイルドカードドメインがあり、それらのすべてからWeb UIにアクセスさせたい場合は、イシュー #2730 を参照してください。
設定がうまくいっているなら、ブラウザには次のように表示されるはずです。
単体で動かしつつベーシック認証を使用する
# this code goes in your config.ru
require 'sidekiq'
Sidekiq.configure_client do |config|
config.redis = { :size => 1 }
end
require 'sidekiq/web'
map '/sidekiq' do
use Rack::Auth::Basic, "Protected Area" do |username, password|
# Protect against timing attacks:
# - See https://codahale.com/a-lesson-in-timing-attacks/
# - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
# - Use & (do not use &&) so that it doesn't short circuit.
# - Use digests to stop length information leaking
Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(username), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_USERNAME"])) &
Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_PASSWORD"]))
end
run Sidekiq::Web
end
Nagios
以下のURLは、特定のキューの深さが特定の範囲内にあることを検証する check_sidekiq_queue スクリプトを含むNagiosチェックのコレクションです。
redis-cliコマンドラインツールを使用する単純なシェルスクリプトであり、rubyに依存しません。
Scout
ScountはRailsアプリケーションのモニタリングサービスです。
このサービスは下記の機能を提供します。
-
各Sidekiqワーカーの主要なメトリック(平均および95パーセンタイルの実行時間、遅延、エラー率など)。
-
個々のジョブのタイミングとメモリ割り当ての両方のGitHub拡張トランザクショントレース。
Pingdom Server Monitoring
Pingdom Server Monitoringには、Sidekiqワーカーを監視するためのプラグインがあります。
このプラグインは、通常のジョブに加えて時間指定されたジョブと再試行について、エンキュー、失敗、および処理されたジョブ数を監視します。利用可能なメトリックのアラートを設定することもできます。
Queue Backlog をモニタリングする
Pingdomで単純なHTTPエンドポイントを使用して、Sidekiqのdefaultキューのバックログのサイズを確認できます。
下記のコードを config/routes.rb
に入れてください。
require 'sidekiq/api'
match "queue-status" => proc { [200, {"Content-Type" => "text/plain"}, [Sidekiq::Queue.new.size < 100 ? "OK" : "UHOH" ]] }, via: :get
こうすることで、http://example.com/queue-status にアクセスしたときのレスポンスbodyは「OK」または「UHOH」になります。
Pingdomチェックを1分ごとに実行し、response == 'UHOH'の場合にメールを送信するといったことができます。
Queue Latency をモニタリングする
カスタムエンドポイントを使用する
キューにたくさんのジョブを投入すると、キューのバックログを監視するときに誤検知が発生する可能性があります。(例:各ジョブが高速に処理されているなら問題ないのに、バックログの数が多いことを問題視してしまう)
代わりに、キューの待ち時間を監視してください。キューの待ち時間は、最も古いジョブがキューにプッシュされた時間と現在の時間との差です。下記のコードは、ジョブがキューに入れられてから30秒以上処理されていないかどうかをチェックします。config/routes.rb
に入れてください。
require 'sidekiq/api'
match "queue-latency" => proc { [200, {"Content-Type" => "text/plain"}, [Sidekiq::Queue.new.latency < 30 ? "OK" : "UHOH" ]] }, via: :get
こうすることで、http://example.com/queue-latency にアクセスしたときのレスポンスbodyは「OK」または「UHOH」のいずれかになります。
組み込みのダッシュボードAPIを使用する
Sidekiqは、/sidekiq/stats
にJSON形式のダッシュボードAPIを提供しています。このエンドポイントのレスポンスは下記の通りです。
{
"sidekiq": {
"processed": 12345,
"failed": 56,
"busy": 25,
"enqueued": 178,
"scheduled": 0,
"retries": 0,
"default_latency": 12
},
"redis": {
"connected_clients": "120",
"uptime_in_days": "35",
"used_memory_human": "602.31M",
"used_memory_peak_human": "1.01G"
}
}
sidekiqmon
Sidekiq 6.0には、コマンドラインに基本的な統計情報を出力する新しい sidekiqmon
バイナリが付属しています。
REDIS_URLを使用して、sidekiqmon
にRedisインスタンスの場所を指定してください。
> sidekiqmon # uses localhost:6379
...
> REDIS_URL=redis://redis.example.com:6380/5 sidekiqmon
...