DEV Community

Cover image for Graceful Shutdowns for containerized Spring Boot applications
Nikola Stanković
Nikola Stanković

Posted on

Graceful Shutdowns for containerized Spring Boot applications

Foreword

You might think, “Why does it matter how my app closes?” Well, let me tell you, it matters — a lot! Ever heard of graceful shutdowns? It’s a fancy term for letting your app finish its work, clean up, and say a proper goodbye before being shut down. Why is this important? Think of it this way: In a microservices setup (like Kubernetes), an abrupt shutdown can be like pulling the rug from under someone’s feet — unpleasant! You could have data inconsistencies, application errors, and unhappy users. 🔥

Let’s start and make our applications’ endings as good as their beginning! 🚀


Preconditions

Before we can jump in, there are some preconditions. And I assume that if you found this guide, you do

  • have a decent understanding of Docker.
  • have proper knowledge about Spring Boot development.

I’m no advocate nor paid to list these links here. I don’t even earn anything if you click on them. I googled them or had them in my bookmarks. So it is truly here to assist you, chosen by my personal bias as a developer. Feel free to get the required know-how anywhere else; the disclaimer ends.


Implementation

A bit of theory

Graceful shutdown, in particular, is an aspect that warrants more attention than it usually gets. In a world where uptime is king and availability is paramount, shutting down an application may seem like an alien concept. Yet, it is common in dynamic environments like Kubernetes or Docker Swarm, where applications (containers) are frequently started, stopped, or moved around due to scaling, deployments, or node maintenance.

A graceful shutdown implies that an application can clean up its resources, finish processing requests, and notify other components of its impending non-availability before being shut down. This is crucial in a microservices architecture, as an abrupt shutdown can lead to unfinished transactions, data inconsistency, application errors, and a poor user experience.

Spring Boot Configuration

The following snippet has to be added to enable in Spring Boot a graceful shutdown. I’d recommend adding it at a minimum to your production profile.

Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and reactive and Servlet-based web applications. When enabled using server.shutdown=graceful, upon shutdown, the web server will no longer permit new requests and wait for a grace period for active requests to complete. The grace period can be configured using spring.lifecycle.timeout-per-shutdown-phase.

⚠️ By default, the value of this property is equal to immediate, which means the server gets shut down immediately.

Graceful Shutdown in a Container

I utilise two key code snippets to ensure a graceful shutdown in all my projects. The sole prerequisite is to have dumb-init installed in your container. Once done, incorporate the dumb-init entrypoint into your Dockerfile. Then, kick off your Spring Boot application using the run_application.sh script, which facilitates a smooth, orderly shutdown.

(Dockerfile)

...

ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/etc/run_application.sh"]
Enter fullscreen mode Exit fullscreen mode

Detailed explanation

  1. term_handler() {...}: This function defines what to do when a SIGTERM signal is caught. It checks if the process with the process ID (pid) is running, and if it is, it sends a SIGTERM signal to it and waits for it to terminate before exiting with a specific exit code (143).
  2. trap 'kill ${!}; term_handler' SIGTERM: This line sets up a trap for the SIGTERM signal. When a SIGTERM signal is received, it runs the term_handler function.
  3. /opt/java/bin/java $JAVA_OPTS org.springframework.boot.loader.JarLauncher --bind 0.0.0.0:$PORT &: This line starts the Spring Boot application with the specified JAVA_OPTS and binds it to the specified PORT. The process runs in the background, and its process ID is stored in the variable pid.
  4. The last part is an infinite loop that continuously checks if the Java application is still running every 5 seconds. If the process is not running, it prints a message and calls the term_handler function to terminate the script properly. It's important because if the script were to end, the Docker container would stop.

Stopping a container - SIGTERM & SIGKILL

kill commands list

Docker

When you issue the docker stop command, Docker sends a SIGTERM signal to the main process (PID 1) running inside the container. This signal is a way to request the process to terminate politely. In Unix-based systems, SIGTERM is the default signal sent to a process to terminate it. It is a way of asking the process to shut itself down cleanly.

After sending SIGTERM, Docker waits for a grace period (default is 10 seconds) to allow the process to exit. If the process does not terminate within this grace period, Docker sends a SIGKILL signal to terminate the process forcibly.

A SIGKILL signal cannot be caught or ignored, so the process has no way to clean up before it is terminated. This is why it's crucial for applications to handle SIGTERM correctly, especially in a containerized environment, to ensure a graceful shutdown.

dumb-init

When using dumb-init in your Docker containers, how signals are handled changes, which can be particularly useful for managing graceful shutdowns.

dumb-init is a minimal init system that is typically used as the default entrypoint for Docker containers. Its primary role is to handle the reaping of zombie processes, but it also forwards signals to the child processes it manages.
In the context of the docker stop command, here's how things change:

  1. Docker still sends a SIGTERM signal to the main process in the container when you execute docker stop. But in this case, the main process is dumb-init.
  2. dumb-init then forwards this SIGTERM signal to its child processes.
  3. If the child processes do not exit within the grace period (default is 10 seconds), Docker sends a SIGKILL signal to dumb-init.
  4. dumb-init again forwards this SIGKILL signal to its child processes, forcibly terminating them if they have not exited.

The key benefit of using dumb-init is that it ensures signals like SIGTERM and SIGKILL are properly forwarded to all child processes, not just PID 1. This is particularly important in containers where the main process (PID 1) spawns child processes. Without dumb-init, these child processes might not receive the termination signals, leading to ungraceful shutdowns and potential resource leaks.

 Docker Stop Command

The one issue I suppose most of us are unaware of is that docker has a default wait time when executing a docker stop of 10 seconds. Therefore, it would be useless if you configure any value higher in your Spring Boot config, which is higher than 10 seconds. But to solve this issue, you can change the default timeout before Docker sends a SIGKILL signal by specifying a different timeout value in seconds when executing the docker stop command.

The syntax for the docker stop command with a custom timeout is:

docker stop -t <seconds> <container_id>
Enter fullscreen mode Exit fullscreen mode

This change is not permanent and applies only to the specific docker stop command where you specify the -t option.

Docker Compose

In Docker Compose, you can specify the amount of time to wait when stopping a container before Docker sends a SIGKILL signal using the stop_grace_period configuration option in your docker-compose.yml file.

Summary

In this article, I discussed the concept of graceful shutdowns for containerized Spring Boot applications, especially in environments like Kubernetes or Docker. I explained how Docker's 'stop' command and 'dumb-init' system helped manage this process, ensuring applications could neatly finish their tasks, clean up resources, and communicate their impending shutdown. This practice is key to maintaining data consistency and preventing errors within microservices architectures.


Closing words

I continuously update my articles and appreciate all feedback 📝. Tech writing is my passion and a source of income. If you find my content useful, reactions, follows, and even a small coffee donation ☕ are appreciated. This support helps me create more quality content. Follow me for more tech insights.

Support me here: https://ko-fi.com/niksta.
Your engagement inspires me to create more insightful content. Stay tuned! 🚀


 References, Useful links


Disclosure

For transparency, I've used ChatGPT, an AI language model by OpenAI, to assist in writing and refining parts of this article. While I have directed its responses and ensured the accuracy of the information, it deserves recognition for its role in content creation.

Top comments (0)