Skip to content

Commit dbc33ce

Browse files
author
Auri Munoz
committed
Enable automatic registration for Quarkus apps using built-in Stork registrars (Consul, Eureka, Static)
1 parent ffa3592 commit dbc33ce

File tree

43 files changed

+1736
-86
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1736
-86
lines changed

.idea/icon.png

-6.64 KB
Binary file not shown.

.idea/icon_dark.png

-6.7 KB
Binary file not shown.

.idea/runConfigurations/mvnDebug.xml

Lines changed: 0 additions & 15 deletions
This file was deleted.

bom/application/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
<smallrye-reactive-types-converter.version>3.0.3</smallrye-reactive-types-converter.version>
6060
<smallrye-mutiny-vertx-binding.version>3.19.2</smallrye-mutiny-vertx-binding.version>
6161
<smallrye-reactive-messaging.version>4.28.0</smallrye-reactive-messaging.version>
62-
<smallrye-stork.version>2.7.3</smallrye-stork.version>
62+
<smallrye-stork.version>2.7.4</smallrye-stork.version>
6363
<jakarta.activation.version>2.1.3</jakarta.activation.version>
6464
<jakarta.annotation-api.version>3.0.0</jakarta.annotation-api.version>
6565
<jakarta.authentication-api>3.1.0</jakarta.authentication-api>
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
////
2+
This guide is maintained in the main Quarkus repository
3+
and pull requests should be submitted there:
4+
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
5+
////
6+
= Automatic Service Registration with SmallRye Stork for Quarkus
7+
:extension-status: preview
8+
include::_attributes.adoc[]
9+
:categories: cloud
10+
:topics: service-discovery, consul
11+
:extensions: io.quarkus:quarkus-smallrye-stork
12+
13+
This guide explains how to enable automatic registration and deregistration of a Quarkus application using SmallRye Stork and Consul, with minimal or no configuration.
14+
15+
== Overview
16+
17+
SmallRye Stork supports service registration backends like Consul and Eureka.
18+
Traditionally, developers link:{quickstarts-tree-url}/stork-programmatic-custom-registration-quickstart[had to explicitly configure and manually register] their service instances using code.
19+
With this feature, Quarkus applications can automatically register with a supported registry (e.g., Consul) during startup and deregister during shutdown — without writing boilerplate code.
20+
21+
**IMPORTANT**: Note that automatic registration is possible **only** for built-in registrars such as Consul, Eureka, and Static.
22+
If you're using a custom registrar, registration must still be handled programmatically.
23+
Check the `stork-programmatic-custom-registration-quickstart` link:{quickstarts-tree-url}/stork-programmatic-custom-registration-quickstart[directory] example for this use case.
24+
25+
This integration minimizes configuration effort and adheres to Quarkus' philosophy of doing as much as possible at build time.
26+
27+
== How It Works
28+
29+
When the application starts and the `stork-service-registration-consul` dependency, besides the `quarkus-smallrye-stork`, is present:
30+
31+
- Quarkus performs a build-time check for registration configuration.
32+
- If valid configuration is detected (or can be inferred), registration metadata is generated.
33+
- The application registers itself automatically using the detected or configured IP address and port.
34+
- During shutdown, the service is automatically deregistered.
35+
36+
The solution is located in the `stork-automatic-consul-registration-quickstart` link:{quickstarts-tree-url}/stork-automatic-consul-registration-quickstart[directory].
37+
38+
39+
== Configuration Options
40+
41+
The registration system uses sensible defaults but also allows for fine-grained control per service.
42+
43+
|===
44+
|Property |Description |Default
45+
46+
|`quarkus.stork.<service-name>.service-registrar.type`
47+
|Backend type (`consul`, etc.)
48+
|Auto-detected if only one registrar is used
49+
50+
|`quarkus.stork.<service-name>.service-registrar.ip-address`
51+
|IP address to register
52+
|Auto-detected using the host's first non-loopback address
53+
54+
|`quarkus.stork.<service-name>.service-registrar.port`
55+
|Port to register
56+
|`quarkus.http.port`
57+
58+
|`quarkus.stork.<service-name>.service-registrar.service-id`
59+
|Consul-specific service ID
60+
|Generated UUID
61+
62+
|`quarkus.stork.<service-name>.service-registrar.enabled`
63+
|Enable or disable registration
64+
|`true`
65+
66+
|`quarkus.stork.<service-name>.service-registrar.parameters.health-check-url`
67+
|Health check endpoint
68+
|If available, uses `liveness` endpoint
69+
70+
|`quarkus.stork.<service-name>.service-registrar.parameters.health-check-interval`
71+
|How often Consul pings for health
72+
|Not set
73+
74+
|`quarkus.stork.<service-name>.service-registrar.parameters.health-check-deregister-after`
75+
|Time after which the service is deregistered if unhealthy
76+
|Not set
77+
|===
78+
79+
== Use Cases
80+
81+
=== Case 1: Implicit Configuration
82+
83+
No configuration required at all:
84+
85+
[source,xml]
86+
----
87+
<dependency>
88+
<groupId>io.smallrye.stork</groupId>
89+
<artifactId>stork-service-registration-consul</artifactId>
90+
</dependency>
91+
----
92+
93+
Quarkus automatically uses:
94+
95+
- Application name
96+
- Detected IP address
97+
- HTTP port
98+
99+
The service is registered and unregistered automatically.
100+
101+
=== Case 2: Minimal Explicit Configuration
102+
103+
Only override what's necessary:
104+
105+
[source,properties]
106+
----
107+
quarkus.stork.my-service.service-registrar.ip-address=192.168.0.42
108+
----
109+
110+
No `quarkus.stork.my-service.service-registrar.type` is needed because there's only one registrar configuration in `application.properties`.
111+
112+
=== Case 3: Multiple Services Registrars
113+
114+
When multiple services are configured, you must specify the `quarkus.stork.<service-name>.service-registrar.typetype`:
115+
116+
[source,properties]
117+
----
118+
quarkus.stork.red-service.service-registrar.type=consul
119+
quarkus.stork.red-service.service-registrar.port=8083
120+
121+
quarkus.stork.blue-service.service-registrar.type=consul
122+
quarkus.stork.blue-service.service-registrar.port=8084
123+
----
124+
125+
=== Case 4: Disabling Registration
126+
127+
Automatic registration is enabled by default. If you want to disable registration, you can do it per service using the following property:
128+
129+
[source,properties]
130+
----
131+
quarkus.stork.red-service.service-registrar.enabled=false
132+
----
133+
134+
=== Case 5: Advanced Health Check Configuration
135+
136+
Customize how Consul polls service health:
137+
138+
[source,properties]
139+
----
140+
quarkus.stork.my-service.service-registrar.parameters.health-check-url=/q/health/live
141+
quarkus.stork.my-service.service-registrar.parameters.health-check-interval=10s
142+
quarkus.stork.my-service.service-registrar.parameters.health-check-deregister-after=1m
143+
----
144+
145+
=== Case 6: Static IP and Port
146+
147+
Useful when behind a proxy or in production with known IPs:
148+
149+
[source,properties]
150+
----
151+
quarkus.stork.my-service.service-registrar.ip-address=203.0.113.25
152+
quarkus.stork.my-service.service-registrar.port=80
153+
----
154+
155+
== IP Detection
156+
157+
By default, Quarkus attempts to determine the IP address using the first available, non-loopback network interface.
158+
If this fails, it falls back to `localhost`.
159+
160+
Note that in Docker/Kubernetes environments, this detection may result in internal IPs that are not externally reachable.
161+
In such cases, it's recommended to explicitly set the IP and port.
162+
163+
== Summary
164+
165+
- Minimal or zero config to get service registration working
166+
- Seamless integration with Consul via build-time processing
167+
- Per-service override support
168+
- Auto-deregistration on shutdown
169+
- Easily extendable to support other backends
170+
171+

extensions/smallrye-stork/deployment/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
<groupId>io.quarkus</groupId>
3030
<artifactId>quarkus-vertx-deployment</artifactId>
3131
</dependency>
32+
<dependency>
33+
<groupId>io.quarkus</groupId>
34+
<artifactId>quarkus-junit5-internal</artifactId>
35+
<scope>test</scope>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.testcontainers</groupId>
39+
<artifactId>testcontainers</artifactId>
40+
<scope>test</scope>
41+
</dependency>
3242
</dependencies>
3343

3444
<build>

extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static java.util.Arrays.asList;
44

5+
import org.eclipse.microprofile.config.Config;
6+
import org.eclipse.microprofile.config.ConfigProvider;
57
import org.jboss.jandex.DotName;
68
import org.jboss.logging.Logger;
79

@@ -21,7 +23,9 @@
2123
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
2224
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
2325
import io.quarkus.stork.SmallRyeStorkRecorder;
26+
import io.quarkus.stork.SmallRyeStorkRegistrationRecorder;
2427
import io.quarkus.stork.StorkConfigProvider;
28+
import io.quarkus.stork.StorkRegistrarConfigRecorder;
2529
import io.quarkus.vertx.deployment.VertxBuildItem;
2630
import io.smallrye.stork.spi.LoadBalancerProvider;
2731
import io.smallrye.stork.spi.ServiceDiscoveryProvider;
@@ -33,6 +37,12 @@
3337
public class SmallRyeStorkProcessor {
3438

3539
private static final String KUBERNETES_SERVICE_DISCOVERY_PROVIDER = "io.smallrye.stork.servicediscovery.kubernetes.KubernetesServiceDiscoveryProvider";
40+
private static final String CONSUL_SERVICE_REGISTRAR_PROVIDER = "io.smallrye.stork.serviceregistration.consul.ConsulServiceRegistrarProvider";
41+
private static final String EUREKA_SERVICE_REGISTRAR_PROVIDER = "io.smallrye.stork.serviceregistration.eureka.EurekaServiceRegistrarProvider";
42+
private static final String STATIC_SERVICE_REGISTRAR_PROVIDER = "io.smallrye.stork.serviceregistration.staticlist.StaticListServiceRegistrarProvider";
43+
private static final String CONSUL_SERVICE_REGISTRAR_TYPE = "consul";
44+
private static final String EUREKA_SERVICE_REGISTRAR_TYPE = "eureka";
45+
private static final String STATIC_SERVICE_REGISTRAR_TYPE = "static";
3646
private static final Logger LOGGER = Logger.getLogger(SmallRyeStorkProcessor.class.getName());
3747

3848
@BuildStep
@@ -86,10 +96,71 @@ void checkThatTheKubernetesExtensionIsUsedWhenKubernetesServiceDiscoveryInOnTheC
8696
@Consume(AlwaysBuildItem.class)
8797
@Consume(RuntimeConfigSetupCompleteBuildItem.class)
8898
@Consume(SyntheticBeansRuntimeInitBuildItem.class)
99+
@Consume(StorkRegistrationBuildItem.class)
100+
@Produce(StorkInitializedBuildItem.class)
89101
void initializeStork(SmallRyeStorkRecorder storkRecorder, ShutdownContextBuildItem shutdown, VertxBuildItem vertx) {
90102
storkRecorder.initialize(shutdown, vertx.getVertx());
91103
}
92104

105+
/**
106+
* Build step to configure automatic service registration
107+
* when using SmallRye Stork in a Quarkus application.
108+
* <p>
109+
* If a supported Stork service registrar is present (e.g., Consul or Eureka), this method
110+
* detects the registrar type and initializes its configuration, optionally including
111+
* health check information if Smallrye Health Check extension is present.
112+
* <p>
113+
* This step ensures a minimal and automatic setup experience for developers,
114+
* reducing manual configuration.
115+
*/
116+
@BuildStep
117+
@Record(ExecutionTime.RUNTIME_INIT)
118+
void configuresAutomaticRegistration(BuildProducer<StorkRegistrationBuildItem> registration,
119+
StorkRegistrarConfigRecorder registrarConfigRecorder, Capabilities capabilities) {
120+
if (isStorkRegistrarPresentAtRuntime()) {
121+
String smallryeHealthCheckDefaultPath = getDefaultHealthCheckPath(capabilities, ConfigProvider.getConfig());
122+
String registrarType = "";
123+
if (QuarkusClassLoader.isClassPresentAtRuntime(CONSUL_SERVICE_REGISTRAR_PROVIDER)) {
124+
registrarType = CONSUL_SERVICE_REGISTRAR_TYPE;
125+
} else if (QuarkusClassLoader.isClassPresentAtRuntime(EUREKA_SERVICE_REGISTRAR_PROVIDER)) {
126+
registrarType = EUREKA_SERVICE_REGISTRAR_TYPE;
127+
} else if (QuarkusClassLoader.isClassPresentAtRuntime(STATIC_SERVICE_REGISTRAR_PROVIDER)) {
128+
registrarType = STATIC_SERVICE_REGISTRAR_TYPE;
129+
}
130+
registrarConfigRecorder.setupServiceRegistrarConfig(registrarType,
131+
smallryeHealthCheckDefaultPath);
132+
}
133+
registration.produce(new StorkRegistrationBuildItem());
134+
135+
}
136+
137+
private static boolean isStorkRegistrarPresentAtRuntime() {
138+
return QuarkusClassLoader.isClassPresentAtRuntime(CONSUL_SERVICE_REGISTRAR_PROVIDER)
139+
|| QuarkusClassLoader.isClassPresentAtRuntime(EUREKA_SERVICE_REGISTRAR_PROVIDER)
140+
|| QuarkusClassLoader.isClassPresentAtRuntime(STATIC_SERVICE_REGISTRAR_PROVIDER);
141+
}
142+
143+
private static String getDefaultHealthCheckPath(Capabilities capabilities, Config quarkusConfig) {
144+
String smallryeHealthCheckDefaultPath = "";
145+
if (capabilities.isPresent(Capability.SMALLRYE_HEALTH)) {
146+
smallryeHealthCheckDefaultPath = quarkusConfig.getConfigValue("quarkus.management.root-path").getValue() + "/"
147+
+ quarkusConfig.getConfigValue("quarkus.smallrye-health.root-path").getValue() + "/"
148+
+ quarkusConfig.getConfigValue("quarkus.smallrye-health.liveness-path").getValue();
149+
LOGGER.infof("Using Smallrye Health Check defaults: %s", smallryeHealthCheckDefaultPath);
150+
}
151+
return smallryeHealthCheckDefaultPath;
152+
}
153+
154+
@BuildStep
155+
@Record(ExecutionTime.RUNTIME_INIT)
156+
void registerServiceInstance(StorkInitializedBuildItem storkInitializedBuildItem, ShutdownContextBuildItem shutdown,
157+
SmallRyeStorkRegistrationRecorder registrationRecorder) {
158+
if (isStorkRegistrarPresentAtRuntime()) {
159+
registrationRecorder.registerServiceInstance();
160+
registrationRecorder.deregisterServiceInstance(shutdown);
161+
}
162+
}
163+
93164
private static final class AlwaysBuildItem extends EmptyBuildItem {
94165
// Just here to be sure we run the `checkThatTheKubernetesExtensionIsUsedWhenKubernetesServiceDiscoveryInOnTheClasspath` build step.
95166
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.quarkus.stork.deployment;
2+
3+
import io.quarkus.builder.item.SimpleBuildItem;
4+
5+
public final class StorkInitializedBuildItem extends SimpleBuildItem {
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.quarkus.stork.deployment;
2+
3+
import io.quarkus.builder.item.SimpleBuildItem;
4+
5+
public final class StorkRegistrationBuildItem extends SimpleBuildItem {
6+
7+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.quarkus.stork;
2+
3+
import java.util.Collections;
4+
import java.util.Map;
5+
6+
import org.testcontainers.containers.GenericContainer;
7+
import org.testcontainers.containers.wait.strategy.Wait;
8+
9+
import com.github.dockerjava.api.model.ExposedPort;
10+
import com.github.dockerjava.api.model.HostConfig;
11+
import com.github.dockerjava.api.model.PortBinding;
12+
import com.github.dockerjava.api.model.Ports;
13+
14+
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
15+
16+
public class ConsulContainerWithFixedPortsTestResource implements QuarkusTestResourceLifecycleManager {
17+
18+
private GenericContainer<?> containerWithFixedPorts;
19+
protected static final String IMAGE = "consul:1.7";
20+
21+
@Override
22+
public Map<String, String> start() {
23+
24+
containerWithFixedPorts = new GenericContainer<>(IMAGE)
25+
.withCreateContainerCmdModifier(cmd -> {
26+
HostConfig hostConfig = new HostConfig()
27+
.withPortBindings(
28+
new PortBinding(Ports.Binding.bindPort(8500), new ExposedPort(8500)),
29+
new PortBinding(Ports.Binding.bindPort(8501), new ExposedPort(8501)));
30+
cmd.withHostConfig(hostConfig);
31+
})
32+
.withExposedPorts(8500, 8501)
33+
.withCommand("agent", "-dev", "-client=0.0.0.0", "-bind=0.0.0.0", "--https-port=8501")
34+
.waitingFor(Wait.forLogMessage(".*Synced node info.*", 1));
35+
containerWithFixedPorts.start();
36+
37+
return Collections.emptyMap();
38+
39+
}
40+
41+
@Override
42+
public void stop() {
43+
containerWithFixedPorts.stop();
44+
}
45+
46+
}

0 commit comments

Comments
 (0)