This is a workshop to develop a Quarkus example with persistence (Panache) involved, containerizing the app and deploying to Kubernetes cluster.
-
git
-
Java >11
-
Kubernetes cluster (in this case Developer Sandbox https://developers.redhat.com/developer-sandbox is used)
Clone current repository:
git clone https://github.com/redhat-developer-demos/quarkus-db-kubernetes.gitAnd enter to the project repository.
cd quarkus-db-kubernetesFinally run the following command to download all dependencies:
./mvnw clean installMost dependencies required for the tutorial are already registered in the pom.xml file, so after the above call most of them are downloaded and available locally.
As noted before dependencies are already placed in the pom.xml file, but in case of a new project you should add them:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
<scope>test</scope>
</dependency>Moreover, since we don’t want to develop a UI, Swagger UI dependency is registered:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>Let’s create a new entity class that represents a developer:
package org.acme;
import javax.persistence.Column;
import javax.persistence.Entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
@Entity
public class Developer extends PanacheEntity { // (1)
@Column public String name;
@Column public int age;
}-
Panache framework is used to implement Active Record pattern
Open DeveloperResource.java and add the following methods to implement developer operations:
@GET
@Path("/{id}")
public Optional<Developer> findById(@PathParam("id") Long id) {
return Developer.findByIdOptional(id); // (1)
}
@GET
public List<Developer> findByName(@QueryParam("name") String name) {
if (misbehave) {
throw new IllegalArgumentException("This service is misbehaving");
}
if (sleep) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if (name == null) {
return Developer.listAll(); // (2)
}
return Developer.list("name", name); // (3)
}
@POST
@Transactional
public Developer create(Developer developer) {
developer.persist(); // (4)
return developer;
}-
Find developer by id
-
List all developers
-
List developer by name
-
Create a new developer
Finally, update the configuration file located at src/main/resources/application.properties with the datasource information:
# (1)
quarkus.swagger-ui.always-include=true
# (2)
quarkus.datasource.db-kind=postgresql
# (3)
%dev.quarkus.hibernate-orm.database.generation=update
%dev.quarkus.hibernate-orm.log.sql=true
# (4)
%test.quarkus.datasource.db-kind=h2
%test.quarkus.hibernate-orm.database.generation=drop-and-create
%test.quarkus.hibernate-orm.log.sql=true-
Adds Swagger UI
-
By default uses PostgreSQL driver
-
At dev mode, Hibernate schema generation property is set to update
-
At test mode, H2 driver is used
Test is an important part too, open src/test/java/org/acme/DeveloperResourceTest.java and update the tests:
@Order(1)
@Test
public void testInsertDeveloper() {
Developer d = new Developer();
d.name = "Alex";
d.age = 41;
given()
.contentType(ContentType.JSON)
.body(d)
.when()
.post("/developer")
.then()
.statusCode(200);
}
@Order(2)
@Test
public void testListDevelopers() {
given()
.when().get("/developer")
.then()
.statusCode(200)
.assertThat()
.body("name", hasItems("Alex"));
}After all these changes, you can start the application in dev mode to be able to interact with the application and at the same time do changes at the code which are automatically reflected to the running instance.
In a terminal window run the following command:
./mvnw compile quarkus:devThen open a browser and navigate to http://localhost:8080/q/swagger-ui
There you can play with Swagger UI to find and insert new developers.
image
Let’s see live reloding in action, open org/acme/Developer.java class and add a new field:
@Column public String favouriteLanguage;Then without doing anything else, just refresh the browser and the change is automatically reflected.
Now it’s time to deploy this application to Kubernetes.
As noted before dependencies are already placed in the pom.xml file, but in case of a new project you should add them:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-jib</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway</artifactId>
</dependency>We need to create a container and push it to a Docker registry.
To do it we added the quarkus-container-image-jib dependency so the image is automatically created and pushed.
Then since we are at production, we’ll use Flyway to create the database schema with a SQL file instead of relying to the automatic generation provided by Hibernate which might not be the best one for production environments.
Finally, as the quarkus-kubernetes extension was provided, deployment of the service to the configured Kubernetes cluster is just calling a command.
|
Important
|
You need to have a Kubernetes cluster configured in the same terminal as you are running Maven (i.e oc login in case of OpenShift).
|
# (1)
quarkus.container-image.group=lordofthejars
quarkus.container-image.registry=quay.io
# (2)
%prod.quarkus.datasource.username=admin
%prod.quarkus.datasource.password=admin
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://postgresql:5432/db
%prod.quarkus.flyway.migrate-at-start=true
# (3)
quarkus.kubernetes.expose=true
quarkus.kubernetes.service-type=load-balancer-
Jib configuration (artifact and tag are get from
pom.xml) -
DataSource configuration for production profile
-
Kubernetes configuration (Ingress and service as LoadBalancer)
|
Important
|
Jib extension uses docker login information to push the image. Run docker login command in a terminal if you haven’t done yet.
|
Create a new SQL file to be imported by Flyway at src/main/resources/db/migration/V1.0.0__Quarkus.sql:
create table Developer (id int8 not null, age int4, name varchar(255), primary key (id));
create sequence hibernate_sequence start 1 increment 1;Instead of setting the PostgreSQL login & password as a configuration value, we could use a Kubernetes secrets for such.
When creating a PostgreSQL instance in OpenShift using templates, a Kubernetes Secret is automatically created with such information.
The only thing we need to do is to configure the application so that generated Kubernetes resource to deploy the application injects secret values as environment variables:
First of all remove the following entries:
%prod.quarkus.datasource.username=admin
%prod.quarkus.datasource.password=admin
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://postgresql:5432/dbAnd then use Kubernetes properties to add the secrets injecction:
# (1)
quarkus.kubernetes.env.secrets=postgresql-ephemeral-parameters-9hqkg
# (2)
quarkus.kubernetes.env.mapping.database-name.from-secret=postgresql-ephemeral-parameters-9hqkg
# (3)
quarkus.kubernetes.env.mapping.database-name.with-key=POSTGRESQL_DATABASE
quarkus.kubernetes.env.mapping.database-user.from-secret=postgresql-ephemeral-parameters-9hqkg
quarkus.kubernetes.env.mapping.database-user.with-key=POSTGRESQL_USER
quarkus.kubernetes.env.mapping.database-password.from-secret=postgresql-ephemeral-parameters-9hqkg
quarkus.kubernetes.env.mapping.database-password.with-key=POSTGRESQL_PASSWORD
# (4)
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://postgresql:5432/${database-name}
%prod.quarkus.datasource.username=${database-user}
%prod.quarkus.datasource.password=${database-password}-
Configures the Kubernetes secret name (
postgresql-ephemeral-parameters-9hqkg) -
Gets Database name
-
Injects Database name in
POSTGRESQL_DATABASEenv var -
Injects Database name in the jdbc configuration property
To create the container, push it to configured registry and deploy it to Kubernetes cluster, run the following command:
./mvnw clean package -DskipTests -Dquarkus.kubernetes.deploy=true|
Tip
|
If you are interested on inspecting the deployment file you can check it at target/kubernetes/kubernetes.yaml directory.
|
So far, PostgreSQL credentials are loaded from a Kubernetes Secret. There is nothing wrong with that but injecting secrets as environment variables or volumes might result in a security issues if the Pod is compromised.
To avoid this problem, Quarkus allows you to inject directly the Kubernetes Secrets into memory without having to map them as environment variable nor a volume by using Kubernetes Config extension.
Regsiter the following extension in pom.xml file.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-config</artifactId>
</dependency>Removes the following properties from application.properties as they are not needed anymore as we are not going to genereate deployment files with secrets:
quarkus.kubernetes.env.secrets=postgresql-ephemeral-parameters-9hqkg
quarkus.kubernetes.env.mapping.database-name.from-secret=postgresql-ephemeral-parameters-9hqkg
quarkus.kubernetes.env.mapping.database-name.with-key=POSTGRESQL_DATABASE
quarkus.kubernetes.env.mapping.database-user.from-secret=postgresql-ephemeral-parameters-9hqkg
quarkus.kubernetes.env.mapping.database-user.with-key=POSTGRESQL_USER
quarkus.kubernetes.env.mapping.database-password.from-secret=postgresql-ephemeral-parameters-9hqkg
quarkus.kubernetes.env.mapping.database-password.with-key=POSTGRESQL_PASSWORD
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://postgresql:5432/${database-name}
%prod.quarkus.datasource.username=${database-user}
%prod.quarkus.datasource.password=${database-password}And add the following properties:
# (1)
%prod.quarkus.kubernetes-config.secrets.enabled=true
# (2)
%prod.quarkus.kubernetes-config.secrets=postgresql-ephemeral-parameters-9hqkg
# (3)
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://postgresql:5432/${POSTGRESQL_DATABASE}
%prod.quarkus.datasource.username=${POSTGRESQL_USER}
%prod.quarkus.datasource.password=${POSTGRESQL_PASSWORD}-
Enables injection of Kubernetes Secrets in memory
-
Sets the Kubernetes Secrets to read
-
Kubernetes Secrets Data can be injected directly as a property