Deploying Spring Cloud Netflix apps on Kubernetes
If you are deploying containers on production, Kubernetes is a no-brainer solution. It takes some time to get familiar with all concepts but once you understand it, piece of cake 🍰.
So today I wanna show you how to deploy an Eureka server, a Hystrix dashboard with Turbine and a microservice.
Lets get down to business
Creating a Kubernetes cluster
That is the easiest part and I won't spend much time on it: go to Google Cloud Console and spin a new cluster:
In the next screen, fill with values as you want, but give it at least 6gb of memory to your cluster (I'm on trial, so I can't actually have more than 10gb)
You should see the cluster after a couple minutes
Click on connect
and follow the instructions to get to the Kubernetes UI.
Kubernetes UI
The UI is very intuitive and you should spend some time getting familiar with it:
The most important part is Workloads
, where our Pods will be found and Services
that will expose our Pods to the world. If you are not familiar with Kubernetes terms, check the Concetps page.
Deploying Eureka to our cluster
In the first post of this blog, I showed how to create an Eureka server, and we will be using this server with some modifications in order to make it run on Kubernetes.
You can find the code for the apps on GitHub
Configuring Eureka
To make Eureka work as intend, I configured it as follow:
spring:
profiles: docker
eureka:
numberRegistrySyncRetries: 1
instance:
preferIpAddress: true
hostname: eureka-server
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false
This is really simple and there are only one thing here worth mention: preferIpAddress
should be set to true
so Turbine later can find our Hystrix streams and aggregate them. In my experience, when instances register themselves with Eureka and preferIpAdress
is set to false
, Turbine will try to resolve the service name, which is usually something like service-name:port-random_name
Deploying Eureka
First thing we should do is "Dockerize" Eureka. To do so, we created a Dockerfile as follow:
FROM java:8-alpine
VOLUME /eureka-server
ADD target/eureka-server-0.0.1-SNAPSHOT.jar app.jar
RUN sh -c 'touch app.jar'
EXPOSE 8761
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Dspring.profiles.active=docker", "-jar","/app.jar"]
Note that we are exposing the port 8761.
Now, build the app, the docker image and push to your registry:
cd eureka-server; mvn clean package -DskipTests=true; cd -
docker build eureka-server -t luizkowalski/eureka-server;
docker push luizkowalski/eureka-server;
Now your image should be on Docker Hub now and you can deploy it to your Kubernetes cluster:
kubectl run eureka-server --replicas=1 --labels="run=eureka-server" --image=luizkowalski/eureka-server --port=8761
this command is doing:
- creating a Kubernetes deployment named
eureka-server
- initially, with one replica (Pod)
- labeling it
- using the image from Docker Hub
- exposing the port 8761
At this point, Eureka should be running but not accessible to the world yet.
To make it accessible, you should create a Service:
kubectl expose deployment eureka-server --type=LoadBalancer --name eureka-server
Here we created a Service called eureka-server
and expose the Deployment with the same name.
Now, we have an external endpoint that if accessed, should show us Eureka dashboard
Deploying Hystrix and Turbine
Hystrix and Turbine are a bit tricky but still, easy.
To enable Turbine (Hystrix streams aggregator), I created a Spring Boot app with the follow annotation:
@SpringBootApplication
@EnableHystrixDashboard
@EnableTurbine
@EnableEurekaClient
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
Basically, this app is the aggregator and the Hystrix dashboad as well.
The configuration of the app is as follow:
server.port: 8090
spring:
application:
name: hystrix-dashboard
cloud:
config:
failFast: true
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka/
instance:
preferIpAddress: false
turbine:
aggregator:
clusterConfig: CONTACTS-SERVICE
appConfig: contacts-service
---
spring:
profiles: docker
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URL}
instance:
metadataMap:
instanceId: ${spring.application.name}:${random.int}
preferIpAddress: true
We are running on port 8090, and aggregating the Hystrix streams from our soon to be deployed app contacts-service
.
Note that EUREKA_URL
will be passed later on as environment variable.
The Dockerfile is very similar to the Eurka's Dockerfile:
FROM java:8-alpine
VOLUME /hystrix-dashboard
ADD target/hystrix-0.0.1-SNAPSHOT.jar app.jar
RUN sh -c 'touch app.jar'
EXPOSE 8090
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Dspring.profiles.active=docker","-jar","/app.jar"]
Now, lets build and push it to Docker Hub
cd hystrix; mvn clean package -DskipTests=true; cd -
docker build hystrix -t luizkowalski/hystrix;
docker push luizkowalski/hystrix;
and deploy to Kubernetes:
kubectl run hystrix --replicas=1 --labels="run=hystrix" --image=luizkowalski/hystrix --port=8090 --env="EUREKA_URL=http://eureka:admin@eureka-server:8761/eureka" --port 8090
kubectl expose deployment hystrix --type=LoadBalancer --name hystrix-dashboard
This is also very similar to the command to deploy Eureka, but it has one things worth mention:
--env="EUREKA_URL=http://eureka:admin@eureka-server:8761/eureka
: as we exposed theeureka-server
deployment, we can now access this server using the hostnameeureka-server
. Kube-DNS will resolve it to the right host.
Now, if you access Eureka dashboard again, you should see a service called HYSTRIX-DASHBOARD
and the Service on Kubernetes:
Deploying our service
Now we can have a microservice that registers itself on Eureka and publishes a /hystrix.stream
endpoint.
Creating the service
Let's work on a service capable of register and return contacts (name and phone number)
I'm not gonna go into the details of this service, you can see it on GitHub. Let's dive into what it is important for this tutorial:
@RequestMapping(method = RequestMethod.GET, path = "/contacts")
public ResponseEntity<Iterable<Contact>> getUsers() throws Exception {
return new ResponseEntity<>(component.getContacts(), HttpStatus.OK);
}
Instead let the controller go straight to repository, we are proxying the call through a component:
@Component
public class ContactsComponent {
@Autowired
ContactsRepository repository;
@HystrixCommand(fallbackMethod = "getContactsFallback")
public Iterable<Contact> getContacts() throws IOException {
return repository.findAll();
}
public Iterable<Contact> getContactsFallback() {
return new ArrayList<>();
}
}
Notice @HystrixCommand
there. Spring will wrap call to the getContacts()
with a Circuit breaker and if it fails, fallback to a empty list (getContactsFallback()
). The fallback method have to have the same signature otherwise won't work.
Deploy
The Dockerfile and the commands to build the image are similar to the ones above so you can adapt to your project needs.
Now let's deploy to Kubernetes:
kubectl run contacts --replicas=2 --labels="run=contacts" --image=luizkowalski/contacts --port=8080 --env="EUREKA_URL=http://eureka:admin@eureka-server:8761/eureka" --env "DATABASE_URL=jdbc:postgresql://database:5432/contacts?user=postgres&password=contacts" --port 8080
kubectl expose deployment contacts --type=LoadBalancer --name contacts-service
Here you should see that we are passing the same Eureka URL as before and also setting a database URL for the service. At this point, you should have spawned a database or use in-memory for the sake of testing. We are also deploying two replicas (two Pods) and our service will load balance the calls automatically.
After run the commands you should now see two contacts-service
instances on Eureka:
and the Service published
Seeing in action
To see it in action, first register a couple contacts with POST /contacts/new
endpoint on contacts service. After that, if you git GET /contacts
you should see the contacts
Hystrix and turbine
Now we are able to see Hystrix aggregating the streams and showing us the status of the app. Access Hystrix dashboard via Service endpoint:
To see the circuit breaker performing, you should pass the Turbine stream URL to the dashboard (which is the Hystrix service itself) with the following address: http://{your-hystrix-external-endpoint}:8090/turbine.stream?cluster=CONTACTS-SERVICE
Turbine will now go through all services registered on Eureka, ask for their homePageUrl, go to the page and ask for a Hystrix stream and aggregate them if exists.
To test this, I'll do a bunch of calls using siege to GET /contacts
and watch the status on Hystrix
Turbine is aggregating two hosts and the circuit is closed which means it is fine. The whole cluster (the two instances, in our case) are handling 21 req/s with around 10.7 req/s on each host.
Success! We now have a resilient infrastructure thanks to Kubernetes and Spring Boot.
Conclusion
Kubernetes is an awesome tool with a very rich ecosystem. Though is a bit complex, once you understand the whole concept you will hardly go for something else in your next project.
Spring Boot is also a no-brainer choice when it comes to microservices these days, with a very strong community and a really low learning curve.
Always important to say that in a real world scenario, you should probably care more about security, expose only a edge server like Zuul instead the services itself, share environment variables using Secrets and deploy containers through a CI/CD platform, avoiding issuing these commands by hand