Skip to content

Commit

Permalink
✨ feat: Add Startup Probe to Healthcheck Middleware (#3069)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kirankumar-grootan authored Jul 18, 2024
1 parent 091a594 commit 4f1dc49
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 7 deletions.
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

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

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

0 comments on commit 4f1dc49

Please sign in to comment.