This is a simple project which demonstrates the pitfalls with the graceful shutdown configuration in a Spring Boot microservice deployed as Docker container.
- Amazon Corretto Alpine-based Linux with JDK-21
- Spring Boot 3.x
- Apache Maven 3.x
- OpenJDK 21.x
- Using a shell as pre-start hook to start the spring-boot app. The exec portion of the bash command is important because it replaces the bash process with your server, so that the shell only exists momentarily at start!
As of Spring Boot 2.3, Spring Boot now supports the graceful shutdown feature for all four embedded web servers (Tomcat, Jetty, Undertow, and Netty) on both servlet and reactive platforms.
To enable the graceful shutdown, all we have to do is to set the server.shutdown property to graceful in our application.properties file:
server.shutdown=graceful
Then, Tomcat, Netty, and Jetty will stop accepting new requests at the network layer. Undertow, however, will continue to accept new requests but send an immediate 503 Service Unavailable response to the clients.
By default, the value of this property is equal to immediate: the server gets shut down immediately.
Some requests might get accepted just before the graceful shutdown phase begins. In that case, the server will wait for all active requests to finish their work up to a specified amount of time. We can configure this grace period using the spring.lifecycle.timeout-per-shutdown-phase configuration property (The default value is 30 seconds.):
spring.lifecycle.timeout-per-shutdown-phase=21s
- Spring-Boot-Service: the Endpoint /entity/all simulates a slow response (5s)
- Demo impl with using a static Linux lib 'dumb-init' as a Docker Entrypoint: for more information go to dumb-init for docker or check dumb-init on GitHub
- Use "docker stop < container-id >" to send SIGTERM signal to the container
- Look at the logs and watch for "[extShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete"
- Check the exited status of the containers ('Exited (143)' means 'gracefully terminated' while 'Exited (137)' means 'killed')
- For more information check sigterm and exit codes
Build the App with Maven:
mvn clean verify
Start the App with Maven from the Terminal:
mvn spring-boot:run
Open your Browser and navigate to:
http://localhost:8080/entity/all
Run the Docker build command and tag the images
docker build -t k8s-spring-boot-app-graceful:1.0.0 . -f Dockerfile
docker build -t k8s-spring-boot-app-non-graceful:1.0.0 . -f Dockerfile-non-graceful
docker build -t k8s-spring-boot-app-graceful-dumb-init:1.0.0 . -f Dockerfile-graceful-dumb-init
docker image ls
docker run -d -p 8080:8080 k8s-spring-boot-app-graceful:1.0.0
To start the container securely pass these params --read-only --tmpfs /tmp
Optional (if in Dockerfile not set) pass the current user: "--user
docker run -d -p 8080:8080 --read-only --tmpfs /tmp k8s-spring-boot-app-graceful:1.0.0
Go to your Browser and open
http://localhost:8080/entity/all
Check the status and logs of the running container
docker ps -a
docker logs <container-id>
Stop the container
docker stop <container-id>
Check the status of the stopped container
docker ps -a
Add '--platform linux/amd64' flag to the 'build' command:
docker build --platform linux/amd64 -t k8s-spring-boot-app-graceful:1.0.0 . -f Dockerfile
docker build --platform linux/amd64 -t k8s-spring-boot-app-non-graceful:1.0.0 . -f Dockerfile-non-graceful
docker build --platform linux/amd64 -t k8s-spring-boot-app-graceful-dumb-init:1.0.0 . -f Dockerfile-graceful-dumb-init
docker image ls
Add '--platform linux/amd64' flag to the run command:
docker run --platform linux/amd64 -d -p 8080:8080 k8s-spring-boot-app-graceful:1.0.0
To start the container securely pass these params --read-only --tmpfs /tmp
Optional (if in Dockerfile not set) pass the current user: "--user
docker run --platform linux/amd64 -d -p 8080:8080 --read-only --tmpfs /tmp k8s-spring-boot-app-graceful:1.0.0
When finished with the demo:
docker system prune
docker image prune -a
Reference: Version and Package Management
Version model <MAJOR.MINOR.PATCH>, where:
- Major as an official release,
- Minor the bugfix fix and
- Patch any implementation or new feature that should be tested.