Micronaut Discovery Client

Adds Service Discovery Features for Eureka and Consul

Version:

1 Introduction

Using the CLI

If you are creating your project using the Micronaut CLI, supply either of discovery-consul or discovery-eureka features to enable service-discovery in your project:

$ mn create-app my-app --features discovery-consul

Service Discovery enables the ability for Microservices to find each other without necessarily knowing the physical location or IP address of associated services.

There are many ways Service Discovery can be implemented, including:

  • Manually implement Service Discovery using DNS without requiring a third party tool or component.

  • Use a discovery server such as Eureka, Consul or ZooKeeper.

  • Delegate the work to a container runtime, such as Kubernetes.

With that in mind, Micronaut tries to flexible to support all of these approaches. As of this writing, Micronaut features integrated support for the popular Service Discovery servers:

  • Eureka

  • Consul

To include Service Discovery in your application simply the first step is to add the discovery-client dependency to your application:

implementation("io.micronaut:micronaut-discovery-client")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-discovery-client</artifactId>
</dependency>

The discovery-client dependency provides implementations of the DiscoveryClient interface.

The DiscoveryClient is fairly simple and provides two main entry points:

Both methods return Publisher instances since the operation to retrieve service ID information may result in a blocking network call depending on the underlying implementation.

If you are using Micronaut’s cache module, the default implementation of the DiscoveryClient interface is CachingCompositeDiscoveryClient which merges all other DiscoveryClient beans into a single bean and provides caching of the results of the methods. The default behaviour is to cache for 30 seconds. This cache can be disabled in application configuration:

Disabling the Discovery Client Cache
micronaut:
  caches:
    discovery-client:
      enabled: false

Alternatively you can alter the cache’s expiration policy:

Configuring the Discovery Client Cache
micronaut:
  caches:
    discovery-client:
      expire-after-access: 60s

See the DiscoveryClientCacheConfiguration class for available configuration options.

2 Release History

For this project, you can find a list of releases (with release notes) here:

3 Consul Support

Consul is a popular Service Discovery and Distributed Configuration server provided by HashiCorp. Micronaut features a native non-blocking ConsulClient that is built using Micronaut’s support for Declarative HTTP Clients.

Starting Consul

The quickest way to start using Consul is via Docker:

  1. Starting Consul with Docker.

docker run -p 8500:8500 consul

Auto Registering with Consul

To register a Micronaut application with Consul simply add the necessary ConsulConfiguration. A minimal example can be seen below:

Auto Registering with Consul (application.yml)
micronaut:
    application:
        name: hello-world
consul:
  client:
    registration:
      enabled: true
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
Using the Micronaut CLI you can quickly create a new service setup with Consul using: mn create-app my-app --features discovery-consul

The consul.client.defaultZone settings accepts a list of Consul servers to be used by default.

You could also simply set consul.client.host and consul.client.port, however ConsulConfiguration allows you specify per zone discovery services for the purpose load balancing. A zone maps onto a AWS availability zone or a Google Cloud zone.

By default registering with Consul is disabled hence you should set consul.client.registration.enabled to true. Note that you may wish to do this only in your production configuration.

Running multiple instances of a service may require an additional configuration param. See below.

If you are running the same applications on the same port across different servers it is important to set the micronaut.application.instance.id property or you will experience instance registration collision.

micronaut:
  application:
    name: hello-world
    instance:
      id: ${random.shortuuid}

Customizing Consul Service Registration

The ConsulConfiguration class features a range of customization options for altering how an instance registers with Consul. You can customize the tags, the retry attempts, the fail fast behaviour and so on.

Notice too that ConsulConfiguration extends DiscoveryClientConfiguration which in turn extends HttpClientConfiguration allowing you to customize the settings for the Consul client, including read timeout, proxy configuration and so on.

For example:

Customizing Consul Registration Configuration
micronaut:
    application:
        name: hello-world
consul:
  client:
    registration:
      enabled: true
      # Alters the tags
      tags:
        - hello
        - world
      # Alters the metadata
      meta:
        some: value
        instance_type: t2.medium
      # Alters the retry count
      retry-count: 5
      # Alters fail fast behaviour
      fail-fast: false
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

Discovery Services from Consul

To discovery other services you could manually interact with the DiscoveryClient, however typically instead you use the Client Annotation to declare how an HTTP client maps to a service.

For example the configuration in the previous section declared a value for micronaut.application.name of hello-world. This is the value that will be used as the service ID when registering with Consul.

Other services can discovery instances of the hello-world service simply by declaring a client as follows:

Using @Client to Discover Services
@Client(id = "hello-world")
interface HelloClient{
    ...
}

Alternatively you can also use @Client as a qualifier to @Inject an instance of HttpClient:

Using @Client to Discover Services
@Client(id = "hello-world")
@Inject
RxHttpClient httpClient;

Consul Health Checks

By default when registering with Consul Micronaut will register a TTL check. A TTL check basically means that if the application does not send a heartbeat back to Consul after a period of time the service is put in a failing state.

Micronaut applications feature a HeartbeatConfiguration which starts a thread using HeartbeatTask that fires HeartbeatEvent instances.

The ConsulAutoRegistration class listens for these events and sends a callback to the /agent/check/pass/:check_id endpoint provided by Consul, effectively keeping the service alive.

With this arrangement the responsibility is on the Micronaut application to send TTL callbacks to Consul on a regular basis.

If you prefer you can push the responsibility for health checks to Consul itself by registering an HTTP check:

Consul HTTP Check Configuration
consul:
  client:
    registration:
       check:
         http: true

With this configuration option in place Consul will assume responsibility of invoking the Micronaut applications Health Endpoint.

Controlling IP/Host Registration

Occasionally, depending on the deployment environment you may wish to expose the IP address and not the host name, since by default Micronaut will register with Consul with either the value of the HOST environment variable or the value configured via micronaut.server.host.

You can use the consul.client.registration.prefer-ip-address setting to indicate you would prefer to register with the IP address.

Micronaut will by default perform an IP lookup to try and figure out the IP address, however you can use the consul.client.registration.ip-addr setting to specify the IP address of the service directly.

Consul HTTP Check Configuration
consul:
  client:
    registration:
      ip-addr: <your base container ip>
      prefer-ip-address: true

This will tell Consul to register the IP that other instances can use to access your service and not the NAT IP it is running under (or 127.0.0.1).

If you use HTTP health checks (see the previous section) then Consul will use the configured IP address to check the Micronaut /health endpoint.

Consul HTTP Check Configuration
consul:
  client:
    registration:
      ip-addr: <your base container ip>
      prefer-ip-address: true
      check:
        http: true

4 Eureka Support

Netflix Eureka is a popular discovery server deployed at scale at organizations like Netflix.

Micronaut features a native non-blocking EurekaClient as part of the discovery-client module that does not require any additional third-party dependencies and is built using Micronaut’s support for Declarative HTTP Clients.

Starting Eureka

The quickest way to start a Eureka server is to use to use Spring Boot’s Eureka starters.

As of this writing the official Docker images for Eureka are significantly out-of-date so it is recommended to create a Eureka server following the steps above.

Auto Registering with Eureka

The process to register a Micronaut application with Eureka is very similar to with Consul, as seen in the previous section, simply add the necessary EurekaConfiguration. A minimal example can be seen below:

Auto Registering with Eureka (application.yml)
micronaut:
  application:
    name: hello-world
eureka:
  client:
    registration:
      enabled: true
    defaultZone: "${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}"

Customizing Eureka Service Registration

You can customize various aspects of registration with Eureka using the EurekaConfiguration. Notice that EurekaConfiguration extends DiscoveryClientConfiguration which in turn extends HttpClientConfiguration allowing you to customize the settings for the Eureka client, including read timeout, proxy configuration and so on.

Example Eureka Configuration
eureka:
  client:
    readTimeout: 5s
    registration:
      asgName: myAsg # the auto scaling group name
      countryId: 10 # the country id
      vipAddress: 'myapp' # The Eureka VIP address
      leaseInfo:
        durationInSecs: 60 # The lease information
      metadata: # arbitrary instance metadata
        foo: bar
      retry-count: 10 # How many times to retry
      retry-delay: 5s # How long to wait between retries
      appname: some-app-name     # optional, eureka instance application name, defaults to ${micronaut.application.name}
      hostname: foo.example.com  # optional, exposed eureka instance hostname, useful in docker bridged network environments
      ip-addr: 1.2.3.4           # optional, exposed eureka instance ip address, useful in docker bridged network environments
      port: 9090                 # optional, exposed eureka instance port, useful in docker bridged network environments
🔗
Table 1. Configuration Properties for EurekaConfiguration$EurekaRegistrationConfiguration
Property Type Description

eureka.client.registration.secure-port

int

eureka.client.registration.port

int

eureka.client.registration.instance-id

java.lang.String

eureka.client.registration.asg-name

java.lang.String

eureka.client.registration.home-page-url

java.lang.String

eureka.client.registration.lease-info

LeaseInfo

eureka.client.registration.country-id

int

eureka.client.registration.status-page-url

java.lang.String

eureka.client.registration.health-check-url

java.lang.String

eureka.client.registration.secure-health-check-url

java.lang.String

eureka.client.registration.data-center-info

DataCenterInfo

eureka.client.registration.status

InstanceInfo$Status

eureka.client.registration.app-group-name

java.lang.String

eureka.client.registration.ip-addr

java.lang.String

eureka.client.registration.vip-address

java.lang.String

eureka.client.registration.secure-vip-address

java.lang.String

eureka.client.registration.metadata

java.util.Map

eureka.client.registration.lease-info.registration-timestamp

long

eureka.client.registration.lease-info.renewal-timestamp

long

eureka.client.registration.lease-info.eviction-timestamp

long

eureka.client.registration.lease-info.service-up-timestamp

long

eureka.client.registration.lease-info.duration-in-secs

int

eureka.client.registration.lease-info.renewal-interval-in-secs

int

eureka.client.registration.prefer-ip-address

boolean

eureka.client.registration.timeout

java.time.Duration

eureka.client.registration.fail-fast

boolean

eureka.client.registration.deregister

boolean

eureka.client.registration.enabled

boolean

eureka.client.registration.retry-count

int

eureka.client.registration.retry-delay

java.time.Duration

eureka.client.registration.health-path

java.lang.String

Eureka Basic Authentication

You can customize the Eureka credentials in the URI you specify to in defaultZone.

For example:

Auto Registering with Eureka
eureka:
  client:
    defaultZone: "https://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@localhost:8761"

The above example externalizes configuration of the username and password Eureka to environment variables called EUREKA_USERNAME and EUREKA_PASSWORD.

Eureka Health Checks

Like Consul, the EurekaAutoRegistration will send HeartbeatEvent instances with the HealthStatus of the Micronaut application to Eureka.

The HealthMonitorTask will by default continuously monitor the HealthStatus of the application by running health checks and the CurrentHealthStatus will be sent to Eureka.

Secure Communication with Eureka

If you wish to configure HTTPS and have clients discovery Eureka instances and communicate over HTTPS then you should set the eureka.client.discovery.use-secure-port option to true to ensure that service communication happens over HTTPS and also configure HTTPS appropriately for each instance.

5 Repository

You can find the source code of this project in this repository: