Extend Kubernetes service discovery with Stork and Quarkus

Quarkus enables developers to integrate client-based load balancing programming using Stork and Consul for reactive Java applications.
2 readers like this.
Ship captain sailing the Kubernetes seas

In traditional monolithic architecture, applications already knew where the backend services existed through static hostnames, IP addresses, and ports. The IT operation team maintained the static configurations for service reliability and system stability. This Day 2 operation has significantly changed since microservices began running in distributed networking systems. The change happened because microservices need to communicate with multiple backend services to improve the load balancing and service resiliency.

The microservices topology became much more complex as the service applications were containerized and placed on Kubernetes. Because the application containers can be terminated and recreated anytime by Kubernetes, the applications can't know the static information in advance. The microservices don't need to be configured with the static information of the backend applications because Kubernetes handles service discovery, load balancing, and self-healing dynamically and automatically.

However, Kubernetes doesn't support programmatic service discovery and client-based load balancing through integrated application configurations. Smallrye Stork is an open source project to solve this problem, providing the following benefits and features:

  • Augment service discovery capabilities
  • Support for Consul and Kubernetes
  • Custom client load-balancing features
  • Manageable and programmatic APIs

Nevertheless, Java developers need some time to adapt to the Stork project and integrate it with an existing Java framework. Luckily, Quarkus enables developers to plug Stork's features into Java applications. This article demonstrates how Quarkus allows developers to add Stork's features to Java applications.

Create a new Quarkus project using Quarkus CLI

Using the Quarkus command-line tool (CLI), create a new Maven project. The following command will scaffold a new reactive RESTful API application:

$ quarkus create app quarkus-stork-example -x rest-client-reactive,resteasy-reactive   

The output should look like this:

...
[SUCCESS] ✅  quarkus project has been successfully generated in:
--> /Users/danieloh/Downloads/demo/quarkus-stork-example
...

Open a pom.xml file and add the following Stork dependencies: stork-service-discovery-consul and smallrye-mutiny-vertx-consul-client. Find the solution to this example here.

<dependency>
  <groupId>io.smallrye.stork</groupId>
  <artifactId>stork-service-discovery-consul</artifactId>
</dependency>
<dependency>
  <groupId>io.smallrye.reactive</groupId>
  <artifactId>smallrye-mutiny-vertx-consul-client</artifactId>
</dependency>

Create new services for the discovery

Create two services (hero and villain) that the Stork load balancer will discover. Create a new services directory in src/main/java/org/acme. Then create a new HeroService.java file in src/main/java/org/acme/services.

Add the following code to the HeroService.java file that creates a new HTTP server based on the Vert.x reactive engine:

@ApplicationScoped
public class HeroService {

    @ConfigProperty(name = "hero-service-port", defaultValue = "9000") int port;

    public void init(@Observes StartupEvent ev, Vertx vertx) {
        vertx.createHttpServer()
                .requestHandler(req -> req.response().endAndForget("Super Hero!"))
                .listenAndAwait(port);
    }
    
}

Next, create another service by creating a VillainService.java file. The only difference is that you need to set a different name, port, and return message in the init() method as below:

@ConfigProperty(name = "villain-service-port", defaultValue = "9001") int port;

public void init(@Observes StartupEvent ev, Vertx vertx) {
        vertx.createHttpServer()
                .requestHandler(req -> req.response().endAndForget("Super Villain!"))
                .listenAndAwait(port);
}

Register the services to Consul

As I mentioned earlier, Stork allows you to use Consul based on Vert.x Consul Client for the service registration. Create a new ConsulRegistration.java file to register two services with the same name (my-rest-service) in src/main/java/org/acme/services. Finally, add the following ConfigProperty and init() method:

@ApplicationScoped
public class ConsulRegistration {

    @ConfigProperty(name = "consul.host") String host;
    @ConfigProperty(name = "consul.port") int port;

    @ConfigProperty(name = "hero-service-port", defaultValue = "9000") int hero;
    @ConfigProperty(name = "villain-service-port", defaultValue = "9001") int villain;

    public void init(@Observes StartupEvent ev, Vertx vertx) {
        ConsulClient client = ConsulClient.create(vertx, new ConsulClientOptions().setHost(host).setPort(port));

        client.registerServiceAndAwait(
                new ServiceOptions().setPort(hero).setAddress("localhost").setName("my-rest-service").setId("hero"));
        client.registerServiceAndAwait(
                new ServiceOptions().setPort(villain).setAddress("localhost").setName("my-rest-service").setId("villain"));

    }

}

Delegate the reactive REST client to Stork

The hero and villain services are normal reactive RESTful services that can be accessed directly by exposable APIs. You need to delegate those services to Stork for service discovery, selection, and calling.

Create a new interface MyRestClient.java file in the src/main/java directory. Then add the following code:

@RegisterRestClient(baseUri = "stork://my-rest-service")
public interface MyRestClient {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    String get();
}

The baseUri that starts with stork:// enables Stork to discover the services and select one based on load balancing type. Next, modify the existing resource file or create a new resource file (MyRestClientResource) to inject the RestClient (MyRestClient) along with the endpoint (/api) as seen below:

@Path("/api")
public class MyRestClientResource {
    
    @RestClient MyRestClient myRestClient;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String invoke() {
        return myRestClient.get();
    }

}

Before you run the application, configure Stork to use the Consul server in the application.properties as shown below:

consul.host=localhost
consul.port=8500

stork.my-rest-service.service-discovery=consul
stork.my-rest-service.service-discovery.consul-host=localhost
stork.my-rest-service.service-discovery.consul-port=8500
stork.my-rest-service.load-balancer=round-robin

Test your application

You have several ways to run a local Consul server. For this example, run the server using a container. This approach is probably simpler than installating or referring to an external server. Find more information here.

$ docker run --rm --name consul -p 8500:8500 -p 8501:8501 consul:1.7 agent -dev -ui -client=0.0.0.0 -bind=0.0.0.0 --https-port=8501

Run your Quarkus application using Dev mode:

$ cd quarkus-stork-example
$ quarkus dev

The output looks like this:

...
INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, jaxrs-client-reactive, rest-client-reactive, resteasy-reactive, smallrye-context-propagation, vertx]

--
Tests paused
Press [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

Access the RESTful API (/api) to retrieve available services based on the round-robin load balancing mechanism. Execute the following curl command-line in your local terminal:

& while true; do curl localhost:8080/api ; echo ''; sleep 1; done

The output should look like this:

Super Villain!
Super Hero!
Super Villain!
Super Hero!
Super Villain!
...

Wrap up

You learned how Quarkus enables developers to integrate client-based load balancing programming using Stork and Consul for reactive Java applications. Developers can also have better developer experiences using live coding while they keep developing the reactive programming in Quarkus. For more information about Quarkus, visit the Quarkus guides and practices.

danieloh
Technical Marketing, Developer Advocate, CNCF Ambassador, Public Speaker, Published Author, Quarkus, Red Hat Runtimes

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.