Rx
Rx is a Rack middleware that provides tiered health checks to any Rack application, including Rails-based apps.
Tiered Health Checks
What is a tiered health check? I'm glad you asked! Health checks serve different purposes:
- Some are used by load balancers to ensure that a server is capable of serving traffic
- Some are used by other applications to verify the health of your service as a dependency
- Some are used by customers or status pages to determine uptime
These use cases are all similar, but may require different levels of verification. One may require your service to just return 200, while another may need to check connectivity to the database, cache, or external services.
Rx provides three levels of health checks:
-
/liveness
: A health check that determines if the server is running. -
/readiness
: Readiness checks determine if critical, dependent services are running (think a database or cache) -
/deep
: A health check that walks your entire dependency tree, checking other critical and secondary services.
Rails Applications
Add rx-healthcheck
to your Gemfile, and then create a new initializer with something like this:
Rails.application.config.middleware.insert(
Rx::Middleware,
liveness: [Rx::Check::FileSystemCheck.new],
readiness: [
Rx::Check::FileSystemCheck.new,
Rx::Check::ActiveRecordCheck.new,
Rx::Check::HttpCheck.new("http://example.com"),
Rx::Check::GenericCheck.new(-> { $redis.ping == "PONG" }, "redis")],
deep_critical: [Rx::Check::HttpCheck.new("http://criticalservice.com/health")],
deep_secondary: [Rx::Check::HttpCheck.new("http://otherservice.com/health-check")]
)
Configuring Dependencies
Now that you're running rx
, you will need to configure which dependencies it tests in each health check. You can do this by passing Rx::Check
objects to the middleware. rx
ships with a number of standard checks:
- Filesystem health
- ActiveRecord
- HTTP
- Generic Check
In addition to the stock checks, you may create your own by copying an existing check and modifying it (though it's probably simpler to just use GenericCheck).
RX::Middleware
accepts the following named parameters as configuration:
liveness: [],
readiness: [],
deep_critical: [],
deep_secondary: []
Each collection must contain 0 or more Rx::Check
objects. Those checks will be performed when the health check is queried. Deep checks will always also run the readiness checks.
Cache
For deep health checks Rx by default will use an in-memory cache. While this is useful when running in production, during testing it could lead to unexpected results. You can disable the in-memory cache by setting the cache key false in the options hash.
Rails.application.config.middleware.insert(
Rx::Middleware,
liveness: [Rx::Check::FileSystemCheck.new],
readiness: [
Rx::Check::FileSystemCheck.new,
Rx::Check::ActiveRecordCheck.new,
Rx::Check::HttpCheck.new("http://example.com"),
Rx::Check::GenericCheck.new(-> { $redis.ping == "PONG" }, "redis")],
deep_critical: [Rx::Check::HttpCheck.new("http://criticalservice.com/health")],
deep_secondary: [Rx::Check::HttpCheck.new("http://otherservice.com/health-check")],
options: {
cache: false
}
)
Deep end-point authorization
It is considered as a good practice to protect the deep checks with a GUID to mitigate DDOS attacks. Hence you have 2 options to enable this. One is to use the default_authorization
by passing the token to the authorization inside options hash, which you can use in a request with the authorization_token in the header of it. The other option would be to pass a lambda with an env argument (this gives you access to hash of request data) and have your own custom_authorization
:
options: {
#default
authorization: <token>
#custom
authorization: -> (env) {
#your code goes here
}
}
Customizing health-check endpoints
By default, Rx will mount the health checks at /liveness
, /readiness
, and /deep
. You can customize these endpoints by passing a liveness_path
, readiness_path
, and deep_path
options to the middleware:
# in this example, liveness and readiness endpoints have been customized. the deep
# endpoint will use the default path (/deep).
Rx::Middleware.new(options: {
liveness_path: "/other_liveness",
readiness_path: "/other_readiness"
})
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/zachpendleton/rx.
Some tips for developing the gem locally:
- Tests can be run by calling
rake
- You can point your Rails app to a local gem by adding a
path
option to your Gemfile, a la `gem "rx", path: "path/to/rx" (though you will need to restart Rails whenever you change the gem).
License
The gem is available as open source under the terms of the MIT License.