Setup

Requirements

You need either kicks (>= 3.0) or sneakers (>= 2.11). Neither is declared as a dependency of this gem: at runtime it needs only the Sneakers namespace, and both provide it. Install exactly one — see LIMITATIONS.md for why having both is worse than it looks.

Those two floors are exact, not aspirational: CI runs the suite against kicks 3.0.0 and sneakers 2.11.0 pinned, alongside the matrix that tracks the current release of each. A ~> matrix on its own would only ever prove that the latest version works.

gem 'kicks_liveness'

If neither is present, install! raises a LoadError naming both with their required versions.

Installing the hooks

The gem works by prepending two modules — one to Sneakers::Worker, one to Sneakers::WorkerGroup. KicksLiveness.install! does that, and it must run before the runner starts. It is idempotent, so calling it twice is harmless.

Rails

Nothing to do. A Railtie calls install! from to_prepare.

That is the one path where this gem acts on its own rather than when told, so it is worth saying how far it has been checked: a production Rails application whose only kicks_liveness code is the configure block below — no install! anywhere — boots its workers with both hooks in place and the probe green. See VERIFYING.md.

The Railtie file is loaded only when Rails::Railtie is already defined at the time this gem is required, which is what Bundler.require in config/application.rb gives you. If your application requires this gem before Rails itself is loaded, the Railtie never loads and the hooks are never installed — call install! manually in that case.

Sinatra, Hanami, Roda, or no framework at all

Call install! yourself, before the runner starts:

require 'kicks_liveness'

KicksLiveness.install!

There is no Rails coupling to work around: outside Rails the Railtie file is never loaded, and the gem pulls in no Rails code. The web framework is not involved at all — workers run in their own process, started by rake sneakers:run or your own runner script, and that process usually contains no web framework.

Getting the application loaded under rake sneakers:run

The line above is the easy half. The half that actually costs people time is how the application gets into the rake process at all, because outside Rails nothing loads it for you.

sneakers/tasks declares task :environment empty, precisely so that you can fill it in. Rake accumulates blocks for the same task rather than replacing them, so adding your own is the supported way:

# Rakefile
require 'sneakers/tasks'

task(:environment) { require_relative 'app' }

where app.rb is whatever configures Sneakers, calls install! and defines (or requires) the worker classes. In Rails this is already handled — its own :environment task loads the application — which is why the problem only shows up outside it, usually as sneakers:run starting with no workers at all.

This is exactly the wiring the gem's own integration fixture uses (spec/integration/fixture/Rakefile), so it is checked against a real broker on every run of VERIFYING.md.

Hanami 2 differs only in what that block contains: load the app the way Hanami wants (require 'hanami/prepare' for a prepared, not fully booted, application) and call install! after it. There is nothing Hanami-specific in the gem — as above, the framework is not in the picture, only the question of which file brings your worker classes into the process.

A minimal standalone runner:

require 'sneakers'          # kicks installs this same file
require 'kicks_liveness'
require_relative 'workers/orders_worker'

Sneakers.configure(amqp: ENV.fetch('AMQP_URL'), workers: 2, threads: 10)
KicksLiveness.install!

Sneakers::Runner.new([OrdersWorker]).run

Workers must be started through Sneakers::Runner

This is the one real constraint, and it has nothing to do with frameworks.

The monitor thread is started from the hook on Sneakers::WorkerGroup#after_fork, and WorkerGroup enters the picture through Sneakers::Runner, which builds a ServerEngine supervisor. If you boot workers by hand — instantiating worker classes and calling worker.run in your own loop, without ServerEngine — that hook never fires. The registry will fill up, but nothing writes the mark files, so the probe stays red and the pod is killed.

Use Sneakers::Runner (that is what rake sneakers:run does), or do not use this gem.

Configuration

Everything has a sensible default; in most applications the block is one line.

KicksLiveness.configure do |config|
  config.enabled = ENV['RACK_ENV'] != 'test'
end

Do not set config.logger = Sneakers.logger. That is already the default, and writing it out is worse than leaving it alone, because the default is resolved lazily and the assignment is not. Sneakers.logger is nil until Sneakers.configure runs, so whether the assignment captures a logger or a nil depends on which file Rails loads first — and initializers load in alphabetical order, which is not something you chose or want to depend on. An initializer named kicks_liveness.rb sorts before sneakers.rb; one named worker_liveness.rb sorts after. Same code, different outcome.

Set it only to point somewhere other than Sneakers.logger:

config.logger = Rails.logger
Option Default Purpose Valid values
logger Sneakers.logger, resolved lazily transition and error logs logger-compatible object
enabled true start the monitor thread true or false
tick 10 seconds between checks positive number smaller than max_age
startup_grace_ticks 6 unhealthy startup ticks before one ERROR positive integer

Invalid combinations are rejected before the monitor starts instead of running one that is guaranteed to publish stale marks or never report a stalled startup.

dir and max_age are not here — they come from environment variables only. See DESIGN.md for why, and KUBERNETES.md for the variables.

Checking that the hooks are in place

In a console of the process that boots your workers:

Sneakers::Worker.ancestors.include?(KicksLiveness::Hooks::Worker)       # => true
Sneakers::WorkerGroup.ancestors.include?(KicksLiveness::Hooks::WorkerGroup) # => true

Both must be true before the runner starts. If either is false, either install! has not run, or it ran before kicks/sneakers was loaded.

Once workers are running, the marks directory is the other half of the answer:

$ ls -l /opt/app/tmp/health/
expected
generation
worker-0
worker-1

One worker-<slot> file per fork, plus expected and the current container generation. If expected is there and the slot files are not, the workers have not finished subscribing. A slot that keeps restarting without ever subscribing also leaves an attempt-<slot> file, which is removed as soon as that slot becomes healthy.