-
Notifications
You must be signed in to change notification settings - Fork 28
5. Containers
The framework also supports to deployment of third party components using container images.
First, we need to add an additional dependency in the pom.xml
file:
<dependency>
<groupId>io.quarkus.qe</groupId>
<artifactId>quarkus-test-containers</artifactId>
<scope>test</scope>
</dependency>
Now, we can deploy third parties for our scenario:
@QuarkusScenario
public class GreetingResourceIT {
private static final String CUSTOM_PROPERTY = "my.property";
@Container(image = "quay.io/bitnami/consul:1.9.3", expectedLog = "Synced node info", port = 8500)
static final DefaultService consul = new DefaultService();
@QuarkusApplication
static final RestService app = new RestService();
// ...
}
We can use properties that require external resources using the resource::
tag. For example: .withProperty("to.property", "resource::/file.yaml");
. This works in bare metal or OpenShift/Kubernetes.
The same works for secret resources: using the secret::
tag. For example: .withProperty("to.property", "secret::/file.yaml");
. For baremetal, there is no difference, but when deploying on OCP and Kubernetes, one secret will be pushed instead. This only works for file system resources (secrets from classpath are not supported).
If you want to delete the images after use, you need to provide the property ts.<YOUR SERVICE NAME>.container.delete.image.on.stop=true
or
ts.global.container.delete.image.on.stop=true
to apply this property to all the containers.
Remember that <YOUR SERVICE NAME>
matches with the field name of the service, for example, in the previous example would be consul
.
Add your container prefix name by adding this property "-Dts.global.docker-container-prefix" to your maven statements.
For example
mvn clean verify -Dts.global.docker-container-prefix=my_prefix
As a result of this configuration, all your docker containers will have a fixed prefix name "my_prefix".
CONTAINER ID IMAGE PORTS NAMES
e7a2ef3a3e84 quay.io/keycloak/keycloak:14.0.0 8443/tcp, 0.0.0.0:50138->8080/tcp, :::50138->8080/tcp my_prefix-2074505752
The main motivation that is behind this feature is the ability to create the concept of "namespaces" in docker. In this way, you will be able to share a docker server between several "CI" workers, and then after the job execution ends, clean the environment by running a script.
For example:
Stop&remove all 4a73c71664 containers:
docker stop $(docker ps -a | grep 4a73c71664 | awk '{print $1}')
docker rm $(docker ps -a | grep 4a73c71664 | awk '{print $1}')
Some containers require Privileged
mode to run properly. This mode can be enabled on a per-container basis via property ts.<YOUR SERVICE NAME>.container.privileged-mode=true
or for all containers via property ts.global.container.privileged-mode=true
. This property only affects containers which are both:
- Deployed on bare metal, not in Kubernetes/OpenShift.
- Use
@Container
annotation, not a specialised one(@KafkaContainer
,@AmqContainer
, etc).
For heavy containers such as DB2
could be possible to reuse the same container instance in several @QuarkusScenario
. This feature could be used via property ts.<YOUR SERVICE NAME>.container.reusable=true
. Please take a look at this example if you want to know more.
Due to the complexity of Kafka deployments, there is a special implementation of containers for Kafka that we can use by adding the dependency:
<dependency>
<groupId>io.quarkus.qe</groupId>
<artifactId>quarkus-test-service-kafka</artifactId>
<scope>test</scope>
</dependency>
And now, we can use the Kafka container in our test:
@QuarkusScenario
public class StrimziKafkaWithoutRegistryMessagingIT {
@KafkaContainer
static final KafkaService kafka = new KafkaService();
@QuarkusApplication
static final RestService app = new RestService()
.withProperty("kafka.bootstrap.servers", kafka::getBootstrapUrl);
// ...
}
By default, the KafkaContainer will use the Strimzi implementation.
Moreover, we can also configure our Kafka instance using a registry (Apicurio in Kafka Strimzi):
@QuarkusScenario
public class StrimziKafkaWithRegistryMessagingIT {
@KafkaContainer(withRegistry = true)
static final KafkaService kafka = new KafkaService();
@QuarkusApplication
static final RestService app = new RestService()
.withProperties("strimzi-application.properties")
.withProperty("kafka.bootstrap.servers", kafka::getBootstrapUrl)
.withProperty("strimzi.registry.url", kafka::getRegistryUrl);
// ...
}
We can override the registry configuration using the following properties from the @KafkaContainer
annotation:
-
registryImage
, this image follow the standard docker:version format asquay.io/apicurio/apicurio-registry-mem:2.0.0.Final
registryPath
This could be useful for some cases where the registry path has changed between Quarkus/Apicurio versions. For example:
- For Quarkus 1.13.7.Final:
@KafkaContainer(vendor = KafkaVendor.STRIMZI, withRegistry = true)
static final KafkaService kafka = new KafkaService();
- For Quarkus 2.x.Final:
@KafkaContainer(vendor = KafkaVendor.STRIMZI, withRegistry = true, registryPath = "/apis/registry/v2")
static KafkaService kafka = new KafkaService();
We can also use the Confluent implementation of kafka by doing:
@KafkaContainer(vendor = KafkaVendor.CONFLUENT)
Note that this implementation supports also registry, but not Kubernetes and OpenShift scenarios.
We can customise the Kafka deployment using a custom server.properties
and external files:
@KafkaContainer(serverProperties = "strimzi-custom-server-ssl.properties", kafkaConfigResources = { "strimzi-custom-server-ssl-keystore.p12"})
| Note that this only works for Strimzi kafka and on baremetal.
// Truststore must be placed on filesystem: https://github.com/quarkusio/quarkus/issues/8573
// So, we need to have:
// - a file "strimzi-server-ssl-truststore.p12" to match the defined in the default server.properties
// - using "top-secret" for the password to match the defined in the default server.properties
// - using "PKCS12" for the type to match the defined in the default server.properties
// If you want another setup, see the scenario `StrimziKafkaWithCustomSslMessagingIT`.
private static final String TRUSTSTORE_FILE = "strimzi-server-ssl-truststore.p12";
@KafkaContainer(vendor = KafkaVendor.STRIMZI, protocol = KafkaProtocol.SSL, kafkaConfigResources = TRUSTSTORE_FILE)
static final KafkaService kafka = new KafkaService();
@QuarkusApplication
static final RestService app = new RestService()
.withProperty("kafka.bootstrap.servers", kafka::getBootstrapUrl)
.withProperty("kafka.security.protocol", "SSL")
.withProperty("kafka.ssl.truststore.location", TRUSTSTORE_FILE)
.withProperty("kafka.ssl.truststore.password", "top-secret")
.withProperty("kafka.ssl.truststore.type", "PKCS12");
@Test
public void checkUserResourceByNormalUser() {
Awaitility.await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> {
app.given().get("/prices/poll")
.then()
.statusCode(HttpStatus.SC_OK);
});
}
| Note that this only works for Strimzi kafka and on baremetal.
private final static String SASL_USERNAME_VALUE = "client";
private final static String SASL_PASSWORD_VALUE = "client-secret";
@KafkaContainer(vendor = KafkaVendor.STRIMZI, protocol = KafkaProtocol.SASL)
static final KafkaService kafka = new KafkaService();
@QuarkusApplication
static final RestService app = new RestService()
.withProperty("kafka.bootstrap.servers", kafka::getBootstrapUrl)
.withProperty("kafka.security.protocol", "SASL_PLAINTEXT")
.withProperty("kafka.sasl.mechanism", "PLAIN")
.withProperty("kafka.sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule required "
+ "username=\"" + SASL_USERNAME_VALUE + "\" "
+ "password=\"" + SASL_PASSWORD_VALUE + "\";");
@Test
public void checkUserResourceByNormalUser() {
Awaitility.await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> {
app.given().get("/prices/poll")
.then()
.statusCode(HttpStatus.SC_OK);
});
}
| Note that this only works for Strimzi kafka and on baremetal.
Similar to Kafka, we have a default implementation of an AMQ container (Artemis vendor).
Required Maven dependency:
<dependency>
<groupId>io.quarkus.qe</groupId>
<artifactId>quarkus-test-service-amq</artifactId>
<scope>test</scope>
</dependency>
Example:
@QuarkusScenario
public class AmqIT {
@AmqContainer
static final AmqService amq = new AmqService();
@QuarkusApplication
static final RestService app = new RestService()
.withProperty("quarkus.artemis.username", amq.getAmqUser())
.withProperty("quarkus.artemis.password", amq.getAmqPassword())
.withProperty("quarkus.artemis.url", amq::getUrl);
We can specify a different image by setting @AmqContainer(image = XXX)
.
This container is compatible with OpenShift, but not with Kubernetes deployments.
Required Maven dependency:
<dependency>
<groupId>io.quarkus.qe</groupId>
<artifactId>quarkus-test-service-jaeger</artifactId>
<scope>test</scope>
</dependency>
Example with quarkus-smallrye-opentracing
extension and a Jaeger thrift collector:
@JaegerContainer
static final JaegerService jaeger = new JaegerService();
@QuarkusApplication
static final RestService app = new RestService().withProperty("quarkus.jaeger.endpoint", jaeger::getRestUrl);
This container is compatible with OpenShift, but not with Kubernetes deployments.
Example with quarkus-opentelemetry-exporter-otlp
extension over gRPC:
@JaegerContainer(useOtlpCollector = true)
static final JaegerService jaeger = new JaegerService();
@QuarkusApplication
static final RestService app = new RestService().withProperty("quarkus.opentelemetry.tracer.exporter.otlp.endpoint", jaeger::getCollectorUrl);