-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Monitoring
I recommend using a tool to monitor your Sidekiq processes in production to ensure they are always up and aren't using too much memory or CPU. I built Inspeqtor because I didn't like the existing tools that were available (e.g. monit, God and bluepill). My recommendations:
- Use Upstart or Systemd to start/stop Sidekiq. This ensures if the Ruby VM crashes, the process will respawn immediately.
- Use Inspeqtor to monitor the CPU and memory usage and restart Sidekiq if necessary.
Sidekiq comes with a web application that can display the current state of a Sidekiq installation.
Add the following to your config/routes.rb:
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'If you receive a Forbidden error when trying to submit a form, you do not have a valid session configured. A valid session is required to prevent CSRF attacks. You must configure the webapp to share the same session with Rails. Try putting this in your routes.rb after the require:
# 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]If the above does not work the following can help you debug which portion of rack-protection is causing the problems. Right after where you set the session, add the following:
Sidekiq::Web.use(::Rack::Protection, { use: :authenticity_token, logger: Rails.logger, message: "Didn't work!" })You should now see "Didn't Work" instead of "Forbidden" when rack-protection prevents an action. If not, make sure the Sidekiq::Web.use call is added as early in the boot process as possible for your app. The log will go to your app server's stderr, by default. You can add the :logger option for more control over that. See https://github.com/sinatra/rack-protection/blob/pre-merge/lib/rack/protection/base.rb for further information.
Tip: If it used to work and it suddenly stopped to work after an upgrade, try clearing your cookies. The incompatible CSRF token might actually just come from an older version of rack-protection. By clearing the session, it will generate a new valid token.
Tip: If you have a load balancer -> reverse proxy -> web application server setup, make sure you're setting the X-Forwarded-Proto header properly. If your load balancer is your SSL termination point you need to make sure it's set to the value forwarded from the load balancer, not the internal value (otherwise rack-protection will see the request as coming via http, not https). If you need to disable the http_origin check, either to diagnose or because you have no control over your upstream proxy configuration, you can adjust rack-protection like so:
require 'sidekiq'
require 'sidekiq/web'
Sidekiq::Web.class_eval do
use Rack::Protection, :except => :http_origin
endIf, when using the Web UI, your sessions are being overwritten, you can disable Sidekiq's sessions completely with an initializer:
require 'sidekiq/web'
Sidekiq::Web.set :sessions, falseIn a production application you'll likely want to protect access to this information. You can use the constraints feature of routing (in the config/routes.rb file) to accomplish this:
Allow any authenticated User
# config/routes.rb
authenticate :user do
mount Sidekiq::Web => '/sidekiq'
endSame as above but also ensures that User#admin? returns true
# config/routes.rb
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web => '/sidekiq'
endClearance provides routing constraints to restrict access to routes.
Blog::Application.routes.draw do
# Restricts access to all authenticated users
constraints Clearance::Constraints::SignedIn.new do
mount Sidekiq::Web, at: '/sidekiq'
end
# Restricts access to all authenticated admins
constraints Clearance::Constraints::SignedIn.new { |user| user.admin? } do
mount Sidekiq::Web, at: '/sidekiq'
end
end# 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.newSince Authlogic 4.0.0 turned on secure cookies then AdminConstraint reading 'user_credentials' from cookies will fail in default developer setup. Instead you can read it from session directly.
# 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
endChecks a User model instance that responds to 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.newclass 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@jonhyman breaks down how Appboy uses Google to protect access to Sidekiq.
See Gist from the creator of the rack-based Sidekiq::Web UI (since Sidekiq 4.2.0)
# 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"If you get an ActionDispatch::Request::Session error, you've hit an incompatibility between Rails and Rack. See this comment for a workaround.
If you use Warden and set failure_app globally (such as in application.rb of a Rails project), you won't see the browser HTTP Basic prompt as Warden will intercept the 401 and invoke your failed_app proc. To bypass this, you can define something like this:
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|
...
endHere's an example config.ru for booting Sidekiq::Web in your choice of Rack server:
require 'sidekiq'
Sidekiq.configure_client do |config|
config.redis = { :size => 1 }
end
require 'sidekiq/web'
run Sidekiq::WebOne click standalone app deploy on Heroku:
You can mount sidekiq to existing Rack application as well:
require 'your_app'
require 'sidekiq/web'
run Rack::URLMap.new('/' => Sinatra::Application, '/sidekiq' => Sidekiq::Web)Note that Sidekiq::Web requires a valid Rack session to work. If you see a Forbidden error when clicking a button in the Web UI, it's because the Rack session is not configured correctly. Sidekiq cannot configure a session for you. If you do not know how to set up a valid session in your system, your best option is to search StackOverflow or post a question there with the code you are using to run the Web UI.
Sidekiq::Web uses Rack::Protection to protect your application against typical web attacks (such as CSRF, XSS, etc). Rack::Protection would invalidate your session and raise Forbidden error if it finds that your request doesn't satisfy security requirements. One of the possible situations is having your application working behind a reverse proxy and not passing important headers to it (X-Forwarded-For, X-Forwarded-Proto). Such situation and solution could be found in this article and issue #2560.
If you have wildcard domains with your Rails app and want to access the Web UI from all of them, see issue #2730.
If you do everything right, you should see this in your browser:

# 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
endBelow is a collection of nagios checks that includes check_sidekiq_queue script, which validates that a given queue depth is within a particular range. It's a simple shell script that uses redis-cli command line tool, and does not have any dependency on ruby.
https://github.com/wanelo/nagios-checks
Scout, a Rails app monitoring service, provides:
- Key metrics for each Sidekiq worker (mean and 95th percentile execution time, latency, error rate, etc).
- GitHub-enhanced transaction traces of both timing and memory allocations for individual jobs.
Pingdom Server Monitoring has a plugin for monitoring Sidekiq workers.
This plugin monitors enqueued, failed, and processed job counts, as well as scheduled jobs and retries. You can also set up alerts for the available metrics.
You can use a simple HTTP endpoint with Pingdom to check the size of your Sidekiq 'default' queue backlog. Put this in config/routes.rb:
require 'sidekiq/api'
match "queue-status" => proc { [200, {"Content-Type" => "text/plain"}, [Sidekiq::Queue.new.size < 100 ? "OK" : "UHOH" ]] }, via: :getNow when you hit http://example.com/queue-status, the body of the response will be either 'OK' or 'UHOH'. We have a Pingdom check every minute which fires off an email if the response == 'UHOH'.
If you throw a lot of jobs into the queue, you can get false positives when monitoring the queue backlog. Instead, monitor the queue latency. Queue latency is the difference between when the oldest job was pushed onto the queue versus the current time. This code will check that jobs don't spend more than 30 seconds enqueued. Put this in config/routes.rb:
require 'sidekiq/api'
match "queue-latency" => proc { [200, {"Content-Type" => "text/plain"}, [Sidekiq::Queue.new.latency < 30 ? "OK" : "UHOH" ]] }, via: :getNow when you hit http://example.com/queue-latency, the body of the response will be either 'OK' or 'UHOH'.
Sidekiq provides a JSON formatted dashboard at /dashboard/stats. You get this :
{
"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"
}
}Sidekiq 6.0 ships with a new sidekiqmon binary which will print out basic stats to your terminal. Use REDIS_URL to point sidekiqmon to your Redis instance.
> sidekiqmon # uses localhost:6379
...
> REDIS_URL=redis://redis.example.com:6380/5 sidekiqmon
...
Previous: Deployment Next: API
