Deploying multiple Fuse Karaf bundles on OpenShift

Towards the end of last year we had a customer contact us with an requirement to migrate a Fuse service which consisted of 60+bundles to OpenShift in very a short time frame, so a minimal code change approach would need to be taken i.e. ‘lift and shift’ as much as possible.

OpenShift has all the required Maven plugins to construct an image with Karaf with any number of additional bundles included. The details are tucked away in Appendix B of the Fuse Integration Services 2.0 for OpenShift documentation.

You’ll need supported versions of Java (I had OpenJDK 1.8.0_256 already installed); Maven (3.3.9); OpenShift (3.11.286) and the OpenShift 3.11.286 Client (Red Hat subscription required – evaluation subscription available).

To resolve OpenShift service hostnames you need to be able to handle wildcard DNS although this wasn’t strictly necessary for this investigation. For Linux, dnsmasq appears to be the go to choice but for Windows the field seems a bit more open. Of the few I tried, Acrylic DNS Proxy was straightforward to install and configure but I’m sure there are many other good alternatives.

The AWS environment I was using didn’t have a static Elastic IP address, so each time the master and node instances were started the new external IP addresses needed to be updated in the Acrylic settings. While not ideal it was only a minor inconvenience.

Next I needed to add some Maven repositories to my .m2/setting.xml (See #get-started-prepare-dev-env) then I was ready to follow the steps in Chapter 8. Develop an Application for the Karaf Image.

Firstly connect to the OpenShift cluster …

oc login https://j1-master.openshift.local:8443

Start a new project…

oc new-project helloopenworld

Create a sample application …

mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeCatalog=https://maven.repository.redhat.com/ga/io/fabric8/archetypes/archetypes-catalog/2.2.195.redhat-000017/archetypes-catalog-2.2.195.redhat-000017-archetype-catalog.xml -DarchetypeGroupId=org.jboss.fuse.fis.archetypes -DarchetypeArtifactId=karaf2-camel-log-archetype -DarchetypeVersion=2.2.195.redhat-000017

And deploy it to OpenShift…

mvn fabric8:deploy

That’s all there was to it, no bother.

Next to add some existing bundles, update the startupBundles section in the pom …

<startupBundles>
    <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
    <bundle>mvn:com.estafet.fuse/hello/1.0.0-SNAPSHOT</bundle>
    <bundle>mvn:com.estafet.fuse/world/1.0.0-SNAPSHOT</bundle>
</startupBundles>

These two bundles are extremely simple. One has a timer route which calls a direct-vm route in the other to get a value (retrieved from a property) it should use in a log message. Deploying these changes successfully installed and activated these extra bundles.

Properties

There are a number of features described in Chapter 8. Develop an Application for the Karaf Image for properties – Fabric8 Karaf Core Bundle functionalities, Fabric8 Karaf Config Admin Support, Fabric8 Karaf Blueprint Support which suggest a syntax of $[k8s:map:myMap/myKey] can be used to reference properties defined in configMaps, however I was completely unable to get any of these to work.

What did work was creating the configMap with a karaf.pid metadata label where the value matched the <cm:property-placeholder> persistent-id in the blueprint (as described in Fabric8 Karaf Config Admin Support, but without any of the other required setup – no extra feature in the pom or Java option being set) and adding an <ext:property-placeholder> element with an evaluator value of “fabric8” with modified prefix and suffix values so it didn’t collide with the <cm:property-placeholder>.

oc create -f filename

kind: ConfigMap
apiVersion: v1
metadata:
  name: world
  labels:
 karaf.pid: com.estafet.fuse.world
data:
  name: bob

Lastly, the project needs read permission for configMaps …

oc policy add-role-to-user view system:serviceaccount:helloopenworld:default -n helloopenworld

So in summary the changes to the existing bundles are shown below …

Before:

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
  xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0
  http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
  http://camel.apache.org/schema/blueprint
  http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">

  <cm:property-placeholder id="blueprint.placeholder" persistent-id="com.estafet.fuse.world">
    <cm:default-properties>
      <cm:property name="name" value="world"/>
    </cm:default-properties>
  </cm:property-placeholder>

  <camelContext id="context-d0402f57-6e99-4b8f-940a-1fb862ad36a5" xmlns="http://camel.apache.org/schema/blueprint">
    <route id="world">
      <from uri="direct-vm:world"/>
      <setBody><constant>{{name}}</constant></setBody>
    </route>    
  </camelContext>
</blueprint>

After

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
  xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.5.0"
  xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0
  http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
  http://camel.apache.org/schema/blueprint
  http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">

  <cm:property-placeholder id="blueprint.placeholder" persistent-id="com.estafet.fuse.world">
    <cm:default-properties>
      <cm:property name="name" value="world"/>
    </cm:default-properties>
  </cm:property-placeholder>

  <ext:property-placeholder evaluator="fabric8" id="fabric8PropertyPlaceholder" placeholder-prefix="$[" placeholder-suffix="]"/>

  <camelContext id="context-d0402f57-6e99-4b8f-940a-1fb862ad36a5" xmlns="http://camel.apache.org/schema/blueprint">
    <route id="world">
      <from uri="direct-vm:world"/>
        <setBody><constant>{{name}}</constant></setBody>
    </route>
  </camelContext>
</blueprint>

Ultimate the customer decided not to proceed, running Fuse inside OpenShift would only have been a temporary step to a more major refactoring exercise, but it was interesting to see that is was possible.