LoginSignup
15
12

More than 3 years have passed since last update.

Sidekiqをモニタリングする [翻訳]

Posted at

Sidekiqをモニタリングする

最終更新 2019/10/31 編集者 RKushnir

運用中のSidekiqプロセスを監視するツールを使用して、常に稼働しており、メモリやCPUを過度に使用していないことを確認することをお勧めします。
Inspeqtorを作ったのは、利用可能な既存のツール(たとえば、monit、god、bluepill)が気に入らなかったためです。
私のおすすめは、

  1. UpstartまたはSystemdを使用してSidekiqを起動/停止する。 こうすることで、Ruby VMがクラッシュした場合、プロセスはすぐに再起動します。

  2. 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

@jonhymanGoogle認証を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::WebRack::Protection を使用して、一般的なWeb攻撃(CSRFXSSなど)からアプリケーションを保護します。
Rack::Protection は、リクエストがセキュリティ要件を満たさないと判断した場合、セッションを無効にし、Forbidden エラーを発生させます。
考えられる状況の1つは、アプリケーションがリバースプロキシの背後で動作し、重要なヘッダーを渡さないことです(X-Forwarded-ForX-Forwarded-Proto)。
このような状況と解決策は、この記事イシュー #2560に記載されています。

Railsアプリにワイルドカードドメインがあり、それらのすべてからWeb UIにアクセスさせたい場合は、イシュー #2730 を参照してください。


web-ui.png

設定がうまくいっているなら、ブラウザには次のように表示されるはずです。

単体で動かしつつベーシック認証を使用する

# 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アプリケーションのモニタリングサービスです。
このサービスは下記の機能を提供します。

  1. 各Sidekiqワーカーの主要なメトリック(平均および95パーセンタイルの実行時間、遅延、エラー率など)。

  2. 個々のジョブのタイミングとメモリ割り当ての両方の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
...
15
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
12