Migrating Spring Cloud to Kubernetes: A Guide to Native Components
When moving a Spring Cloud application to Kubernetes, a common question arises: should I continue using Spring Cloud components, or should I use the native Kubernetes equivalents?
In this post, we’ll explore this question by comparing three key components:
- Config Server vs. ConfigMaps/Secrets
- Discovery Server (Eureka) vs. Services
- Spring Cloud Gateway vs. Ingress
Let’s see why using the built-in Kubernetes components is often the simpler, more powerful choice.
Configuration: Spring Cloud Config vs. Kubernetes ConfigMaps
In a traditional Spring Cloud architecture, the Spring Cloud Config Server provides a central place to manage properties for all microservices. Typically, these configurations are stored in a Git repository. This is a great way to centralize configuration.
However, Kubernetes has its own powerful, built-in solution: ConfigMaps and Secrets.
- ConfigMaps are used for storing non-sensitive configuration data (like API URLs, feature flags, etc.)
- Secrets are used for sensitive data (like passwords and API keys)
These objects are managed directly by the Kubernetes API and stored securely in its etcd database.
apiVersion: v1kind: ConfigMapmetadata: name: conference-servicedata: # These properties will be available as environment variables # or mounted as files inside the pod. KEYNOTE_API_URL: http://keynote-service:8080Hot Reloading with Configuration Watcher
“But what about hot reloading?” you might ask. This is one of the most powerful features of the Spring Cloud Config Server. When a change is made, the server can notify clients, which then call their /actuator/refresh endpoint to reload only the necessary configuration beans without restarting the pod.
We can achieve this exact same behavior in Kubernetes by using the official Spring Cloud Kubernetes Configuration Watcher.
Instead of a generic tool that just restarts the pod (like stakater/Reloader), the Configuration Watcher is designed specifically for Spring applications. Here’s how it works:
- You deploy the Configuration Watcher into your cluster
- It monitors any ConfigMap or Secret that your application is using
- When it detects a change, it doesn’t just kill the pod. Instead, it makes a POST request to that pod’s
/actuator/refreshendpoint
This gives you the best of both worlds:
- You use simple, native Kubernetes ConfigMaps to store your properties
- You get true, application-aware hot reloading at runtime, just as you would with the Spring Cloud Config Server
Service Discovery: Netflix Eureka vs. Kubernetes Services
In a microservice architecture, services need to find and talk to each other. Netflix Eureka solves this by acting as a registry. When a microservice starts, it “registers” itself with Eureka (e.g., keynote-service is at 192.168.1.10:8080). When another service wants to talk to it, it first asks Eureka for the address.
In Kubernetes, this entire discovery process is handled automatically by Services and CoreDNS.
When you create a Deployment for your microservice, you also create a Service object to expose it. This Service gets a stable, internal IP address and a DNS name (like conference-service) that never changes, even when the pods behind it are created or destroyed.
apiVersion: v1kind: Servicemetadata: name: conference-servicespec: # This selector finds all pods with the label "app: conference-service" selector: app: conference-service ports: - protocol: TCP port: 8081 # The port the Service is available on targetPort: 8081 # The port the container is listening onNow, when your keynote-service wants to talk to conference-service, it doesn’t need Eureka. It simply makes a request to the hostname conference-service (or http://conference-service:8081).
Here’s what happens automatically:
- Kubernetes’s internal DNS (CoreDNS) resolves the name
conference-serviceto the Service’s stable IP address - The Service acts as a load balancer. It knows the actual IPs of all healthy
conference-servicepods - It forwards the request to one of the healthy pods
The creation and deletion of pods is completely abstracted. Your application only ever needs to know the static Service name.
API Gateway: Spring Cloud Gateway vs. Kubernetes Ingress
Finally, we need a single entry point for our application to route external traffic (from end-users) to our various microservices.
The Spring Cloud Gateway is an application-aware facade. It’s smart: it can integrate with the discovery server (Eureka) to find microservices, and you can write complex routing rules and filters in Java.
The Kubernetes equivalent is an Ingress. An Ingress is a set of rules that tells an Ingress Controller (like NGINX, Traefik, or HAProxy) how to route traffic. The Ingress Controller is the public entry point to your cluster.
The Ingress object is a simple, declarative way to define routing:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: conference-microservice-ingressspec: # This assumes you have the NGINX Ingress Controller installed ingressClassName: nginx rules: # All traffic for "super.app" comes here - host: super.app http: paths: # If the path is /api/conferences... - path: /api/conferences pathType: Prefix backend: service: # ...send it to the "conference-service" Service name: conference-service port: number: 8081 # If the path is /api/keynotes... - path: /api/keynotes pathType: Prefix backend: service: # ...send it to the "keynote-service" Service name: keynote-service port: number: 8080When a user browses to super.app/api/conferences, the Ingress Controller sees this rule and automatically forwards the request to the conference-service Service, which in turn load-balances the request to a healthy pod.
Conclusion
While Spring Cloud provides a fantastic suite of tools, Kubernetes provides native, declarative, and powerful alternatives for configuration, service discovery, and routing. By replacing these Spring Cloud components, you can:
- Simplify your application by removing additional dependencies
- Reduce overhead by leveraging platform capabilities
- Fully embrace the power of the Kubernetes platform
The migration doesn’t have to be all-or-nothing. You can gradually replace these components one by one, starting with the one that provides the most immediate benefit for you