Deploying Spring Cloud Netflix apps on Kubernetes

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:

FB_IMG_14951178050176258

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 the eureka-server deployment, we can now access this server using the hostname eureka-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

Icons created by Freepik - Flaticon