Skip to content

Compose Dev Service #46848

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 2, 2025
Merged

Conversation

ozangunalp
Copy link
Contributor

@ozangunalp ozangunalp commented Mar 17, 2025

The Compose Dev Service discovers Compose files in a Quarkus project and starts services defined in the Compose project, using Docker Compose or Podman Compose.

Detailed feature docs in compose-dev-services.adoc.


Extension dev services build steps can consume the DevServicesComposeProjectBuildItem and can locate whether compose has created an eligible service that they can configure the Quarkus app for, much like how dev service instances shared between projects.

This change already integrates following platform dev services: Datasource Dev Services (DB2, MariaDB, MS SQL, MySQL, Oracle, Postgres), Keycloak, Elasticsearch, Infinispan, Kafka, Kubernetes, AMQP, MQTT, Pulsar, RabbitMQ, MongoDB, Observability, Redis, Apicurio Schema Registry.

For any not-yet-integrated service, it is possible to include description in the compose files to produce runtime configuration for the Quarkus application in dev/test.

The dev service hashes targeted compose files in order to detect changes in the content and restart compose.

Dev UI Devservices pane shows a brief description of the Compose Dev Services.

If a Quarkus project doesn't contain Compose files, it can still discover Compose services using a configuration, effectively sharing a single compose project between multiple projects.

Copy link

quarkus-bot bot commented Mar 17, 2025

/cc @gsmet (elasticsearch), @loicmathieu (elasticsearch), @marko-bekhta (elasticsearch)

This comment has been minimized.

@geoand
Copy link
Contributor

geoand commented Mar 17, 2025

cc @iocanel @alesj

Copy link

github-actions bot commented Mar 17, 2025

🙈 The PR is closed and the preview is expired.

This comment has been minimized.

@ozangunalp ozangunalp force-pushed the compose_dev_service branch from 5c8ef2f to 2dd25b2 Compare March 18, 2025 08:44

This comment has been minimized.

@ozangunalp ozangunalp force-pushed the compose_dev_service branch from 2dd25b2 to 3232191 Compare March 18, 2025 09:10

This comment has been minimized.

@ozangunalp
Copy link
Contributor Author

@geoand last commit contains a change to enable the use of the shared network with compose services and the container-based app test runner.
Combining compose services with regular dev services for this container-based run is still problematic. I think the best way is for dev service implementations to re-use the default network created by the compose, when it is available.

It also smooths already running compose app discovery scenarios and continuous-testing support. With the addition of start-containers flag and reuse-project-for-tests flags, it's easier to run docker compose up separately and keep it running. cf @tqvarnst (Would need some documentation on that)

This comment has been minimized.

if (strategy != null) {
LOG.infov("Waiting for service {0} to be ready", serviceName);
try {
strategy.waitUntilReady(instance);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For information, when disabling ryuk and enabling reuse of tests

quarkus.compose.devservices.ryuk-enabled=true
quarkus.compose.devservices.reuse-project-for-tests=true

An error can be thrown because the listChildContainers also returns stopped containers and when the wait strategy is called on a stopped instance it throws an error.

For example:

            io.quarkus.builder.BuildException: Build failure: Build failed due to errors
                [error]: Build step io.quarkus.devservices.deployment.compose.ComposeDevServicesProcessor#config threw an exception: java.util.concurrent.CompletionException: java.lang.RuntimeException: java.lang.IllegalArgumentException: Requested port (5556) is not mapped
                at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
                at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
                at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1807)
                at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
                at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2675)
                at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2654)
                at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1627)
                at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1594)
                at java.base/java.lang.Thread.run(Thread.java:1583)
                at org.jboss.threads.JBossThread.run(JBossThread.java:499)
            Caused by: java.lang.RuntimeException: java.lang.IllegalArgumentException: Requested port (5556) is not mapped
                at org.rnorth.ducttape.timeouts.Timeouts.callFuture(Timeouts.java:68)
                at org.rnorth.ducttape.timeouts.Timeouts.doWithTimeout(Timeouts.java:60)
                at org.testcontainers.containers.wait.strategy.WaitAllStrategy.waitUntilReady(WaitAllStrategy.java:54)
                at io.quarkus.devservices.deployment.compose.ComposeProject.waitUntilReady(ComposeProject.java:236)
                at io.quarkus.devservices.deployment.compose.ComposeProject.lambda$waitOnThread$7(ComposeProject.java:226)
                at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's odd. Those config aren't that related. What are your steps to reproduce it?

If compose has an error starting services, like container created but not started etc. It leaves services in an intermediate state. I think we should capture errors on running compose up and run a compose down immediately and propagate the error so when the problem gets fixed we have the expected running state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a ran tests that started the compose services, then the containers stayed on because i disabled ryuk.

And after a while one of the container stopped and then the tests threw this error at each run until i removed the stopped container.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally dev services do compose down on shutdown. But during development, between changing the compose files and app restarts, things can get ugly and it can be in a state where compose up has run but didn't get registered for shutdown cleanup. ryuk is there to clean that up.

If a service container has stopped, the dev service is looking to validate that the container is healthy by checking mapped ports.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a reproducer here, https://github.com/Malandril/reproduce-compose you can run the ./reproduce-error.sh . If the listChildContainer is patched to only list running containers, the tests works and the service is started correctly.

diff --git i/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/compose/ComposeProject.java w/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/compose/ComposeProject.java
index 723cb3069b5..c6f14d45c78 100644
--- i/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/compose/ComposeProject.java
+++ w/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/compose/ComposeProject.java
@@ -299,6 +299,7 @@ private List<Container> listChildContainers() {
         return dockerClient
                 .listContainersCmd()
                 .withLabelFilter(Map.of(DOCKER_COMPOSE_PROJECT, project))
+                .withStatusFilter(List.of("running"))
                 .withShowAll(true)
                 .exec();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the reproducer!
It may be interesting to capture running and restarting containers at this stage.
I remember testing without showAll(true), and it didn't work at some point, but I don't remember why and I changed quiet a few things since.

@ozangunalp ozangunalp force-pushed the compose_dev_service branch from 5c34b6b to 1d59196 Compare March 26, 2025 10:02
})
.or(() -> ComposeLocator.locateContainer(composeProjectBuildItem,
List.of(capturedDevServicesConfiguration.imageName()), LaunchMode.current())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ozangunalp so no need for additional label / tag? Image name is enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If observability service can be anything, it is better to specify the image-name directly or use the container locator label.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be multiple services, but should have unique image-name.

@geoand
Copy link
Contributor

geoand commented Mar 26, 2025

This will also need a rebase

@ozangunalp ozangunalp force-pushed the compose_dev_service branch from 1d59196 to 722062c Compare March 26, 2025 15:23
@ozangunalp
Copy link
Contributor Author

Rebased and reviewed the doc. I will squash once it's good to go.

This comment has been minimized.

.map(r -> {
Map<String, String> cfg = new LinkedHashMap<>();
for (ContainerInfo.ContainerPort port : r.containerInfo().exposedPorts()) {
cfg.putAll(dev.config(port.privatePort(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alesj could we use these properties to setup the extensions instead of relying on the internal system properties?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brunobat I think we can make adjustments in later iterations..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brunobat wdym? On which internal system properties do we rely on?

Copy link
Contributor

@brunobat brunobat Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the grafana.endpoint and otel-collector.url. I wonder if we can rid of them by using the container info

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alesj @brunobat we could use the service-name attribute. if it would've been lgtm instead of quarkus.
Only keycloak and lgtm have quarkus as service name. I'd like to change that, if you're ok.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brunobat I don't see (yet) how the properties you mention can replace the current "internal" ones ...

This comment has been minimized.

- Fix amqp username prop
- Introduced DevServicesNetworkIdBuildItem to set the dev services network id to the startup action, Regular dev services not reuse the compose default network when available
@ozangunalp ozangunalp force-pushed the compose_dev_service branch from 722062c to 74bbe72 Compare March 27, 2025 09:21
@ozangunalp
Copy link
Contributor Author

@Malandril thank you for testing this feature early.
I feel like this is stable enough to merge.
Please don't hesitate to come up with any other suggestions.

Copy link

quarkus-bot bot commented Mar 27, 2025

Status for workflow Quarkus Documentation CI

This is the status report for running Quarkus Documentation CI on commit 74bbe72.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

Warning

There are other workflow runs running, you probably need to wait for their status before merging.

Copy link

quarkus-bot bot commented Mar 27, 2025

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit 74bbe72.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

You can consult the Develocity build scans.

@ozangunalp
Copy link
Contributor Author

@maxandersen shall we get this in ?

@brunobat
Copy link
Contributor

brunobat commented Apr 1, 2025

@ozangunalp I tried the PR with Postgres the Observability dev service. Works ok with the first but not with the second.
The service starts but doesn't receive any data.
I wonder why the test doesn't fail... Debugging now.

@ozangunalp
Copy link
Contributor Author

ozangunalp commented Apr 1, 2025

@brunobat probably not configured the observability stack correctly. Is it an ad-hoc test you are running ?

I've done a test with the observability configured, but the "auto-discovery" doesn't work yet as I mentioned before.

Copy link
Contributor

@brunobat brunobat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. The dev service works if the defaults are used.
I was setting the protocol and a different port on the app and I found a bug in the dev service not related with this PR.

@ozangunalp ozangunalp merged commit 77ebed3 into quarkusio:main Apr 2, 2025
59 checks passed
@quarkus-bot quarkus-bot bot added this to the 3.22 - main milestone Apr 2, 2025
@adriens
Copy link

adriens commented May 1, 2025

🤩 🙌 👏

@vsevel
Copy link
Contributor

vsevel commented May 6, 2025

hello @ozangunalp I had a look at the documentation.
one thing I was looking for is to have a better control on start/stop of dev services, for quarkus:dev, or when running individual tests.
I can see that we can have that with compose support, however I now have to figure out how to write my compose file, whereas dev services were managing this for me.
one idea was that quarkus could generate the compose file, at least for the dev services relying on testcontainers.
that is something that @brunobat was hinting at in #users > dev services start time => docker compose? @ 💬
do you think this would be relevant? feasable? a good idea?
if not, I will have to come up with a docker compose for each dev service, document it and ask each project to reuse my template. and every time a dev service would decide to make a change (for instance a new env variable), I would have to update my template, and ask each dev team to update their respective projects. this would not scale.
ideally I would be able to ask dev service to add its own definition in the compose file, then commit this file in my project.
and next time I upgrade to the new quarkus version, regenerate a new compose file (in case a dev service decided it wanted to create containers some other way).
what do you think?

@vsevel
Copy link
Contributor

vsevel commented May 6, 2025

I thought that I could either let quarkus start my compose file, and start it myself, and quarkus would reuse the container started through compose.
I tried running docker compose -f compose-devservices.yml up then mvn package
and in the logs I see:

2025-05-06 15:39:17,239 INFO  [io.qua.dev.dep.com.ComposeRunner] (build-21) {} Compose is running command: docker.exe compose up -d
2025-05-06 15:39:17,992 INFO  [io.qua.dev.dep.com.ComposeRunner] (Process stdout) {}  Network quarkus-devservices-myapp-geswcp_default  Creating
2025-05-06 15:39:18,055 INFO  [io.qua.dev.dep.com.ComposeRunner] (Process stdout) {}  Network quarkus-devservices-myapp-geswcp_default  Created
2025-05-06 15:39:18,057 INFO  [io.qua.dev.dep.com.ComposeRunner] (Process stdout) {}  Container quarkus-devservices-myapp-geswcp-db-1  Creating
2025-05-06 15:39:18,167 INFO  [io.qua.dev.dep.com.ComposeRunner] (Process stdout) {}  Container quarkus-devservices-myapp-geswcp-db-1  Created
2025-05-06 15:39:18,177 INFO  [io.qua.dev.dep.com.ComposeRunner] (Process stdout) {}  Container quarkus-devservices-myapp-geswcp-db-1  Starting
2025-05-06 15:39:18,515 INFO  [io.qua.dev.dep.com.ComposeRunner] (Process stdout) {}  Container quarkus-devservices-myapp-geswcp-db-1  Started
2025-05-06 15:39:18,544 INFO  [io.qua.dev.dep.com.ComposeRunner] (build-21) {} Compose has finished running
2025-05-06 15:39:18,598 INFO  [io.qua.dev.dep.com.ComposeProject] (build-109) {} Waiting for service db to be ready
2025-05-06 15:39:23,646 INFO  [io.qua.dev.dep.com.ComposeProject] (build-109) {} Service db is ready

if I look at the running containers I see:

$ docker ps
CONTAINER ID   IMAGE                                                COMMAND                  CREATED              STATUS                        PORTS                                                                  NAMES
3f0baeb97d8d   redpandadata/redpanda:v24.1.2                        "sh -c 'while [ ! -f…"   32 seconds ago       Up 31 seconds                 8081/tcp, 9644/tcp, 0.0.0.0:63330->8082/tcp, 0.0.0.0:32775->9092/tcp   frosty_kowalevski
5f0176a314d5   quay.io/apicurio/apicurio-registry-mem:2.4.2.Final   "/usr/local/s2i/run"     32 seconds ago       Up 31 seconds                 8443/tcp, 8778/tcp, 9779/tcp, 0.0.0.0:63329->8080/tcp                  flamboyant_proskuriakova
31422860dbf1   postgres:17                                          "docker-entrypoint.s…"   38 seconds ago       Up 38 seconds (healthy)       0.0.0.0:63321->5432/tcp                                                quarkus-devservices-myapp-geswcp-db-1
249786c28e40   testcontainers/ryuk:0.11.0                           "/bin/ryuk"              40 seconds ago       Up 39 seconds                 0.0.0.0:63318->8080/tcp                                                testcontainers-ryuk-f4f28fe0-5f4d-4932-b20c-ceef61735c8d
9e4f6520c5cc   postgres:17                                          "docker-entrypoint.s…"   About a minute ago   Up About a minute (healthy)   0.0.0.0:63265->5432/tcp                                                workshop-db-1

so now I have 2 postgres:17 instead of just 1 that could be reused.

anything I am missing? how come it did not reuse my compose db service?

@ozangunalp
Copy link
Contributor Author

ideally I would be able to ask dev service to add its own definition in the compose file, then commit this file in my project.

Yes, this is something that we've discussed doing in the future.


I tried running docker compose -f compose-devservices.yml up then mvn package

The discovery of a running compose project is done via the "project name" (see. https://quarkus.io/guides/compose-dev-services#compose-project-name)

When you run compose up -f .., the current directory name is chosen as project name, which I assume workshop-db in your case. But Quarkus uses a derivative of the application name, so it doesn't get discovered.

If you add a top-level name attribute to your compose file, it'll be used as the compose project name, and because Quarkus looks at the compose file, it knows the project name to look for.

Another option is to set the quarkus.compose.devservices.project-name to the project name that'll be assigned by the manual compose up command.

I wanted to have some usage and feedback before having a decision on this behavior, so any suggestion is welcome.

@vsevel
Copy link
Contributor

vsevel commented May 6, 2025

would you have examples of how to define kafka and apicurio as compose services, just like they would be run if they were regular dev services running with testcontainers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.