spring-projects / spring-projects/spring-boot

Provide a built-in Actuator drain endpoint for Kubernetes preStop hook polling

Open
#51,310 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
Dominant language
Java
Stars
81.5k
Forks
42.7k
Avg merge
2d 4h
Merged PRs (30d)
65

Description

Summary

Spring Boot 2.3 introduced server.shutdown: graceful, liveness/readiness probe
endpoints, and SmartLifecycle-based shutdown ordering. These cover HTTP-level draining
well. However, there is no standard Actuator endpoint that allows a Kubernetes preStop
hook to poll and determine when all application-level in-flight work (async tasks,
Kafka consumer processing, custom thread pools, etc.) has completed before SIGTERM is sent
to the JVM.

Current Behaviour

Spring Boot provides:

  • /health/liveness — signals whether the application context is alive
  • /health/readiness — signals whether the pod should receive traffic; Spring
    automatically sets ReadinessState.REFUSING_TRAFFIC on shutdown, which causes this to
    return 503
  • server.shutdown: graceful — Tomcat waits for in-flight HTTP requests to complete
  • SmartLifecycle — allows beans to participate in ordered shutdown

None of these can be used as a simple HTTP 200/503 polling endpoint for a Kubernetes
preStop hook to determine when async, non-HTTP work is finished.

The Gap

The Kubernetes pod termination sequence is:

  • Pod removed from Service endpoints
  • preStop hook executes: Spring is fully alive here; no shutdown has started
  • SIGTERM sent to JVM process
  • Spring shutdown begins (SmartLifecycle, Tomcat drain, etc.)
  • SIGKILL after terminationGracePeriodSeconds

The preStop hook runs before SIGTERM reaches the JVM. Spring's own graceful shutdown
therefore cannot participate in this phase. Teams are left implementing their own drain
endpoint, typically:

  • A custom @RestController returning 200 OK when idle, 503 while busy
  • A custom SmartLifecycle bean that polls registered "drainable" components
  • Wiring of ThreadPoolTaskExecutor.getActiveCount() and similar metrics by hand

This is a common enough pattern that every team deploying Spring Boot on Kubernetes
implements it independently.

Proposed Solution

Introduce a Drainable SPI (or reuse SmartLifecycle) and a built-in Actuator
endpoint — e.g. /actuator/drain or a new drain health group — that:

  1. Aggregates the state of all registered Drainable beans (or SmartLifecycle beans
    that have not yet completed their stop())
  2. Returns 200 OK when all components report no in-flight work
  3. Returns 503 Service Unavailable with a JSON body listing the still-busy component
    names while work is in progress

The endpoint should be exposed under the management port and require no authentication by
default (consistent with /health/liveness and /health/readiness), so that a minimal
preStop script can use it:

lifecycle:
  preStop:
    exec:
      command:
        - /bin/sh
        - -c
        - until wget -qO- http://localhost:8080/actuator/drain; do sleep 2; done
/**
 * Register a bean of this type to participate in the built-in drain endpoint.
 * Spring Boot will expose aggregate drain state at /actuator/drain.
 */
public interface Drainable {
    /** Returns true when this component has no more in-flight work. */
    boolean isDrained();
    /** Human-readable name used in the /actuator/drain response body. */
    String name();
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading Spring Boot Actuator endpoint conventions, SmartLifecycle shutdown ordering, and the Kubernetes preStop sequence described in the issue. Define whether the implementation uses a new Drainable SPI or SmartLifecycle, then verify that the endpoint aggregates registered components, reports busy names with 503, and returns 200 only when all work is drained.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kubernetes, spring-boot
Domain
api, backend, devops
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.