Spring Boot Microservice Communication on Kubernetes with Feign Clients
I was recently tasked with creating a series of Spring Boot microservices that would run on a Kubernetes cluster. For inter-service communication, I chose to use Spring feign clients. I had used them on a similar project in the past, and I really liked their simplicity and ease of configuration and use.
In this post, we’re going to create two simple RESTful web services in Spring Boot, use Spring’s @FeignClient annotation to provide a mechanism for one service to call the other, then deploy them to Kubernetes running in Docker Desktop. This article assumes that you have some understanding of creating a Spring Boot starter project. If you’re not sure how to do that, I recommend reading this first: https://spring.io/guides/gs/spring-boot/
Here’s what you’ll need to follow along:
- Java 11 or greater
- Docker Desktop (https://www.docker.com/products/docker-desktop)
- kubectl (https://kubernetes.io/docs/tasks/tools/install-kubectl/)
- Your favorite IDE (I’ll be using Spring Tool Suite: https://spring.io/tools)
Once you have these installed and set up, create two Spring Boot starter projects. You can do this in your IDE, or with https://start.spring.io/. Name one of your projects “hello”, and the other “goodbye”.
Goodbye Service
The first service we’ll build is the goodbye services. The Goodbye service is a simple one-endpoint microservice which returns the word “goodbye”. Here are the dependencies I have in my “goodbye” project:
Step 1:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent><properties>
<java.version>11</java.version>
</properties><dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Step 2:
Create the application.yaml
file in src/main/resources. The only thing you’ll need in this file is:
server:
port: 9800
You could also do this in application.properties
if you prefer that format.
Step 3:
Create the GoodbyeApplication.java
file in src/main/java under the primary namespace of the application (If you used your IDE or start.spring.io, you should already have this file):
package com.example.goodbye;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication()
public class GoodbyeApplication {
public static void main(String[] args) {
SpringApplication.run(GoodbyeApplication.class, args);
}
}
Step 4:
Create a simple controller class in the same package:
package com.example.goodbye;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController()
public class GoodbyeController {@GetMapping("/saygoodbye")
public String sayGoodbye() {
return "goodbye!";
}
}
Step 5:
That’s it for goodbye
. You should be able to navigate to the project directory and run the app:
> mvn clean spring-boot:run
Confirm your application works by opening a browser window and navigating to http://localhost:9800/saygoodbye
. You should get a simple response:
goodbye!
Hello Service
The hello
service is very similar to goodbye
, but we’ll need to add a few more dependencies and a little more code.
Step 6:
Add the same dependencies to your hello
project with the following additions:
<properties>
<java.version>11</java.version>
<spring.cloud>2020.0.1</spring.cloud>
<spring.cloud.kubernetes>1.1.7.RELEASE</spring.cloud.kubernetes>
</properties><dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement><dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-all</artifactId>
<version>${spring.cloud.kubernetes}</version>
</dependency>
</dependencies>
Step 7:
We’ll also need a little more information in our application.yaml
file:
server:
port: 9900
spring:
cloud:
kubernetes:
loadbalancer:
mode: service
Step 8:
We’re going to need a new class to facilitate communications with the goodbye
service. Here is where we’ll implement our feign client:
package com.example.hello;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;@FeignClient(name = "goodbye-service")
public interface GoodbyeClient {
@GetMapping("/saygoodbye")
String sayGoodbye();
}
In Spring, a feign client is just an interface that matches the API of the goodbye
service. It’s important to note that the value in the name
attribute of the @FeignClient
annotation must match the name of the service we’ll create shortly in Kubernetes.
Step 9:
Our HelloApplication.java
and HelloController.java
files are very similar to the ones we did above.
HelloApplication.java
Note the addition of two new annotations:
package com.example.hello;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class HelloApplication {
public static void main(final String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
HelloController.java
Here we add an @Autowired
reference to our GoodbyeClient
and a new endpoint, /saygoodbye
, that we’ll pass through to the goodbye
service using our FeignClient
.
package com.example.hello;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController()
public class HelloController { @Autowired
GoodbyeClient goodbye; @GetMapping("/sayhello")
public String sayHello() {
return "Hello!";
} @GetMapping("/saygoodbye")
public String sayGoodbye() {
return this.goodbye.sayGoodbye();
}
}
Step 10:
The last thing we’ll need to create is our Kubernetes deployment files for each project. In the root of each of your projects, create a folder named k8s
. Inside each folder, create a file named deployment.yaml
. This file will contain all of the information we’ll need to create our pods, services, and deployments on Kubernetes.
For the goodbye
service, put the following in your deployment.yaml
file:
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: goodbye-service
name: goodbye-service
spec:
replicas: 1
selector:
matchLabels:
app: goodbye-service
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: goodbye-service
spec:
containers:
- image: goodbye:0.0.1-SNAPSHOT
name: goodbye-service
resources: {}
status: {}
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: goodbye-service
name: goodbye-service
spec:
ports:
- name: 9800-9800
port: 9800
protocol: TCP
targetPort: 9800
selector:
app: goodbye-service
type: LoadBalancer
status:
loadBalancer: {}
This will create a deployment of the goodbye
service and expose it on port 9800 on your localhost.
Then do the same for the hello
service:
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: hello-service
name: hello-service
spec:
replicas: 1
selector:
matchLabels:
app: hello-service
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: hello-service
spec:
containers:
- image: hello:0.0.1-SNAPSHOT
name: hello-service
resources: {}
status: {}
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: hello-service
name: hello-service
spec:
ports:
- name: 9900-9900
port: 9900
protocol: TCP
targetPort: 9900
selector:
app: hello-service
type: LoadBalancer
status:
loadBalancer: {}
Putting It All Together
Now we’re ready to deploy our applications to kubernetes. In Docker Desktop, open the Dashboard, then click on the settings button at the top right (the gear icon). Select the Kubernetes
menu option on the left and make sure the Enable Kubernetes
option is selected. You may need to restart Docker Desktop to make it take effect.
Deploy The Services
Once you have kubernetes running, open a terminal window and navigate to your goodbye
project folder. Type the following command:
> kubectl apply -f k8s/deployment.yaml
If everything is set up correctly, you should get something similar to this output:
deployment.apps/goodbye-service created
service/goodbye-service created
You can confirm that your goodbye
service is running with:
> kubectl get services
And you should see (the IP address, second port, and time may differ):
goodbye-service LoadBalancer 10.1.2.3 localhost 9800:30870/TCP 1m
Finally, repeat the deployment steps above for the hello service:
> kubectl get servicesgoodbye-service LoadBalancer 10.1.2.3 localhost 9800:30870/TCP 1m
hello-service LoadBalancer 10.1.2.4 localhost 9900:30658/TCP 1m
Try It Out!
Open a browser window and navigate to the sayhello
endpoint in the hello
service:
http://localhost:9900/sayhello
You should get the response hello
in the browser window. Finally, we’ll confirm that our FeignClient
communication is working correctly:
http://localhost:9900/saygoodbye
Here we’re calling the hello
service, which is passing the call on to the goodbye
service. If all went correctly, you should see the response goodbye!
in the browser window.
Conclusion
Congratulations! You’ve just created two microservices that can talk to each other, using a minimal amount of code and configuration, and deployed them to Kubernetes.