Docker & Kubernetes Deployment
Health Probes, Resource Limits, and Rolling Updates in Kubernetes
Mapping Actuator health endpoints to liveness/readiness probes, and tuning CPU/memory boundaries.
Interview: JVM resource boundaries inside container orchestration. Expect questions on configuring MaxRAMPercentage, liveness vs readiness probes, graceful shutdown hooks, and rollout configurations.
Introduction
Deploying a Java application in Kubernetes without configuring resource boundaries and health monitoring can cause cluster instability. If a JVM process experiences a memory leak or deadlock, the API server needs to know, and the container runtime must restrict resource usage before it impacts other services.
Kubernetes manages workloads using **Probes** (Liveness, Readiness, Startup) to check container health, and **Resource Limits** to cap CPU and memory footprints.
Why It Matters
Probes prevent routing traffic to booting applications and restart deadlocked containers. Resource limits prevent Java heap sizing errors from triggering cluster-wide node terminations.
Actuator Kubernetes Integration
Spring Boot Actuator integrates with Kubernetes probes automatically, exposing dedicated health endpoints:
- /actuator/health/liveness: Indicates if the app internal state is running. If this fails, Kubernetes terminates and restarts the container.
- /actuator/health/readiness: Indicates if the app is ready to accept HTTP traffic (e.g. database connection is ready). If this fails, Kubernetes stops sending traffic to the pod.
Tuning the JVM Heap inside Containers
By default, the JVM sizes its heap based on host physical memory. Inside a container, if you do not set heap limits, Java might try to allocate memory exceeding the container limits, causing the host operating system to immediately terminate the process with an **OOMKilled (Exit Code 137)**.
To resolve this, configure the container with JVM arguments to size the heap relative to the container limit:
-XX:MaxRAMPercentage=75.0
This tells the JVM to cap its heap at 75% of the container memory limits, leaving 25% for metadata, garbage collection overhead, and thread stacks.
Practical Example
Let's write a Kubernetes Deployment manifest that configures Actuator health probes, memory limits, and JVM arguments:
Quick Quiz
Q1: What happens if a Spring Boot container has memory limits of 1Gi configured, but no MaxRAMPercentage JVM arguments specified?
A) The JVM automatically detects the container boundary and caps heap space to 750Mi.
B) The JVM might attempt to allocate heap size based on the host physical memory (e.g. 16Gi), causing the container runtime to kill the process with exit code 137.
C) The container runtime dynamically scales the container memory limit.
D) The pod starts, but disables garbage collection.
Answer: B — Modern JVMs (JDK 10+) are container-aware, but without explicit arguments, default heap calculations can still exceed container memory limits, leading to OOMKilled failures.
Scenario-Based Challenge
Production Scenario:
You deploy a Spring Boot application. During startup, the JVM takes 60 seconds to compile beans and run migrations. Because you only configured a liveness probe with an initial delay of 15 seconds, the liveness checks fail while the app is booting, causing Kubernetes to restart the pod in an infinite loop. How do you resolve this?
View Solution
To prevent startup restart loops:
1. **Deploy a Startup Probe:** Add a startupProbe configuration pointing to /actuator/health/liveness.
2. **Define a Failure Threshold:** Set the startup probe's failureThreshold to allow enough time for booting (e.g. periodSeconds: 5 and failureThreshold: 20 allows 100 seconds).
3. **Probe Evaluation Order:** Kubernetes executes the startup probe first. Liveness and readiness checks remain disabled until the startup probe successfully succeeds, protecting the container from early restarts.
Interview Questions
1. How do you configure Spring Boot to support Graceful Shutdown inside Kubernetes?
Set server.shutdown=graceful in Spring properties. This tells Tomcat/Undertow to stop accepting new requests when a SIGTERM is received, and wait for active requests to complete. Also, configure Kubernetes terminationGracePeriodSeconds in your deployment spec to allow enough time for graceful shutdown before the pod is force-killed.
Production Considerations
Configure rolling updates with maxUnavailable: 0 and maxSurge: 1 to guarantee that new pods are healthy and ready to receive traffic before old replicas are terminated, ensuring zero downtime.