lolly/internal/server/healthz.go
xfy f605ef3b44 feat(server): add /healthz and /readyz endpoints for Kubernetes probes
Add lightweight health check endpoints for container orchestration
(Kubernetes liveness/readiness probes, load balancer health checks).

New config under monitoring:
- healthz.enabled (default true), healthz.path (default /healthz)
- readyz.enabled (default true), readyz.path (default /readyz)

/healthz (liveness):
- Always returns 200 {"status":"ok"} if process is alive
- No dependency checks, minimal overhead

/readyz (readiness):
- Returns 200 {"status":"ready"} when server is running
- Returns 503 {"status":"not ready","reasons":[...]} when not ready
- Static-only servers (no proxies) always return 200

Registration:
- Registered alongside status/pprof endpoints
- Available in single mode (LocationEngine) and multi-server mode (Router)
- No IP allowlist required (K8s probes come from localhost)
- 6 unit tests covering all response scenarios
2026-06-11 23:41:45 +08:00

60 lines
1.5 KiB
Go

package server
import "github.com/valyala/fasthttp"
// HealthzHandler returns a liveness probe that always responds 200 {"status":"ok"}.
func HealthzHandler(ctx *fasthttp.RequestCtx) {
ctx.SetContentType("application/json")
ctx.SetStatusCode(200)
ctx.SetBodyString(`{"status":"ok"}`)
}
// NewReadyzHandler creates a readiness probe using the provided checker function.
func NewReadyzHandler(checker func() (bool, []string)) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
ctx.SetContentType("application/json")
ready, reasons := checker()
if ready {
ctx.SetStatusCode(200)
ctx.SetBodyString(`{"status":"ready"}`)
} else {
ctx.SetStatusCode(503)
ctx.SetBodyString(buildReasonsJSON(reasons))
}
}
}
func buildReasonsJSON(reasons []string) string {
if len(reasons) == 0 {
return `{"status":"not ready"}`
}
var buf []byte
buf = append(buf, `{"status":"not ready","reasons":[`...)
for i, r := range reasons {
if i > 0 {
buf = append(buf, ',')
}
buf = append(buf, '"')
buf = append(buf, r...)
buf = append(buf, '"')
}
buf = append(buf, "]}"...)
return string(buf)
}
// DefaultReadyzChecker returns a readiness checker for the Server.
func DefaultReadyzChecker(s *Server) func() (bool, []string) {
return func() (bool, []string) {
if !s.running.Load() {
return false, []string{"server not running"}
}
s.proxiesMu.RLock()
n := len(s.proxies)
s.proxiesMu.RUnlock()
if n == 0 {
return true, nil
}
return true, nil
}
}