Java + Quarkus 2

Java Operator SDK(JOSDK) is an open source project that aims to simplify the task of creating Kubernetes operators using Java. Container Solutions started the project, and Red Hat is now a major contributor. The JOSDK project now lives under the Operator Framework umbrella, which is a Cloud Native Computing Foundation (CNCF) incubating project.

The first article in this series introduced JOSDK and explained why it could be interesting to create operators in Java. The second article showed how the JOSDK Quarkus extension quarkus-operator-sdk, also called QOSDK, facilitates the development experience by taking care of managing the Custom Resource Definition automatically. The third article focused on requirements for implementing the reconciliation logic for the example operator you build in this series. Many things have changed since the third installment of this series. This article will thus focus on updating the code to the latest versions and provide upgrading strategies.

Where things stand

You implemented a simple operator exposing your application outside the cluster via an Ingress, creating the associated Deployment and Service along the way. However, it has been a while since the last part of this blog series and many things have changed. When the third article was written, QOSDK was in version 3.0.4. Now it is up to 6.3.0. Quarkus has also been updated. How can you update your operator to use more recent versions, and what are possible strategies to update your code?

How to use Quarkus update

Upgrading a project is always a tricky proposition, especially when there is a wide gap between the old and new versions. Quarkus can help you with this task, though it might not work in all cases. In this case, you want to migrate from Quarkus 2.7.3.Final to the latest version, which at the time of writing this article, is 3.2.4.Final. You can use the update command that Quarkus provides. If you have the quarkus command line tool, you might want to upgrade it first and then simply run quarkus update.

Otherwise, using maven only, you can run:

mvn io.quarkus.platform:quarkus-maven-plugin:3.2.4.Final:update -N

The complete procedure is detailed in the related Quarkus guide.

In your case, you should notice that the update procedure fails with an error when the command attempts to check the updated project as follows:

[INFO] [ERROR] [ERROR] Some problems were encountered while processing the POMs:
[INFO] [ERROR] 'dependencies.dependency.version' for io.quarkiverse.operatorsdk:quarkus-operator-sdk-csv-generator:jar is missing. @ line 38, column 17

Updating outdated QOSDK dependency

The problem occurs because this dependency doesn’t exist anymore. Though the project actually doesn’t need this dependency at this point, it is included by default when bootstrapping a QOSDK project using the operator-sdk CLI and allows for automatic generation of Operator Lifecycle Manager (OLM) bundles. OLM enables you to manage the lifecycle of operators on clusters in a more principled way. We might discuss this feature in greater detail in a future article.

There are two ways to fix your project. If you’re not interested in the feature, you can remove the dependency, or change it to the correct one. This dependency doesn’t exist in its previous form anymore because it has been renamed to better reflect its expanded scope. It initially focused solely on the ClusterServiceVersion part of OLM bundles, but now extends to generating complete bundles. The feature was actually disabled using quarkus.operator-sdk.generate-csv=false in the application.properties file.

The new dependency name is quarkus-operator-sdk-bundle-generator. So use that if you want to use the OLM generation feature. Note that you will also need to change the associated property name to activate the feature. You’ll see a warning in the logs that the property doesn’t exist if you don’t, and the OLM generation will be activated by default. The new property is named quarkus.operator-sdk.bundle.enabled.

After making these changes, you can re-run the update command. It should now succeed, with an output similar to the following:

[INFO] Detected project Java version: 11
[INFO] Quarkus platform BOMs:
[INFO]         io.quarkus:quarkus-bom:pom:3.2.4.Final ✔
[INFO] Add:    io.quarkus.platform:quarkus-operator-sdk-bom:pom:3.2.4.Final
[INFO]
[INFO] Extensions from io.quarkus:quarkus-bom:
[INFO]         io.quarkus:quarkus-micrometer-registry-prometheus ✔
[INFO]
[INFO] Extensions from io.quarkus.platform:quarkus-operator-sdk-bom:
[INFO] Update: io.quarkiverse.operatorsdk:quarkus-operator-sdk-bundle-generator:6.3.0 -> remove version (managed)
[INFO] Update: io.quarkiverse.operatorsdk:quarkus-operator-sdk:6.3.0 -> remove version (managed)

Strategies for QOSDK and Quarkus updates

Now you can see that you can actually simplify things even further. It is advising you to add the io. quarkus.platform:quarkus-operator-sdk-bom:pom:3.2.4.Final dependency. Indeed, QOSDK has been added to the Quarkus platform, making it easier to consume from a given Quarkus version. Switching to this BOM only allows you to decide which version of Quarkus to use, and the BOM will make sure you get the appropriate QOSDK version.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.quarkiverse.operatorsdk</groupId>
            <artifactId>quarkus-operator-sdk-bom</artifactId>
            <version>${quarkus-sdk.version}</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

This project is currently using the QOSDK BOM with quarkus-sdk.version with the 3.0.4 value. You’ll also note that there is a quarkus.version property with the 2.7.3.Final value. Looking at the QOSDK BOM, you can see that there is also a Quarkus version property defined there, with the same quarkus.version name. Therefore, if you upgrade the QOSDK version, with the current setup, you need to make sure to also upgrade the Quarkus version in your project in such a way that is compatible with the version defined in the QOSDK BOM.

Using the QOSDK BOM defined by the Quarkus platform (i.e., Using the io.quarkus.platform:quarkus-operator-sdk-bom artifact instead of the io.quarkiverse.operatorsdk:quarkus-operator-sdk-bom, note the different group identifier.) simplifies this aspect by making sure that both QOSDK and Quarkus versions are aligned. The downside of this is that using the QOSDK BOM directly from the QOSDK project, you have the Quarkus BOM automatically included in your project. The price for this, as previously explained, is that you need to make sure the versions are in sync.

That said, you can also see that it is letting us know that there is a more recent version of the QOSDK extension (6. 3.0), which will only be available from the Quarkus platform starting with version 3.2.5.Final. Using the Quarkus platform, therefore, means that you’re not necessarily using the latest QOSDK version. This is, however, the version that is verified to work with the platform as a whole, so this is the more conservative option.

If you wish to use the absolute latest version of QOSDK, you should use the BOM provided by QOSDK, but you will need to make sure to update the Quarkus version using the quarkus.version, while updating the QOSDK version using the quarkus-sdk.version property in your pom.xml file as previously done.

Which approach to choose depends on your appetence for risk, or how you wish to manage your dependencies. Generally speaking, the Quarkus platform is updated frequently, and QOSDK versions are usually updated accordingly as needed. So the Quarkus platform is usually up-to-date when it comes to the latest QOSDK version. If you absolutely need the latest QOSDK version, upgrading from the Quarkus platform offerings, a patch or even a minor version should typically work with issues since QOSDK strives to maintain backwards compatibility between minor versions.

Going the opposite direction, upgrading Quarkus to a minor version above (e.g., from 3.2.x to 3.3.x) might prove tricky since the Fabric8 Kubernetes client version used by that new Quarkus version might also have been updated to a new minor version. This has been known to bring API changes, so you might want to tread carefully with such updates.

Actually, QOSDK issues debug-level warnings when it detects version mismatches (minor version and above, patch level mismatches considered safe) between Quarkus, JOSDK, and Fabric8 Kubernetes client. You can even configure it to fail a build by setting the quarkus.operator-sdk.fail-on-version-check to true. Please refer to the documentation for more details.

It’s also worth repeating that since QOSDK bundles JOSDK, you do not need to worry about updating that dependency separately. One less thing to worry about.

Adapting to Fabric8 Kubernetes client changes

Now that the dependencies are sorted out, if you try to build now, you should get a compilation error due to an API change in the Fabric8 Kubernetes client:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project expose: Compilation failure
[ERROR] exposedapp-rhdblog/src/main/java/io/halkyon/ExposedAppReconciler.java:[63,33] cannot find symbol
[ERROR]   symbol:   method withIntVal(int)
[ERROR]   location: interface io.fabric8.kubernetes.api.model.ServicePortFluent.TargetPortNested<io.fabric8.kubernetes.api.model.ServiceSpecFluent.PortsNested<io.fabric8.kubernetes.api.model.ServiceFluent.SpecNested<io.fabric8.kubernetes.api.model.ServiceBuilder>>>

This issue is easily fixed by changing the following line:

.withNewTargetPort().withIntVal(8080).endTargetPort()

to:

.withNewTargetPort(8080)

The Fabric8 Kubernetes client provides detailed notes for each release. It’s always a good idea to take a look at them, especially whenever a new minor version is released (here are the notes for the 6.8. 0 release, which does contain breaking changes). Another interesting resource is the cheat sheet, which contains a wealth of information on how to perform a wide variety of tasks using the client.

That said, you should now be all set for this batch of updates!

Summary

While less focused on writing operators per se, this article still covered an important part of any software development: upgrading dependencies. Your operator should be now ready for improvements, which we will tackle in the next article. We will also discuss adding status handling and how to make your operator react to events that are not targeting primary resources.

For reference, you can find the completed code for this part under the part-4 tag of the https://github.com/halkyonio/exposedapp-rhdblog repository.