PostgreSQL as Database

So before we deploy the code we need a Database right? For the sake of simplicity let’s deploy PostgreSQL using some simple commands. Maybe in your real deployment you would use an external instance.

Deploying PostgreSQL on OpenShift

Before proceeding log in your cluster using oc login with a normal user no need for special permissions.

In order to run our application, we need a namespace, let’s create one:

export PROJECT_NAME=%USERNAME%-fruit-service-postgresql
oc new-project ${PROJECT_NAME}

In order for the application to work properly we must first deploy a PostgreSQL database, like this:

oc new-app -e POSTGRESQL_USER=luke -e POSTGRESQL_PASSWORD=secret -e POSTGRESQL_DATABASE=FRUITSDB \
  centos/postgresql-10-centos7 --as-deployment-config=true --name=postgresql-db -n ${PROJECT_NAME}

Now let’s add some labels to the database deployment object:

oc label dc/postgresql-db app.kubernetes.io/part-of=fruit-service-app -n ${PROJECT_NAME} && \
  oc label dc/postgresql-db app.openshift.io/runtime=postgresql --overwrite=true -n ${PROJECT_NAME}

Check the database is running:

oc get pod -n ${PROJECT_NAME}

You should see something like this:

NAME                        READY   STATUS      RESTARTS   AGE
postgresql-db-1-deploy      0/1     Completed   0          2d6h
postgresql-db-1-n585q       1/1     Running     0          2d6h

You can also run this command to check the name of the POD:

oc get pods -n ${PROJECT_NAME} -o json | jq -r '.items[] | select(.status.phase | test("Running")) | select(.metadata.name | test("postgresql-db")).metadata.name'

Deploying the code on OpenShift

mvn clean package -Dquarkus.kubernetes.deploy=true -DskipTests -Dquarkus.profile=prod-postgresql

Move to the OpenShift web console and from Topology in the Developer perspective click on the route link as in the picture.

Fruit Service on PostgreSQL Topology

You should see this.

Fruit Service on PostgreSQL

Extending the inner-loop with Telepresence

Permissions needed

This needs to be run by a cluster-admin, but once you run it go back to the normal user.

oc adm policy add-scc-to-user privileged -z default -n ${PROJECT_NAME} && \
oc adm policy add-scc-to-user anyuid -z default -n ${PROJECT_NAME}
Telepresence will modify the network so that Services in Kubernetes are reachable from your laptop and vice versa.

The next command will result in the deployment for our application being scaled down to zero and the network altered so that traffic to it ends up in your laptop in port 8080.

Fruit Service on PostgreSQL Topology - Quarkus with Telepresence
Figure 1. Deployment scaled down to zero while using Telepresence
You’ll be asked for sudo password, this is normal, telepresence needs to be able to modify networking rules so that you can see Kubernetes Services as local.
export TELEPRESENCE_USE_OCP_IMAGE=NO
oc project ${PROJECT_NAME}
telepresence --swap-deployment atomic-fruit-service --expose 8080

Eventually you’ll se something like this:

...
T: Forwarding remote port 8080 to local port 8080.

T: Guessing that Services IP range is ['172.30.0.0/16']. Services started after this point will be inaccessible if are outside this range; restart telepresence
T: if you can't access a new Service.
T: Connected. Flushing DNS cache.
T: Setup complete. Launching your command.

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
@fruit-service-postgresql-dev/api-cluster-6d30-6d30-example-opentlc-com:6443/user1|bash-3.2$

In the Topology view you should see this.

Our deployment has been scaled down to zero and substituted by a pod generated by Telepresence.
Fruit Service on PostgreSQL Topology - Quarkus with Telepresence

Run from another terminal:

curl http://postgresql-db:5432

You should receive which looks bad but it’s actually good, this means that the DNS Service name local to Kubernetes can be resolved from your computer and that the port 5432 has been reached!

curl: (52) Empty reply from server`

Now let’s run our code locally but connected to the database (and/or other consumed services). To do so run this command from another terminal:

export PROJECT_NAME=%USERNAME%-fruit-service-postgresql
DB_USER=luke DB_PASSWORD=secret mvn quarkus:dev -Dquarkus.profile=prod-postgresql

Now open a browser and point to http://localhost:8080

You can edit, save, delete to test the functionalities implemented by FruitResource and debug locally.

You should see this:

You can also use the external route and get the same result.
Fruit Service on PostgreSQL - Quarkus with Telepresence

Now you can go to the terminal running the code locally and stop the process with Ctrl+C.

Also go to the terminal window where Telepresence is running locally and type exit, you should see something similar to this:

...
@fruit-service-postgresql-dev/api-cluster-6d30-6d30-example-opentlc-com:6443/user1|bash-3.2$ exit
exit
T: Your process has exited.
T: Exit cleanup in progress
T: Cleaning up Pod

Extending the inner-loop with Quarkus remote development

If you use Quarkus there’s another method to extend the inner-loop to your cluster, it’s called Remote Development Mode. The idea is simple, run your code in the cluster while being able to replace classes/files that you change locally in your IDE automatically.

There are two parts in this setup:

  • The server side, where the code runs in a container

  • The client side, where you connect to the server side and files are watched and uploaded whenever changes are detected

A couple of things to take into account regarding the server side:

  1. The normal JAR is substituted by a mutable or fast JAR, that can be updated/reloaded live

  2. An environment variable QUARKUS_LAUNCH_DEVMODE needs to be set to true

In order to use remote development we need to update the appllication.properties file, please add these two lines at the top of the file.

  1. We only need these properties while using remote development and you should comment them once you’re done.

  2. You can (or even should) update the live-reload password as it’s checked while connecting to the server side of the remote development set up.

# REMOTE DEV
quarkus.package.type=mutable-jar
quarkus.live-reload.password=changeit2

Now, delete the current deployment, then deploy it again but this time with remote development mode enabled. Please, run the following commands in order to do so:

Pay close attention to these system properties we’re setting to deploy our code in remote development mode:

  • -Dquarkus.kubernetes.deploy=true: deploys to target openshift

  • -Dquarkus.profile=prod-postgresql: activates this profile

  • -Dquarkus.container-image.build=true: builds the image using S2I as target is openshift

  • -Dquarkus.openshift.env-vars.quarkus-launch-devmode.value=true: this property commands Quarkus plugin to add an environment variable called QUARKUS_LAUNCH_DEVMODE with value true to the DeploymentConfig object

  • -Dquarkus.openshift.env-vars.quarkus-profile.value=prod-postgresql: this property commands Quarkus plugin to add an environment variable called QUARKUS_PROFILE with value prod-postgresql to the DeploymentConfig object

oc delete dc/atomic-fruit-service -n ${PROJECT_NAME}
mvn clean package -DskipTests -Dquarkus.kubernetes.deploy=true -Dquarkus.profile=prod-postgresql \
  -Dquarkus.container-image.build=true \
  -Dquarkus.openshift.env-vars.quarkus-launch-devmode.value=true \
  -Dquarkus.openshift.env-vars.quarkus-profile.value=prod-postgresql

You should see this:

...
[INFO] [io.quarkus.container.image.openshift.deployment.OpenshiftProcessor] Successfully pushed image-registry.openshift-image-registry.svc:5000/fruit-service-postgresql-dev/atomic-fruit-service@sha256:13c3781e82fa20b664bcc9e3c21a7a51d57c7a140c8232da66aa03ba73ff9b69
[INFO] [io.quarkus.container.image.openshift.deployment.OpenshiftProcessor] Push successful
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] Deploying to openshift server: https://api.cluster-6d30.6d30.example.opentlc.com:6443/ in namespace: fruit-service-postgresql-dev.
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] Applied: Secret fruits-db.
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] Applied: Service atomic-fruit-service.
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] Applied: ImageStream atomic-fruit-service.
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] Applied: ImageStream openjdk-11.
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] Applied: BuildConfig atomic-fruit-service.
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] Applied: DeploymentConfig atomic-fruit-service.
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] Applied: Route atomic-fruit-service.
[INFO] [io.quarkus.kubernetes.deployment.KubernetesDeployer] The deployed application can be accessed at: http://atomic-fruit-service-fruit-service-postgresql-dev.apps.cluster-6d30.6d30.example.opentlc.com
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 96673ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:43 min
[INFO] Finished at: 2021-02-09T13:51:33+01:00
[INFO] ------------------------------------------------------------------------

Let’s check that the deployment is setup correctly for remote development. Be patient here, give it some seconds before you see something, you may even need to run it again.

Dont forget to stop the command by typing Ctrl+C
oc logs -f dc/atomic-fruit-service | grep -i profile
The important thing is that profile prod-postgresql and live coding have been activated.

Eventually you’ll see this. It just takes some seconds, be patient.

...  [io.quarkus] (Quarkus Main Thread) Profile prod-postgresql activated. Live Coding activated.

We need the external route to our service, we’ll save the value in ROUTE_URL to use it later.

export ROUTE_URL="http://$(oc get route/atomic-fruit-service -o jsonpath='{.spec.host}')"

Now we have to connect to our application running in remote dev mode, we’ll do it like this.

./mvnw quarkus:remote-dev \
  -Dquarkus.live-reload.url=${ROUTE_URL} \
  -Dquarkus.profile=prod-postgresql

You should see something similar to this:

  1. Make sure you see Connected to remote server at the end of the log.

  2. You’ll find interesting that the jar sent while in remote mode is quarkus-run.jar, the mutable-jar. This fast jar allows sending only those parts that have change and reload them live.

[INFO] Scanning for projects...
[INFO]
[INFO] ------------< com.redhat.atomic.fruit:atomic-fruit-service >------------
[INFO] Building atomic-fruit-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:1.10.5.Final:remote-dev (default-cli) @ atomic-fruit-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 5 resources
[INFO] Nothing to compile - all classes are up to date
Listening for transport dt_socket at address: 5005
2021-02-10 11:38:53,921 INFO  [org.jbo.threads] (main) JBoss Threads version 3.1.1.Final
2021-02-10 11:38:54,138 INFO  [io.qua.kub.dep.KubernetesDeployer] (build-11) Only the first deployment target (which is 'openshift') selected via "quarkus.kubernetes.deployment-target" will be deployed
2021-02-10 11:38:54,329 INFO  [org.hib.Version] (build-10) HHH000412: Hibernate ORM core version 5.4.26.Final
2021-02-10 11:38:54,400 WARN  [io.qua.arc.pro.BeanArchives] (build-5) Failed to index boolean: Class does not exist in ClassLoader QuarkusClassLoader:Deployment Class Loader
[INFO] Checking for existing resources in: /Users/cvicensa/Projects/openshift/redhat-scholars/java-inner-loop-dev-guide/apps/quarkus-app/src/main/kubernetes.
[INFO] Adding existing Secret with name: fruits-db.
2021-02-10 11:38:56,848 INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 3265ms
2021-02-10 11:38:59,792 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Sending lib/deployment/appmodel.dat
2021-02-10 11:38:59,867 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Sending quarkus-run.jar
2021-02-10 11:38:59,952 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Sending app/atomic-fruit-service-1.0-SNAPSHOT.jar
2021-02-10 11:38:59,989 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Sending lib/deployment/build-system.properties
2021-02-10 11:39:00,030 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Connected to remote server

If you go to the web console you should see this.

Fruit Service on PostgreSQL Topology - Quarkus with Remote Dev

Let’s give it a try, shall we?

Open ${ROUTE_URL} in a browser, you should see this.

Fruit Service on PostgreSQL - Quarkus with Remote Dev

Now go to the editor find src/main/resources/templates/index.html and replace this:

<h1>CRUD Mission - Quarkus</h1>

With this:

<h1>CRUD Mission - Quarkus Remote</h1>

Changes are automatically detected and sent to the remote application. Check the terminal, you should see these new lines:

2021-02-10 12:21:45,266 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (Remote dev client thread) File change detected: /Users/cvicensa/Projects/openshift/redhat-scholars/java-inner-loop-dev-guide/apps/quarkus-app/src/main/resources/templates/index.html
2021-02-10 12:21:45,678 INFO  [io.qua.kub.dep.KubernetesDeployer] (build-17) Only the first deployment target (which is 'openshift') selected via "quarkus.kubernetes.deployment-target" will be deployed
2021-02-10 12:21:47,092 INFO  [io.qua.dep.QuarkusAugmentor] (Remote dev client thread) Quarkus augmentation completed in 1769ms
2021-02-10 12:21:47,106 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (Remote dev client thread) Hot replace total time: 1.852s
2021-02-10 12:21:48,896 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Sending dev/app/templates/index.html
2021-02-10 12:21:48,964 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Sending quarkus-run.jar
2021-02-10 12:21:49,006 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Sending app/atomic-fruit-service-1.0-SNAPSHOT.jar
2021-02-10 12:21:49,044 INFO  [io.qua.ver.htt.dep.dev.HttpRemoteDevClient] (Remote dev client thread) Sending lib/deployment/build-system.properties

Go back to the UI of our application and refresh change

Fruit Service on PostgreSQL New - Quarkus with Remote Dev
Don’t forget that whenever you change a file locally the app is rebuilt and relevant elements and uploaded and refreshed
Once you’re done with the remote development mode don’t forget to comment the lines we added before, as in the next exceprt.
# REMOTE DEV
#quarkus.package.type=mutable-jar
#quarkus.live-reload.password=changeit2

Binary deploy S2I

Finally, imagine that after debugging your code locally you want to redeploy on OpenShift in a similar way but without using the OpenShift extension. Well, this is possible by leveraging Source to Image (S2I) let’s have a look to the BuildConfigs in our project.

Once you have deployed your code using the OpenShift plugin one BuildConfig has been created for you (atomic-fruit-service in this case). But if you had started from the scratch you could have created that BuildConfig by hand and use the technique we’re explaining here.

So first of all let’s get the BCs (BuildConfigs) in our namespace:

oc get bc -n ${PROJECT_NAME}

You should get this:

NAME                   TYPE     FROM     LATEST
atomic-fruit-service   Source   Binary   1

Let’s read some details:

oc get bc/atomic-fruit-service -o yaml -n ${PROJECT_NAME}

We have copied the relevants parts below to focus on the important part of the BC YAML.

Focus on spec→source→typeBinary this means that in order to build image spec→output→to→name you need to provide a binary file, a JAR file in this case.
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
  labels:
    app.kubernetes.io/name: atomic-fruit-service
    app.kubernetes.io/part-of: fruit-service-app
    app.kubernetes.io/version: 1.0-SNAPSHOT
    app.openshift.io/runtime: quarkus
    department: fruity-dept
  name: atomic-fruit-service
  namespace: fruit-service-postgresql-dev
  ...
spec:
  nodeSelector: null
  output:
    to:
      kind: ImageStreamTag
      name: atomic-fruit-service:1.0-SNAPSHOT
  postCommit: {}
  resources: {}
  runPolicy: Serial
  source:
    binary: {}
    type: Binary
  strategy:
    sourceStrategy:
      from:
        kind: ImageStreamTag
        name: openjdk-11:latest
    type: Source
status:
...

Let’s package our application with the right profile and build an image with it:

mvn clean package -DskipTests -Dquarkus.kubernetes.deploy=false -Dquarkus.profile=prod-postgresql

After a successful build, let’s start the build of the image in OpenShift:

We have to include target/atomic-fruit-service-1.0-SNAPSHOT-runner.jar and the contents of target/lib/ in a zip file and start a new build with it.
zip atomic-fruit-service.zip target/lib/* target/atomic-fruit-service-1.0-SNAPSHOT-runner.jar
oc start-build atomic-fruit-service --from-archive=./atomic-fruit-service.zip -n ${PROJECT_NAME}
rm atomic-fruit-service.zip

As you can see in the output the JAR file is uploaded and a new Build is started.

Uploading archive file "atomic-fruit-service.zip" as binary input for the build ...

Uploading finished
build.build.openshift.io/fruit-service-3 started

And let’s have a look to the logs while the build is happening:

oc logs -f bc/atomic-fruit-service -n ${PROJECT_NAME}

Log output from the current build (only relevant lines):

It all starts with Receiving source from STDIN as archive so the image is built from a binary file within OpenShift as we checked out before.
Receiving source from STDIN as archive ...
Caching blobs under "/var/cache/blobs".
...
Storing signatures
Generating dockerfile with builder image registry.access.redhat.com/ubi8/openjdk-11@sha256:7921ba01d91c5598595dac9a9216e45cd6b175c22d7d859748304067d2097fae
STEP 1: FROM registry.access.redhat.com/ubi8/openjdk-11@sha256:7921ba01d91c5598595dac9a9216e45cd6b175c22d7d859748304067d2097fae
STEP 2: LABEL "io.openshift.build.image"="registry.access.redhat.com/ubi8/openjdk-11@sha256:7921ba01d91c5598595dac9a9216e45cd6b175c22d7d859748304067d2097fae"       "io.openshift.build.source-location"="/tmp/build/inputs"       "io.openshift.s2i.destination"="/tmp"
STEP 3: ENV OPENSHIFT_BUILD_NAME="atomic-fruit-service-4"     OPENSHIFT_BUILD_NAMESPACE="fruit-service-postgresql-dev"
STEP 4: USER root
STEP 5: COPY upload/src /tmp/src
STEP 6: RUN chown -R 185:0 /tmp/src
STEP 7: USER 185
STEP 8: RUN /usr/local/s2i/assemble
INFO S2I source build with plain binaries detected
INFO Copying binaries from /tmp/src to /deployments ...
target/
target/atomic-fruit-service-1.0-SNAPSHOT-runner.jar
target/lib/
target/lib/antlr.antlr-2.7.7.jar
...
STEP 9: CMD /usr/local/s2i/run
STEP 10: COMMIT temp.builder.openshift.io/fruit-service-postgresql-dev/atomic-fruit-service-4:31d1775e
Getting image source signatures
...
Writing manifest to image destination
Storing signatures
--> cd6e79768eb
cd6e79768ebfd82c7617a115f558e16e9a8bd3a2eeb8be9ae605c8e48ef9b75f

Pushing image image-registry.openshift-image-registry.svc:5000/fruit-service-postgresql-dev/atomic-fruit-service:1.0-SNAPSHOT ...
Getting image source signatures
...
Writing manifest to image destination
Storing signatures
Successfully pushed image-registry.openshift-image-registry.svc:5000/fruit-service-postgresql-dev/atomic-fruit-service@sha256:497f0886ac1d9c5610ec20ea4f899e9617fb549eda28342905ff6369c5af4b2d
Push successful

You can now test the new image as we have done before!