$ mn create-app my-app --features discovery-consul
Micronaut Discovery Client
Adds Service Discovery Features for Eureka and Consul
Version: 4.4.0
1 Introduction
Using the CLI
If you are creating your project using the Micronaut CLI, supply either of |
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.
-
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.discovery:micronaut-discovery-client")
<dependency>
<groupId>io.micronaut.discovery</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:
-
DiscoveryClient.getServiceIds() - Returns all discovered service IDs
-
DiscoveryClient.getInstances(java.lang.String) - Returns all the ServiceInstance objects for a given service ID
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:
micronaut.caches.discovery-client.enabled=false
micronaut:
caches:
discovery-client:
enabled: false
[micronaut]
[micronaut.caches]
[micronaut.caches.discovery-client]
enabled=false
micronaut {
caches {
discoveryClient {
enabled = false
}
}
}
{
micronaut {
caches {
discovery-client {
enabled = false
}
}
}
}
{
"micronaut": {
"caches": {
"discovery-client": {
"enabled": false
}
}
}
}
Alternatively you can alter the cache’s expiration policy:
micronaut.caches.discovery-client.expire-after-access=60s
micronaut:
caches:
discovery-client:
expire-after-access: 60s
[micronaut]
[micronaut.caches]
[micronaut.caches.discovery-client]
expire-after-access="60s"
micronaut {
caches {
discoveryClient {
expireAfterAccess = "60s"
}
}
}
{
micronaut {
caches {
discovery-client {
expire-after-access = "60s"
}
}
}
}
{
"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:
-
Starting Consul with Docker.
docker run -p 8500:8500 consul
Alternatively you can install and run a local Consul instance.
Auto Registering with Consul
To register a Micronaut application with Consul simply add the necessary ConsulConfiguration. A minimal example can be seen below:
micronaut.application.name=hello-world
consul.client.registration.enabled=true
consul.client.defaultZone=${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}
micronaut:
application:
name: hello-world
consul:
client:
registration:
enabled: true
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
[micronaut]
[micronaut.application]
name="hello-world"
[consul]
[consul.client]
defaultZone="${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
[consul.client.registration]
enabled=true
micronaut {
application {
name = "hello-world"
}
}
consul {
client {
registration {
enabled = true
}
defaultZone = "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
}
}
{
micronaut {
application {
name = "hello-world"
}
}
consul {
client {
registration {
enabled = true
}
defaultZone = "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
}
}
}
{
"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
micronaut.application.instance.id=${random.shortuuid}
micronaut:
application:
name: hello-world
instance:
id: ${random.shortuuid}
[micronaut]
[micronaut.application]
name="hello-world"
[micronaut.application.instance]
id="${random.shortuuid}"
micronaut {
application {
name = "hello-world"
instance {
id = "${random.shortuuid}"
}
}
}
{
micronaut {
application {
name = "hello-world"
instance {
id = "${random.shortuuid}"
}
}
}
}
{
"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:
micronaut.application.name=hello-world
consul.client.registration.enabled=true
consul.client.registration.tags[0]=hello
consul.client.registration.tags[1]=world
consul.client.registration.meta.some=value
consul.client.registration.meta.instance_type=t2.medium
consul.client.registration.retry-count=5
consul.client.registration.fail-fast=false
consul.client.defaultZone=${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}
micronaut:
application:
name: hello-world
consul:
client:
registration:
enabled: true
tags:
- hello
- world
meta:
some: value
instance_type: t2.medium
retry-count: 5
fail-fast: false
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
[micronaut]
[micronaut.application]
name="hello-world"
[consul]
[consul.client]
defaultZone="${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
[consul.client.registration]
enabled=true
tags=[
"hello",
"world"
]
retry-count=5
fail-fast=false
[consul.client.registration.meta]
some="value"
instance_type="t2.medium"
micronaut {
application {
name = "hello-world"
}
}
consul {
client {
registration {
enabled = true
tags = ["hello", "world"]
meta {
some = "value"
instance_type = "t2.medium"
}
retryCount = 5
failFast = false
}
defaultZone = "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
}
}
{
micronaut {
application {
name = "hello-world"
}
}
consul {
client {
registration {
enabled = true
tags = ["hello", "world"]
meta {
some = "value"
instance_type = "t2.medium"
}
retry-count = 5
fail-fast = false
}
defaultZone = "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
}
}
}
{
"micronaut": {
"application": {
"name": "hello-world"
}
},
"consul": {
"client": {
"registration": {
"enabled": true,
"tags": ["hello", "world"],
"meta": {
"some": "value",
"instance_type": "t2.medium"
},
"retry-count": 5,
"fail-fast": false
},
"defaultZone": "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
}
}
}
-
tags
alters the tags -
meta
alters the metadata -
retry-count
alsters the retry count -
fail-fast
alters the fail fast behavior
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:
@Client(id = "hello-world")
interface HelloClient{
...
}
Alternatively you can also use @Client as a qualifier to @Inject
an instance of HttpClient:
@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.client.registration.check.http=true
consul:
client:
registration:
check:
http: true
[consul]
[consul.client]
[consul.client.registration]
[consul.client.registration.check]
http=true
consul {
client {
registration {
check {
http = true
}
}
}
}
{
consul {
client {
registration {
check {
http = true
}
}
}
}
}
{
"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.client.registration.ip-addr=<your base container ip>
consul.client.registration.prefer-ip-address=true
consul:
client:
registration:
ip-addr: <your base container ip>
prefer-ip-address: true
[consul]
[consul.client]
[consul.client.registration]
ip-addr="<your base container ip>"
prefer-ip-address=true
consul {
client {
registration {
ipAddr = "<your base container ip>"
preferIpAddress = true
}
}
}
{
consul {
client {
registration {
ip-addr = "<your base container ip>"
prefer-ip-address = true
}
}
}
}
{
"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.client.registration.ip-addr=<your base container ip>
consul.client.registration.prefer-ip-address=true
consul.client.registration.check.http=true
consul:
client:
registration:
ip-addr: <your base container ip>
prefer-ip-address: true
check:
http: true
[consul]
[consul.client]
[consul.client.registration]
ip-addr="<your base container ip>"
prefer-ip-address=true
[consul.client.registration.check]
http=true
consul {
client {
registration {
ipAddr = "<your base container ip>"
preferIpAddress = true
check {
http = true
}
}
}
}
{
consul {
client {
registration {
ip-addr = "<your base container ip>"
prefer-ip-address = true
check {
http = true
}
}
}
}
}
{
"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:
micronaut.application.name=hello-world
eureka.client.registration.enabled=true
eureka.client.defaultZone=${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}
micronaut:
application:
name: hello-world
eureka:
client:
registration:
enabled: true
defaultZone: "${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}"
[micronaut]
[micronaut.application]
name="hello-world"
[eureka]
[eureka.client]
defaultZone="${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}"
[eureka.client.registration]
enabled=true
micronaut {
application {
name = "hello-world"
}
}
eureka {
client {
registration {
enabled = true
}
defaultZone = "${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}"
}
}
{
micronaut {
application {
name = "hello-world"
}
}
eureka {
client {
registration {
enabled = true
}
defaultZone = "${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}"
}
}
}
{
"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.
eureka.client.readTimeout=5s
eureka.client.registration.asgName=myAsg
eureka.client.registration.countryId=10
eureka.client.registration.vipAddress=myapp
eureka.client.registration.leaseInfo.durationInSecs=60
eureka.client.registration.metadata.foo=bar
eureka.client.registration.retry-count=10
eureka.client.registration.retry-delay=5s
eureka.client.registration.appname=some-app-name
eureka.client.registration.hostname=foo.example.com
eureka.client.registration.ip-addr=1.2.3.4
eureka.client.registration.port=9090
eureka:
client:
readTimeout: 5s
registration:
asgName: myAsg
countryId: 10
vipAddress: 'myapp'
leaseInfo:
durationInSecs: 60
metadata:
foo: bar
retry-count: 10
retry-delay: 5s
appname: some-app-name
hostname: foo.example.com
ip-addr: 1.2.3.4
port: 9090
[eureka]
[eureka.client]
readTimeout="5s"
[eureka.client.registration]
asgName="myAsg"
countryId=10
vipAddress="myapp"
retry-count=10
retry-delay="5s"
appname="some-app-name"
hostname="foo.example.com"
ip-addr="1.2.3.4"
port=9090
[eureka.client.registration.leaseInfo]
durationInSecs=60
[eureka.client.registration.metadata]
foo="bar"
eureka {
client {
readTimeout = "5s"
registration {
asgName = "myAsg"
countryId = 10
vipAddress = "myapp"
leaseInfo {
durationInSecs = 60
}
metadata {
foo = "bar"
}
retryCount = 10
retryDelay = "5s"
appname = "some-app-name"
hostname = "foo.example.com"
ipAddr = "1.2.3.4"
port = 9090
}
}
}
{
eureka {
client {
readTimeout = "5s"
registration {
asgName = "myAsg"
countryId = 10
vipAddress = "myapp"
leaseInfo {
durationInSecs = 60
}
metadata {
foo = "bar"
}
retry-count = 10
retry-delay = "5s"
appname = "some-app-name"
hostname = "foo.example.com"
ip-addr = "1.2.3.4"
port = 9090
}
}
}
}
{
"eureka": {
"client": {
"readTimeout": "5s",
"registration": {
"asgName": "myAsg",
"countryId": 10,
"vipAddress": "myapp",
"leaseInfo": {
"durationInSecs": 60
},
"metadata": {
"foo": "bar"
},
"retry-count": 10,
"retry-delay": "5s",
"appname": "some-app-name",
"hostname": "foo.example.com",
"ip-addr": "1.2.3.4",
"port": 9090
}
}
}
}
-
asgName
the auto scaling group name -
countryId
the country id -
vipAddress
The Eureka VIP address -
durationInSecs
The lease information -
metadata
arbitrary instance metadata -
retry-count
How many times to retry -
retry-delay
How long to wait between retries -
appname
(optional) eureka instance application name, defaults to ${micronaut.application.name} -
hostname
(optional) exposed eureka instance hostname, useful in docker bridged network environments -
ip-addr
(optional) exposed eureka instance ip address, useful in docker bridged network environments -
port
(optional) exposed eureka instance port, useful in docker bridged network environments
Eureka Basic Authentication
You can customize the Eureka credentials in the URI you specify to in defaultZone
.
For example:
eureka.client.defaultZone=https://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@localhost:8761
eureka:
client:
defaultZone: "https://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@localhost:8761"
[eureka]
[eureka.client]
defaultZone="https://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@localhost:8761"
eureka {
client {
defaultZone = "https://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@localhost:8761"
}
}
{
eureka {
client {
defaultZone = "https://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@localhost:8761"
}
}
}
{
"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 Spring Cloud Config Server Support
Spring Cloud Config Server Spring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system. With the Config Server, you have a central place to manage external properties for applications across all environments.
A Micronaut application can be a Spring Cloud Config client to consume Spring Cloud Config Server configurations.
Setup
The quickest way to start a Spring Cloud Config Server is to use to use Spring Boot’s Quick Start.
Loading configurations from config server
The process to consume configurations in a Micronaut application from a Spring Cloud Config Server is very strait forward, simply add the necessary configurations. A complete example can be seen below:
micronaut.application.name=hello-world
micronaut.config-client.enabled=true
spring.cloud.config.enabled=true
spring.cloud.config.uri=http://configserver:9000
spring.cloud.config.name=filename1,filename2
micronaut:
application:
name: hello-world
config-client:
enabled: true
spring:
cloud:
config:
enabled: true
uri: "http://configserver:9000"
name: filename1,filename2
[micronaut]
[micronaut.application]
name="hello-world"
[micronaut.config-client]
enabled=true
[spring]
[spring.cloud]
[spring.cloud.config]
enabled=true
uri="http://configserver:9000"
name="filename1,filename2"
micronaut {
application {
name = "hello-world"
}
configClient {
enabled = true
}
}
spring {
cloud {
config {
enabled = true
uri = "http://configserver:9000"
name = "filename1,filename2"
}
}
}
{
micronaut {
application {
name = "hello-world"
}
config-client {
enabled = true
}
}
spring {
cloud {
config {
enabled = true
uri = "http://configserver:9000"
name = "filename1,filename2"
}
}
}
}
{
"micronaut": {
"application": {
"name": "hello-world"
},
"config-client": {
"enabled": true
}
},
"spring": {
"cloud": {
"config": {
"enabled": true,
"uri": "http://configserver:9000",
"name": "filename1,filename2"
}
}
}
}
The field name is optional, if it’s not informed, the value in micronaut.application.name will be used.
6 Repository
You can find the source code of this project in this repository: