diff --git a/docs/middleware/healthcheck.md b/docs/middleware/healthcheck.md index d92e6cff..2837c550 100644 --- a/docs/middleware/healthcheck.md +++ b/docs/middleware/healthcheck.md @@ -4,7 +4,7 @@ id: healthcheck # Health Check -Liveness and readiness probes middleware for [Fiber](https://github.com/gofiber/fiber) that provides two endpoints for checking the liveness and readiness state of HTTP applications. +Liveness, readiness and startup probes middleware for [Fiber](https://github.com/gofiber/fiber) that provides three endpoints for checking the liveness, readiness, and startup state of HTTP applications. ## Overview @@ -16,6 +16,10 @@ Liveness and readiness probes middleware for [Fiber](https://github.com/gofiber/ - **Default Endpoint**: `/readyz` - **Behavior**: By default returns `true` immediately when the server is operational. +- **Startup Probe**: Checks if the application has completed its startup sequence and is ready to proceed with initialization and readiness checks. + - **Default Endpoint**: `/startupz` + - **Behavior**: By default returns `true` immediately when the server is operational. + - **HTTP Status Codes**: - `200 OK`: Returned when the checker function evaluates to `true`. - `503 Service Unavailable`: Returned when the checker function evaluates to `false`. @@ -44,6 +48,8 @@ After you initiate your [Fiber](https://github.com/gofiber/fiber) app, you can u app.Get(healthcheck.DefaultLivenessEndpoint, healthcheck.NewHealthChecker()) // Provide a minimal config for readiness check app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker()) +// Provide a minimal config for startup check +app.Get(healthcheck.DefaultStartupEndpoint, healthcheck.NewHealthChecker()) // Provide a minimal config for check with custom endpoint app.Get("/live", healthcheck.NewHealthChecker()) @@ -59,6 +65,12 @@ app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker(healt return true }, })) +// And it works the same for startup, just change the route +app.Get(healthcheck.DefaultStartupEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ + Probe: func(c fiber.Ctx) bool { + return true + }, +})) // With a custom route and custom probe app.Get("/live", healthcheck.NewHealthChecker(healthcheck.Config{ Probe: func(c fiber.Ctx) bool { @@ -90,6 +102,10 @@ type Config struct { // Function used for checking the liveness of the application. Returns true if the application // is running and false if it is not. The liveness probe is typically used to indicate if // the application is in a state where it can handle requests (e.g., the server is up and running). + // The readiness probe is typically used to indicate if the application is ready to start accepting traffic (e.g., all necessary components + // are initialized and dependent services are available) and the startup probe typically used to + // indicate if the application has completed its startup sequence and is ready to proceed with + // initialization and readiness checks // // Optional. Default: func(c fiber.Ctx) bool { return true } Probe HealthChecker diff --git a/docs/whats_new.md b/docs/whats_new.md index b2e318c4..a2d0603b 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -33,6 +33,7 @@ Here's a quick overview of the changes in Fiber `v3`: - [Session](#session) - [Filesystem](#filesystem) - [Monitor](#monitor) + - [Healthcheck](#healthcheck) - [📋 Migration guide](#-migration-guide) ## Drop for old Go versions @@ -330,6 +331,25 @@ DRAFT section Monitor middleware is now in Contrib package. +### Healthcheck + +The Healthcheck middleware has been enhanced to support more than two routes, with default endpoints for liveliness, readiness, and startup checks. Here's a detailed breakdown of the changes and how to use the new features. + +1. **Support for More Than Two Routes**: + - The updated middleware now supports multiple routes beyond the default liveliness and readiness endpoints. This allows for more granular health checks, such as startup probes. + +2. **Default Endpoints**: + - Three default endpoints are now available: + - **Liveness**: `/livez` + - **Readiness**: `/readyz` + - **Startup**: `/startupz` + - These endpoints can be customized or replaced with user-defined routes. + +3. **Simplified Configuration**: + - The configuration for each health check endpoint has been simplified. Each endpoint can be configured separately, allowing for more flexibility and readability. + +Refer to the [healthcheck middleware migration guide](./middleware/healthcheck.md) or the [general migration guide](#-migration-guide) to review the changes. + ## 📋 Migration guide - [🚀 App](#-app-1) @@ -480,3 +500,48 @@ app.Use(static.New("", static.Config{ MaxAge: 3600, })) ``` + +### Healthcheck + +Previously, the Healthcheck middleware was configured with a combined setup for liveliness and readiness probes: + +```go +//before +app.Use(healthcheck.New(healthcheck.Config{ + LivenessProbe: func(c *fiber.Ctx) bool { + return true + }, + LivenessEndpoint: "/live", + ReadinessProbe: func(c *fiber.Ctx) bool { + return serviceA.Ready() && serviceB.Ready() && ... + }, + ReadinessEndpoint: "/ready", +})) +``` + +With the new version, each health check endpoint is configured separately, allowing for more flexibility: + +```go +// after + +// Default liveness endpoint configuration +app.Get(healthcheck.DefaultLivenessEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ + Probe: func(c *fiber.Ctx) bool { + return true + }, +})) + +// Default readiness endpoint configuration +app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker()) + +// New default startup endpoint configuration +// Default endpoint is /startupz +app.Get(healthcheck.DefaultStartupEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ + Probe: func(c *fiber.Ctx) bool { + return serviceA.Ready() && serviceB.Ready() && ... + }, +})) + +// Custom liveness endpoint configuration +app.Get("/live", healthcheck.NewHealthChecker()) +``` diff --git a/middleware/healthcheck/config.go b/middleware/healthcheck/config.go index 8b7b26a2..eba6d537 100644 --- a/middleware/healthcheck/config.go +++ b/middleware/healthcheck/config.go @@ -24,6 +24,7 @@ type Config struct { const ( DefaultLivenessEndpoint = "/livez" DefaultReadinessEndpoint = "/readyz" + DefaultStartupEndpoint = "/startupz" ) func defaultProbe(fiber.Ctx) bool { return true } diff --git a/middleware/healthcheck/healthcheck_test.go b/middleware/healthcheck/healthcheck_test.go index e6c17487..07efa3de 100644 --- a/middleware/healthcheck/healthcheck_test.go +++ b/middleware/healthcheck/healthcheck_test.go @@ -34,30 +34,38 @@ func Test_HealthCheck_Strict_Routing_Default(t *testing.T) { StrictRouting: true, }) - app.Get("/livez", NewHealthChecker()) - app.Get("/readyz", NewHealthChecker()) + app.Get(DefaultLivenessEndpoint, NewHealthChecker()) + app.Get(DefaultReadinessEndpoint, NewHealthChecker()) + app.Get(DefaultStartupEndpoint, NewHealthChecker()) shouldGiveOK(t, app, "/readyz") shouldGiveOK(t, app, "/livez") + shouldGiveOK(t, app, "/startupz") shouldGiveNotFound(t, app, "/readyz/") shouldGiveNotFound(t, app, "/livez/") + shouldGiveNotFound(t, app, "/startupz/") shouldGiveNotFound(t, app, "/notDefined/readyz") shouldGiveNotFound(t, app, "/notDefined/livez") + shouldGiveNotFound(t, app, "/notDefined/startupz") } func Test_HealthCheck_Default(t *testing.T) { t.Parallel() app := fiber.New() - app.Get("/livez", NewHealthChecker()) - app.Get("/readyz", NewHealthChecker()) + app.Get(DefaultLivenessEndpoint, NewHealthChecker()) + app.Get(DefaultReadinessEndpoint, NewHealthChecker()) + app.Get(DefaultStartupEndpoint, NewHealthChecker()) shouldGiveOK(t, app, "/readyz") shouldGiveOK(t, app, "/livez") + shouldGiveOK(t, app, "/startupz") shouldGiveOK(t, app, "/readyz/") shouldGiveOK(t, app, "/livez/") + shouldGiveOK(t, app, "/startupz/") shouldGiveNotFound(t, app, "/notDefined/readyz") shouldGiveNotFound(t, app, "/notDefined/livez") + shouldGiveNotFound(t, app, "/notDefined/startupz") } func Test_HealthCheck_Custom(t *testing.T) { @@ -80,6 +88,11 @@ func Test_HealthCheck_Custom(t *testing.T) { } }, })) + app.Get(DefaultStartupEndpoint, NewHealthChecker(Config{ + Probe: func(_ fiber.Ctx) bool { + return false + }, + })) // Setup custom liveness and readiness probes to simulate application health status // Live should return 200 with GET request @@ -97,6 +110,8 @@ func Test_HealthCheck_Custom(t *testing.T) { // Ready should return 503 with GET request before the channel is closed shouldGiveStatus(t, app, "/ready", fiber.StatusServiceUnavailable) + shouldGiveStatus(t, app, "/startupz", fiber.StatusServiceUnavailable) + // Ready should return 200 with GET request after the channel is closed c1 <- struct{}{} shouldGiveOK(t, app, "/ready") @@ -155,13 +170,15 @@ func Test_HealthCheck_Next(t *testing.T) { }, }) - app.Get("/readyz", checker) - app.Get("/livez", checker) + app.Get(DefaultLivenessEndpoint, checker) + app.Get(DefaultReadinessEndpoint, checker) + app.Get(DefaultStartupEndpoint, checker) // This should give not found since there are no other handlers to execute // so it's like the route isn't defined at all shouldGiveNotFound(t, app, "/readyz") shouldGiveNotFound(t, app, "/livez") + shouldGiveNotFound(t, app, "/startupz") } func Benchmark_HealthCheck(b *testing.B) { @@ -169,6 +186,7 @@ func Benchmark_HealthCheck(b *testing.B) { app.Get(DefaultLivenessEndpoint, NewHealthChecker()) app.Get(DefaultReadinessEndpoint, NewHealthChecker()) + app.Get(DefaultStartupEndpoint, NewHealthChecker()) h := app.Handler() fctx := &fasthttp.RequestCtx{} @@ -190,6 +208,7 @@ func Benchmark_HealthCheck_Parallel(b *testing.B) { app.Get(DefaultLivenessEndpoint, NewHealthChecker()) app.Get(DefaultReadinessEndpoint, NewHealthChecker()) + app.Get(DefaultStartupEndpoint, NewHealthChecker()) h := app.Handler()