feat: Add Startup Probe to Healthcheck Middleware (#3069)

* added startup default probe endpoint

* added test case

* updated docs

* updated test order

* added test case

* fixed go fmt and md lint

* fixed go fmt and md lint

* updated doc as per coderabbitai suggestions

* changed healhtcheck route register to use default const instead of string for test cases

* updated whats new with healthcheck content

* updated whats new doc with coderabbitai sugg

* updated migration guide
pull/3075/head
kirankumar-grootan 2024-07-18 17:24:44 +05:30 committed by GitHub
parent 091a59472c
commit 4f1dc49894
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 108 additions and 7 deletions

View File

@ -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

View File

@ -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())
```

View File

@ -24,6 +24,7 @@ type Config struct {
const (
DefaultLivenessEndpoint = "/livez"
DefaultReadinessEndpoint = "/readyz"
DefaultStartupEndpoint = "/startupz"
)
func defaultProbe(fiber.Ctx) bool { return true }

View File

@ -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()