$ mn create-app my-app --features discovery-consul
Micronaut Discovery Client
Adds Service Discovery Features for Eureka and Consul
Version: 5.0.0-SNAPSHOT
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.
It also provides distributed configuration integrations for Consul, Vault, and Spring Cloud Config Server. The preferred way to consume those remote configuration sources is with explicit micronaut.config.import URIs instead of the deprecated bootstrap ConfigurationClient path.
See Distributed Configuration for the shared overview and the provider-specific sections for Consul, Vault, and Spring Cloud Config Server.
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 Distributed Configuration
Micronaut features a robust system for externalizing and adapting configuration to the environment.
When multiple services need to share configuration, this module provides Micronaut’s distributed configuration integrations for Consul, Vault, and Spring Cloud Config Server.
For new applications and new integrations, the recommended approach is to use configuration import via micronaut.config.import instead of relying on the bootstrap context. Configuration imports load remote or shared configuration as part of normal property source resolution, and custom remote sources can be integrated with PropertySourceImporter implementations.
The legacy bootstrap model is still available for compatibility and migrations. In that model, ConfigurationClient implementations resolve remote property sources during bootstrap and emit zero or many PropertySource instances.
You can either implement your own ConfigurationClient or use the integrations provided by this module. For new work, prefer configuration import support and importer-based integrations. The following sections cover the available distributed configuration options.
The bootstrap context is still supported for legacy scenarios, but it is no longer the recommended default for distributed configuration. If you still load distributed configuration during bootstrap, the application needs the discovery client infrastructure from io.micronaut.discovery:micronaut-discovery-client, and any beans involved in resolving that configuration must remain bootstrap-compatible. See Bootstrap Configuration for the legacy requirements.
|
4 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}"
}
}
}
-
tagsalters the tags -
metaalters the metadata -
retry-countalsters the retry count -
fail-fastalters 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
}
}
}
}
}
Distributed Configuration with Consul
|
Using the CLI
If you create your project using the Micronaut CLI, supply the $ mn create-app my-app --features config-consul |
The legacy bootstrap ConfigurationClient approach is deprecated. Prefer micronaut.config.import for distributed configuration and use the legacy bootstrap path only during migration.
Importing Consul configuration
The preferred approach is to import explicit Consul KV paths with micronaut.config.import:
micronaut.config.import=consul://localhost:8500/config/application
micronaut:
config:
import: "consul://localhost:8500/config/application"
[micronaut]
[micronaut.config]
import="consul://localhost:8500/config/application"
micronaut {
config {
'import' = "consul://localhost:8500/config/application"
}
}
{
micronaut {
config {
import = "consul://localhost:8500/config/application"
}
}
}
{
"micronaut": {
"config": {
"import": "consul://localhost:8500/config/application"
}
}
}
To load structured data formats from an explicit namespace:
micronaut.config.import=consul://localhost:8500/config/application?format=json
micronaut:
config:
import: "consul://localhost:8500/config/application?format=json"
[micronaut]
[micronaut.config]
import="consul://localhost:8500/config/application?format=json"
micronaut {
config {
'import' = "consul://localhost:8500/config/application?format=json"
}
}
{
micronaut {
config {
import = "consul://localhost:8500/config/application?format=json"
}
}
}
{
"micronaut": {
"config": {
"import": "consul://localhost:8500/config/application?format=json"
}
}
}
For ACL-protected Consul instances, prefer URI user-info:
micronaut.config.import=consul://token@localhost:8500/config/application
micronaut:
config:
import: "consul://token@localhost:8500/config/application"
[micronaut]
[micronaut.config]
import="consul://token@localhost:8500/config/application"
micronaut {
config {
'import' = "consul://token@localhost:8500/config/application"
}
}
{
micronaut {
config {
import = "consul://token@localhost:8500/config/application"
}
}
}
{
"micronaut": {
"config": {
"import": "consul://token@localhost:8500/config/application"
}
}
}
Query-parameter ACL tokens are also supported during migration:
micronaut.config.import=consul://localhost:8500/config/application?acl-token=token
micronaut:
config:
import: "consul://localhost:8500/config/application?acl-token=token"
[micronaut]
[micronaut.config]
import="consul://localhost:8500/config/application?acl-token=token"
micronaut {
config {
'import' = "consul://localhost:8500/config/application?acl-token=token"
}
}
{
micronaut {
config {
import = "consul://localhost:8500/config/application?acl-token=token"
}
}
}
{
"micronaut": {
"config": {
"import": "consul://localhost:8500/config/application?acl-token=token"
}
}
}
To enable refresh for an explicit importer path, append watch=true:
micronaut.config.import=consul://localhost:8500/config/application?watch=true
micronaut:
config:
import: "consul://localhost:8500/config/application?watch=true"
[micronaut]
[micronaut.config]
import="consul://localhost:8500/config/application?watch=true"
micronaut {
config {
'import' = "consul://localhost:8500/config/application?watch=true"
}
}
{
micronaut {
config {
import = "consul://localhost:8500/config/application?watch=true"
}
}
}
{
"micronaut": {
"config": {
"import": "consul://localhost:8500/config/application?watch=true"
}
}
}
When watch=true is used, Consul change notifications trigger an environment refresh so the imported configuration is reloaded and a RefreshEvent is published.
Treat import URIs with embedded credentials as sensitive values and store them accordingly.
Consul import URI reference
| ConnectionString part | Example | Meaning |
|---|---|---|
Scheme |
|
Selects the Consul importer. |
User info |
|
Optional Consul ACL token. The importer binds the URI username as the Consul ACL token. |
Host and port |
|
Consul host and port. |
Path |
|
Required explicit Consul KV/config path. |
Query string |
|
Optional importer settings. |
| Option | Example | Description |
|---|---|---|
|
|
Consul config format. Supported values are |
|
|
Consul datacenter. |
|
|
Consul ACL token. Prefer URI user-info instead. |
|
|
Whether failures should abort startup immediately. |
|
|
Enables importer-driven refresh for the explicit Consul import path. |
|
|
Number of config import retry attempts handled by Micronaut’s retryable importer support. |
|
|
Alias for |
|
|
Delay between import retries. |
|
|
Maximum overall retry delay. |
|
|
Multiplier applied between retry delays. |
|
|
Retry jitter factor from |
|
|
Read timeout for Consul client calls. |
|
|
Connection timeout for Consul client calls. |
Consul config import retries are now handled by Micronaut core’s retryable importer support. Do not combine these import retry settings with separate Consul client retry configuration for the same import path, or retries will be duplicated.
Legacy bootstrap configuration
To enable the legacy bootstrap integration, create a src/main/resources/bootstrap.[yml/toml/properties] file with the following configuration:
micronaut.application.name=hello-world
micronaut.config-client.enabled=true
consul.client.defaultZone=${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}
micronaut:
application:
name: hello-world
config-client:
enabled: true
consul:
client:
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
[micronaut]
[micronaut.application]
name="hello-world"
[micronaut.config-client]
enabled=true
[consul]
[consul.client]
defaultZone="${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
micronaut {
application {
name = "hello-world"
}
configClient {
enabled = true
}
}
consul {
client {
defaultZone = "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
}
}
{
micronaut {
application {
name = "hello-world"
}
config-client {
enabled = true
}
}
consul {
client {
defaultZone = "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
}
}
}
{
"micronaut": {
"application": {
"name": "hello-world"
},
"config-client": {
"enabled": true
}
},
"consul": {
"client": {
"defaultZone": "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
}
}
}
After enabling distributed configuration, store the configuration to share in Consul’s key/value store.
Storing configuration as key/value pairs
One option is to store keys and values directly in Consul. In this mode, Micronaut looks for configuration in the Consul /config directory by default.
You can alter the path by setting consul.client.config.path.
|
Within the configured path, Micronaut resolves values from the following directories in order of precedence:
| Directory | Description |
|---|---|
|
Configuration shared by all applications |
|
Configuration shared by all applications for the |
|
Application-specific configuration, for example |
|
Application-specific configuration for an active environment |
The value of APPLICATION_NAME comes from micronaut.application.name in bootstrap configuration.
For example, this cURL command stores foo.bar=myvalue in /config/application:
curl -X PUT -d @- localhost:8500/v1/kv/config/application/foo.bar <<< myvalue
With the legacy bootstrap integration enabled, @Value("${foo.bar}") and environment.getProperty(..) can then resolve the value from Consul.
Storing configuration in YAML, JSON, or properties format
Some Consul setups store configuration in a structured blob instead of individual keys. The legacy bootstrap integration supports YAML, JSON, and Java properties formats.
Configure the format with consul.client.config.format. For example, to use JSON:
consul.client.config.format=JSON
consul:
client:
config:
format: JSON
[consul]
[consul.client]
[consul.client.config]
format="JSON"
consul {
client {
config {
format = "JSON"
}
}
}
{
consul {
client {
config {
format = "JSON"
}
}
}
}
{
"consul": {
"client": {
"config": {
"format": "JSON"
}
}
}
}
Then write the configuration as JSON to Consul:
curl -X PUT localhost:8500/v1/kv/config/application \
-d @- << EOF
{ "foo": { "bar": "myvalue" } }
EOF
Storing configuration as file references
Another option is to mirror a Git repository into Consul using a tool such as git2consul.
In that setup, keys in Consul represent files such as /config/application.yml or /config/hello-world-test.json. Configure the legacy bootstrap integration to use the FILE format:
consul.client.config.format=FILE
consul:
client:
config:
format: FILE
[consul]
[consul.client]
[consul.client.config]
format="FILE"
consul {
client {
config {
format = "FILE"
}
}
}
{
consul {
client {
config {
format = "FILE"
}
}
}
}
{
"consul": {
"client": {
"config": {
"format": "FILE"
}
}
}
}
Consul Watch
The watcher calls the KV Store API to watch all keys used for the distributed configurations,
using Blocking Queries
to wait for any changes made on those keys.
If no change occurred during the max-wait-duration, the query will be re-executed after the delay-duration.
When a change is detected in a KV used for configurations,
the corresponding PropertySource will be updated and a RefreshEvent published.
See Micronaut > Refreshable Scope for more details
The legacy watcher can be configured using
micronaut.application.name=hello-world
micronaut.config-client.enabled=true
consul.client.defaultZone=${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}
consul.client.config.format=YAML
consul.client.config.path=/config
consul.client.watch.enabled=true
consul.client.blocking-queries.max-wait-duration=10m
consul.client.blocking-queries.delay-duration=50ms
micronaut:
application:
name: hello-world
config-client:
enabled: true
consul:
client:
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
config:
format: YAML
path: /config
watch:
enabled: true
blocking-queries:
max-wait-duration: 10m
delay-duration: 50ms
[micronaut]
[micronaut.application]
name="hello-world"
[micronaut.config-client]
enabled=true
[consul]
[consul.client]
defaultZone="${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
[consul.client.config]
format="YAML"
path="/config"
[consul.client.watch]
enabled=true
[consul.client.blocking-queries]
max-wait-duration="10m"
delay-duration="50ms"
micronaut {
application {
name = "hello-world"
}
configClient {
enabled = true
}
}
consul {
client {
defaultZone = "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
config {
format = "YAML"
path = "/config"
}
watch {
enabled = true
}
blockingQueries {
maxWaitDuration = "10m"
delayDuration = "50ms"
}
}
}
{
micronaut {
application {
name = "hello-world"
}
config-client {
enabled = true
}
}
consul {
client {
defaultZone = "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
config {
format = "YAML"
path = "/config"
}
watch {
enabled = true
}
blocking-queries {
max-wait-duration = "10m"
delay-duration = "50ms"
}
}
}
}
{
"micronaut": {
"application": {
"name": "hello-world"
},
"config-client": {
"enabled": true
}
},
"consul": {
"client": {
"defaultZone": "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}",
"config": {
"format": "YAML",
"path": "/config"
},
"watch": {
"enabled": true
},
"blocking-queries": {
"max-wait-duration": "10m",
"delay-duration": "50ms"
}
}
}
}
Formats supported are
-
NATIVE -
JSON -
PROPERTIES -
YAML
5 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
}
}
}
}
-
asgNamethe auto scaling group name -
countryIdthe country id -
vipAddressThe Eureka VIP address -
durationInSecsThe lease information -
metadataarbitrary instance metadata -
retry-countHow many times to retry -
retry-delayHow 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.
6 Spring Cloud Config Server Support
Spring Cloud Config Server 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.
Micronaut includes native Spring Cloud Config client support for consuming Spring Cloud Config Server configuration.
The legacy bootstrap ConfigurationClient approach is deprecated. Prefer micronaut.config.import for new applications and migrations.
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 preferred process to consume configurations in a Micronaut application from a Spring Cloud Config Server is to use an explicit import URI:
micronaut.config.import=springcloud://configserver:9000/hello-world/default
micronaut:
config:
import: "springcloud://configserver:9000/hello-world/default"
[micronaut]
[micronaut.config]
import="springcloud://configserver:9000/hello-world/default"
micronaut {
config {
'import' = "springcloud://configserver:9000/hello-world/default"
}
}
{
micronaut {
config {
import = "springcloud://configserver:9000/hello-world/default"
}
}
}
{
"micronaut": {
"config": {
"import": "springcloud://configserver:9000/hello-world/default"
}
}
}
To target multiple profiles or a Git label:
micronaut.config.import=springcloud://configserver:9000/hello-world/first,second?label=main
micronaut:
config:
import: "springcloud://configserver:9000/hello-world/first,second?label=main"
[micronaut]
[micronaut.config]
import="springcloud://configserver:9000/hello-world/first,second?label=main"
micronaut {
config {
'import' = "springcloud://configserver:9000/hello-world/first,second?label=main"
}
}
{
micronaut {
config {
import = "springcloud://configserver:9000/hello-world/first,second?label=main"
}
}
}
{
"micronaut": {
"config": {
"import": "springcloud://configserver:9000/hello-world/first,second?label=main"
}
}
}
For secured servers, prefer credentials in the URI authority:
micronaut.config.import=springcloud://user:password@configserver:9000/hello-world/default
micronaut:
config:
import: "springcloud://user:password@configserver:9000/hello-world/default"
[micronaut]
[micronaut.config]
import="springcloud://user:password@configserver:9000/hello-world/default"
micronaut {
config {
'import' = "springcloud://user:password@configserver:9000/hello-world/default"
}
}
{
micronaut {
config {
import = "springcloud://user:password@configserver:9000/hello-world/default"
}
}
}
{
"micronaut": {
"config": {
"import": "springcloud://user:password@configserver:9000/hello-world/default"
}
}
}
Query-parameter credentials are also supported during migration, but URI user-info is preferred:
micronaut.config.import=springcloud://configserver:9000/hello-world/default?username=user&password=secret
micronaut:
config:
import: "springcloud://configserver:9000/hello-world/default?username=user&password=secret"
[micronaut]
[micronaut.config]
import="springcloud://configserver:9000/hello-world/default?username=user&password=secret"
micronaut {
config {
'import' = "springcloud://configserver:9000/hello-world/default?username=user&password=secret"
}
}
{
micronaut {
config {
import = "springcloud://configserver:9000/hello-world/default?username=user&password=secret"
}
}
}
{
"micronaut": {
"config": {
"import": "springcloud://configserver:9000/hello-world/default?username=user&password=secret"
}
}
}
Treat import URIs with embedded credentials as sensitive values and store them accordingly.
Spring Cloud Config import URI reference
| ConnectionString part | Example | Meaning |
|---|---|---|
Scheme |
|
Selects the Spring Cloud Config importer. |
User info |
|
Optional Basic-auth credentials. |
Host and port |
|
Spring Cloud Config Server host and port. |
Path |
|
Required. The first path segment is the application name and the second is the profile list. Multiple profiles may be comma-separated. |
Query string |
|
Optional importer settings. |
| Option | Example | Description |
|---|---|---|
|
|
Optional Git label or branch requested from the Config Server. |
|
|
Whether failures should abort startup immediately. |
|
|
Optional Basic-auth username. Prefer URI user-info instead. |
|
|
Optional Basic-auth password. Prefer URI user-info instead. |
|
|
Number of config import retry attempts handled by Micronaut’s retryable importer support. |
|
|
Alias for |
|
|
Delay between import retries. |
|
|
Maximum overall retry delay. |
|
|
Multiplier applied between retry delays. |
|
|
Retry jitter factor from |
|
|
Read timeout for remote calls. |
|
|
Connection timeout for remote calls. |
The legacy bootstrap/client configuration is still available during migration. 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"
}
}
}
}
Optional legacy retry settings are also supported:
-
retry-attemptsspecifies the number of retries -
retry-delayspecifies the delay between retries
The legacy integration uses the configured micronaut.application.name to look up property sources from the server configured via spring.cloud.config.uri.
The name field is optional in the legacy configuration. With micronaut.config.import, the application name and profiles are explicit in the URI path. See the Spring Cloud Config documentation for server setup details.
7 HashiCorp Vault Support
HashiCorp Vault can be used as a distributed configuration source for Micronaut applications.
The legacy bootstrap ConfigurationClient approach is deprecated. Prefer micronaut.config.import with explicit Vault paths.
Importing configuration from Vault
micronaut.config.import=vault://testtoken@localhost:8200/application
micronaut:
config:
import: "vault://testtoken@localhost:8200/application"
[micronaut]
[micronaut.config]
import="vault://testtoken@localhost:8200/application"
micronaut {
config {
'import' = "vault://testtoken@localhost:8200/application"
}
}
{
micronaut {
config {
import = "vault://testtoken@localhost:8200/application"
}
}
}
{
"micronaut": {
"config": {
"import": "vault://testtoken@localhost:8200/application"
}
}
}
micronaut.config.import=vault://testtoken@localhost:8200/application/prod
micronaut:
config:
import: "vault://testtoken@localhost:8200/application/prod"
[micronaut]
[micronaut.config]
import="vault://testtoken@localhost:8200/application/prod"
micronaut {
config {
'import' = "vault://testtoken@localhost:8200/application/prod"
}
}
{
micronaut {
config {
import = "vault://testtoken@localhost:8200/application/prod"
}
}
}
{
"micronaut": {
"config": {
"import": "vault://testtoken@localhost:8200/application/prod"
}
}
}
micronaut.config.import=vault://testtoken@localhost:8200/application?kv-version=V2&secret-engine-name=backendv2-prefixed&path-prefix=mock/v2/path/prefix
micronaut:
config:
import: "vault://testtoken@localhost:8200/application?kv-version=V2&secret-engine-name=backendv2-prefixed&path-prefix=mock/v2/path/prefix"
[micronaut]
[micronaut.config]
import="vault://testtoken@localhost:8200/application?kv-version=V2&secret-engine-name=backendv2-prefixed&path-prefix=mock/v2/path/prefix"
micronaut {
config {
'import' = "vault://testtoken@localhost:8200/application?kv-version=V2&secret-engine-name=backendv2-prefixed&path-prefix=mock/v2/path/prefix"
}
}
{
micronaut {
config {
import = "vault://testtoken@localhost:8200/application?kv-version=V2&secret-engine-name=backendv2-prefixed&path-prefix=mock/v2/path/prefix"
}
}
}
{
"micronaut": {
"config": {
"import": "vault://testtoken@localhost:8200/application?kv-version=V2&secret-engine-name=backendv2-prefixed&path-prefix=mock/v2/path/prefix"
}
}
}
Query-parameter tokens are also supported during migration, but URI user-info is preferred:
micronaut.config.import=vault://localhost:8200/application?token=testtoken
micronaut:
config:
import: "vault://localhost:8200/application?token=testtoken"
[micronaut]
[micronaut.config]
import="vault://localhost:8200/application?token=testtoken"
micronaut {
config {
'import' = "vault://localhost:8200/application?token=testtoken"
}
}
{
micronaut {
config {
import = "vault://localhost:8200/application?token=testtoken"
}
}
}
{
"micronaut": {
"config": {
"import": "vault://localhost:8200/application?token=testtoken"
}
}
}
Treat import URIs with embedded credentials as sensitive values and store them accordingly.
Vault import URI reference
| ConnectionString part | Example | Meaning |
|---|---|---|
Scheme |
|
Selects the Vault importer. |
User info |
|
Optional Vault token. The importer binds the URI username as |
Host and port |
|
Vault host and port. |
Path |
|
Required explicit secret path. Leading |
Query string |
|
Optional importer settings. |
| Option | Example | Description |
|---|---|---|
|
|
Vault token. Prefer URI user-info instead. |
|
|
Vault KV engine version. |
|
|
Vault secret engine name. |
|
|
Optional path prefix prepended before the explicit secret path. |
|
|
Whether failures should abort startup immediately. |
|
|
Number of config import retry attempts handled by Micronaut’s retryable importer support. |
|
|
Alias for |
|
|
Delay between import retries. |
|
|
Maximum overall retry delay. |
|
|
Multiplier applied between retry delays. |
|
|
Retry jitter factor from |
|
|
Read timeout for remote calls. |
|
|
Connection timeout for remote calls. |
During migration, the older bootstrap configuration is still available:
micronaut.application.name=hello-world
micronaut.config-client.enabled=true
vault.client.uri=http://localhost:8200
vault.client.config.enabled=true
micronaut:
application:
name: hello-world
config-client:
enabled: true
vault:
client:
uri: http://localhost:8200
config:
enabled: true
[micronaut]
[micronaut.application]
name="hello-world"
[micronaut.config-client]
enabled=true
[vault]
[vault.client]
uri="http://localhost:8200"
[vault.client.config]
enabled=true
micronaut {
application {
name = "hello-world"
}
configClient {
enabled = true
}
}
vault {
client {
uri = "http://localhost:8200"
config {
enabled = true
}
}
}
{
micronaut {
application {
name = "hello-world"
}
config-client {
enabled = true
}
}
vault {
client {
uri = "http://localhost:8200"
config {
enabled = true
}
}
}
}
{
"micronaut": {
"application": {
"name": "hello-world"
},
"config-client": {
"enabled": true
}
},
"vault": {
"client": {
"uri": "http://localhost:8200",
"config": {
"enabled": true
}
}
}
}
Prefer the importer-based configuration for new applications and migrations.
The legacy bootstrap integration uses the configured micronaut.application.name to look up property sources from Vault.
| Secret Path | Description |
|---|---|
|
Configuration shared by all applications |
|
Application-specific configuration |
|
Configuration shared by all applications for an active environment |
|
Application-specific configuration for an active environment |
With micronaut.config.import, the secret path is explicit in the import URI, so those default lookup paths are no longer implied. See the HashiCorp Vault KV documentation for server setup and secret storage details.
8 Breaking Changes
This section documents breaking changes between Micronaut Discovery Client versions:
Distributed configuration migration note
Distributed configuration via the legacy bootstrap ConfigurationClient path is now deprecated. Prefer micronaut.config.import with explicit provider URIs such as:
-
consul://localhost:8500/config/application -
vault://localhost:8200/application -
springcloud://configserver:9000/hello-world/default
Applications can continue using the legacy bootstrap path during migration, but startup will log deprecation warnings that point to the importer-based alternative.
Micronaut Discovery Client 5.0.0
The following classes deprecated previously are now removed. The replacements are as follows:
Deprecated Class |
Replacement |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The following methods of ConsulOperations deprecated previously are now removed. The replacements are as follows. Note that some have changed return types.
Deprecated Method |
Replacement |
|
|
|
|
|
|
|
|
|
|
|
|
As a consequence of removals, the following ConsulOperations methods were not deprecated previously, but their return types changed:
Method with changed return type |
Replacement |
|
|
|
|
As a consequence of removals, the following NewServiceEntry methods were not deprecated previously, but their method signatures have changed:
Method with changed method signature |
Replacement |
|
|
|
|
|
|
|
|
-
The constructor
io.micronaut.discovery.consul.ConsulServiceInstance(HealthEntry, String)deprecated previously is now removed. UseConsulServiceInstance(ConsulHealthEntry, String)instead. -
The method
io.micronaut.discovery.consul.ConsulServiceInstance.getHealthEntry()deprecated previously is now removed. It was unused and has no replacement. -
The method
io.micronaut.discovery.consul.registration.ConsulAutoRegistration.customizeServiceEntry(ServiceInstance, ConsulNewServiceEntry)deprecated previously is now removed. It was unused and has no replacement.
9 Repository
You can find the source code of this project in this repository: