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