Cloud Native 16 min read

Spring Boot 3.x + Spring Cloud Kubernetes: Native Service Discovery, Config Injection & Graceful Shutdown

This production-hardened guide shows how to replace Eureka/Nacos with Kubernetes-native Service/EndpointSlice for service discovery, use ConfigMap/Secret for configuration with dynamic refresh, and implement zero-downtime deployments via preStop hooks and Spring Boot graceful shutdown — complete with RBAC hardening, fault-drill baselines, and a 7-point production checklist.

Xiaolin Talks Programming
Xiaolin Talks Programming
Xiaolin Talks Programming
Spring Boot 3.x + Spring Cloud Kubernetes: Native Service Discovery, Config Injection & Graceful Shutdown

1. Architecture Evolution: Why Hand Registry/Config to Kubernetes

Traditional microservice stacks relied on Eureka or Nacos for service governance, but after full containerization these external clusters introduce three pains:

Architectural redundancy — separate registry/config clusters consume resources and require dedicated ops.

Time-gap inconsistency — K8s schedules a Pod, assigns an IP, then the app registers; during this window the registry either lacks the instance or holds stale metadata, causing traffic to hit dead endpoints.

Extra network hop — every service call first queries the registry for metadata, lengthening the call chain and expanding the failure surface.

Kubernetes already solves this natively: Service + EndpointSlice + CoreDNS provide declarative service discovery, while kube-proxy (IPVS mode) maintains per-node L4 load-balancing rules so DNS returns actual Pod IPs. Spring Cloud Kubernetes preserves the Spring Cloud programming model while wiring registration and configuration directly to the K8s API Server via its watch mechanism, eliminating middleware and cutting ops overhead.

2. Core Component Integration: Dependencies, Configuration & Property Mapping

2.1 Dependencies (Maven)

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-kubernetes-client-discovery</artifactId>
</dependency>
<!-- Replace Ribbon, must be explicit -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

Use the official kubernetes-client starters; the legacy fabric8 client is unmaintained and breaks on Boot 3.x.

2.2 Core Configuration (application.yml)

spring:
  application:
    name: order-service
  cloud:
    kubernetes:
      config:
        enabled: true
        name: ${spring.application.name}
        namespace: ${POD_NAMESPACE:default}
      discovery:
        enabled: true
        all-namespaces: false
        primary-service-only: true
      lifecycle:
        timeout-per-shutdown-phase: 30s
  server:
    shutdown: graceful
POD_NAMESPACE

is injected via the Downward API — never hard-code it. primary-service-only: true filters out headless-service noise, keeping only standard Service endpoints.

2.3 ConfigMap Mapping & Strongly-Typed Binding

ConfigMap data is fed straight into Spring's Environment. The safest pattern is @ConfigurationProperties:

# K8s ConfigMap: order-service
data:
  app:
    rate-limit: 1000
    db-timeout: 5000ms
@ConfigurationProperties(prefix = "app")
@Component
public class AppProperties {
    private int rateLimit;
    private Duration dbTimeout;
    // getters/setters omitted
}

Service discovery requires zero registration code. KubernetesDiscoveryClient periodically fetches EndpointSlice IPs; simply annotate RestClient or WebClient with @LoadBalanced to enable client-side load balancing.

3. Dynamic Config Refresh: EVENT Listening & Hot-Reload Pitfalls

Spring Cloud Kubernetes supports two watch modes; EVENT (default) is the only production-grade choice. POLLING is a fallback for exotic network constraints.

The event-driven flow: client opens a Watch connection to the API Server; on ConfigMap ADDED/MODIFIED events, KubernetesPropertySourceLocator re-fetches data, merges into Environment, then ContextRefresher fires EnvironmentChangeEvent and RefreshScopeRefreshedEvent. Beans annotated with @RefreshScope are destroyed and recreated on next access.

Production pitfalls: @RefreshScope uses AOP proxies with local caches; bean rebuild discards caches, scheduled tasks, thread-pool state. If a config change hits during peak traffic, state loss triggers business errors. Fix: isolate volatile settings in a dedicated @ConfigurationProperties class, decoupled from core lifecycle beans.

DataSource pools, Redis clients, etc. carry high hot-reload risk. Either keep them out of ConfigMap or implement custom smooth-switch logic in @EventListener(RefreshScopeRefreshedEvent.class) — don't rely on framework auto-close.

Control-plane hiccups may fire duplicate events. Although the framework de-duplicates, add a simple debounce or version check on the business side to avoid CPU-spinning refresh storms.

4. Graceful Shutdown: PreStop, Connection Draining & Traffic Switch

K8s rolling updates send SIGTERM, but without proper tuning you'll see 502/503 spikes. server.shutdown: graceful alone is insufficient.

Real draining sequence:

K8s marks the Pod Terminating and removes it from EndpointSlice. kube-proxy syncs the change to node iptables/IPVS — 5–15 s async delay .

If the container starts stopping Tomcat/Undertow before the LB layer finishes draining, new requests are rejected.

Industry-standard fix: add a preStop sleep in the Deployment:

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 15"]
readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

The 15 s pause lets the LB layer purge the old IP. During this window the container stays alive, readiness returns 200, and traffic naturally shifts to new Pods. After the pause, Spring Boot's graceful shutdown begins: marks the web container PAUSE (rejects new connections), finishes in-flight requests, then forces exit after timeout-per-shutdown-phase (30 s). Do not tweak tomcat.connection-timeout — it conflicts with the graceful-shutdown logic; defaults are safe.

5. Security & Permissions: RBAC, ServiceAccount & Secret Handling

K8s enables RBAC by default. Pods needing ConfigMap reads or Service watches must bind a least-privilege ServiceAccount — never use the default sa.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: order-sa
  namespace: prod
automountServiceAccountToken: true
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: order-config-reader
  namespace: prod
rules:
- apiGroups: [""]
  resources: ["configmaps", "secrets", "endpoints"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: order-rb
  namespace: prod
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: order-config-reader
subjects:
- kind: ServiceAccount
  name: order-sa
  namespace: prod

Secret injection: never stuff secrets into environment variables — /proc/1/environ is world-readable inside the container. Mount via volume with 0400 permissions. For rotation, adopt Secrets Store CSI Driver backed by Vault or cloud KMS; the app rotates transparently without reload logic.

6. Fault Drills & Stress-Test Baselines

Configuration review isn't enough — validate with production-grade chaos:

Pod kill test: kubectl delete --force — verify preStop + graceful shutdown + Resilience4j Retry absorb requests.

Network partition / DNS slowdown: EndpointSlice sync stalls; mitigate with topologySpreadConstraints to spread Pods across AZs. Keep readiness timeouts tight; configure LoadBalancer retries so transient blips self-heal.

ConfigMap corruption: set spring.cloud.kubernetes.config.fail-fast=false so the app starts with local defaults and stays alive while alerts fire.

Hard metrics to hit:

Rolling-update 5xx rate < 0.05% (with maxSurge: 25%, maxUnavailable: 0).

EVENT-mode config refresh latency 1–2 s.

Connection-pool drain success > 99.8% at 500 QPS.

Monitor via Micrometer: Spring Cloud cache metrics, HTTP 5xx counters, graceful-shutdown duration.

7. Production Checklist & Experience Summary

Version alignment: Boot 3.2+ / Spring Cloud 2023.0.x / kubernetes-client 6.8+. No cross-major mixing; follow the compatibility matrix strictly.

Resource quotas: set adequate requests/limits. API Server QPS throttling + starved Pods = config-fetch timeout = startup failure.

JVM DNS cache: Java defaults to infinite TTL in some versions. Add -Dsun.net.inetaddr.ttl=30 or stale Pod IPs linger locally, defeating even perfect draining.

ConfigMap has no undo: K8s doesn't version ConfigMaps. Enforce GitOps (e.g., ArgoCD) or periodic etcd snapshots. Validate YAML syntax in a pre-prod env before applying.

Multi-environment isolation: no hard-coded env flags. Separate by Namespace, overlay with spring.profiles.active for clean, safe promotion.

Converging service discovery and config governance onto the K8s control plane saves massive middleware ops effort, but stability comes from mastering EndpointSlice sync mechanics, LB draining strategies, and Spring lifecycle details — not from "cloud-native" magic. This baseline has run in 10M+ QPS production clusters for over a year; follow it to dodge 90% of startup jitter and release interruption issues.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

KubernetesService DiscoveryConfiguration ManagementGraceful ShutdownRBACConfigMapSpring Cloud KubernetesSpring Boot 3.x
Xiaolin Talks Programming
Written by

Xiaolin Talks Programming

Focuses on sharing original technical insights. Senior architect at a top tech company with years of experience in technical architecture and management, and extensive interview experience. Offers one-on-one technical coaching, guiding you from beginner to architecture design to technical management. Follow for free learning resources. Free one-on-one interview coaching to help you land offers quickly.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.