Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat: Add Startup Probe to Healthcheck Middleware #3069

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion docs/middleware/healthcheck.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ id: healthcheck

# Health Check

ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved
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

Expand All @@ -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`.
Expand Down Expand Up @@ -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())

Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions docs/whats_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -302,6 +303,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)
Expand Down Expand Up @@ -452,3 +472,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())
```
1 change: 1 addition & 0 deletions middleware/healthcheck/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Config struct {
const (
DefaultLivenessEndpoint = "/livez"
DefaultReadinessEndpoint = "/readyz"
DefaultStartupEndpoint = "/startupz"
)

func defaultProbe(fiber.Ctx) bool { return true }
Expand Down
31 changes: 25 additions & 6 deletions middleware/healthcheck/healthcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
gaby marked this conversation as resolved.
Show resolved Hide resolved

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) {
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -155,20 +170,23 @@ 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) {
app := fiber.New()

app.Get(DefaultLivenessEndpoint, NewHealthChecker())
app.Get(DefaultReadinessEndpoint, NewHealthChecker())
app.Get(DefaultStartupEndpoint, NewHealthChecker())

h := app.Handler()
fctx := &fasthttp.RequestCtx{}
Expand All @@ -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()

Expand Down
Loading