class HealthController < ApplicationController
skip_before_action :verify_authenticity_token
def liveness
render json: { status: 'ok' }, status: :ok
end
def readiness
checks = {
database: check_database,
redis: check_redis,
sidekiq: check_sidekiq
}
all_healthy = checks.values.all? { |status| status == 'ok' }
status_code = all_healthy ? :ok : :service_unavailable
render json: { status: all_healthy ? 'ok' : 'degraded', checks: checks }, status: status_code
end
private
def check_database
ActiveRecord::Base.connection.execute('SELECT 1')
'ok'
rescue StandardError
'failed'
end
def check_redis
Redis.current.ping == 'PONG' ? 'ok' : 'failed'
rescue StandardError
'failed'
end
def check_sidekiq
Sidekiq.redis(&:ping) == 'PONG' ? 'ok' : 'failed'
rescue StandardError
'failed'
end
end
Rails.application.routes.draw do
get 'health/liveness', to: 'health#liveness'
get 'health/readiness', to: 'health#readiness'
end
Load balancers and orchestration platforms like Kubernetes rely on health check endpoints to determine if an application instance is ready to serve traffic. A robust health check doesn't just return 200 OK—it verifies critical dependencies like database connectivity, Redis availability, and background job processing. I implement separate /health/liveness and /health/readiness endpoints: liveness checks if the app process is running, while readiness checks if all dependencies are available. This distinction allows orchestrators to restart unhealthy instances while giving newly deployed instances time to warm up before receiving traffic. Health checks should complete quickly (under 100ms) to avoid triggering false positives during traffic spikes.