Chapter 11. Developing an application for the Karaf image

This tutorial shows how to create and deploy an application for the Karaf image.

11.1. Creating a Karaf project using Maven archetype

To create a Karaf project using a Maven archetype, follow these steps.

Procedure

  1. Go to the appropriate directory on your system.
  2. Launch the Maven command to create a Karaf project

    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.0.fuse-770011-redhat-00003/archetypes-catalog-2.2.0.fuse-770011-redhat-00003-archetype-catalog.xml \
      -DarchetypeGroupId=org.jboss.fuse.fis.archetypes \
      -DarchetypeArtifactId=karaf-camel-log-archetype \
      -DarchetypeVersion=2.2.0.fuse-770011-redhat-00003
  3. The archetype plug-in switches to interactive mode to prompt you for the remaining fields

    Define value for property 'groupId': : org.example.fis
    Define value for property 'artifactId': : fuse77-karaf-camel-log
    Define value for property 'version':  1.0-SNAPSHOT: :
    Define value for property 'package':  org.example.fis: :
    Confirm properties configuration:
    groupId: org.example.fis
    artifactId: fuse77-karaf-camel-log
    version: 1.0-SNAPSHOT
    package: org.example.fis
     Y: : Y

    When prompted, enter org.example.fis for the groupId value and fuse77-karaf-camel-log for the artifactId value. Accept the defaults for the remaining fields.

  4. If the above command exited with the BUILD SUCCESS status, you should now have a new Fuse on OpenShift project under the fuse77-karaf-camel-log subdirectory.
  5. You are now ready to build and deploy the fuse77-karaf-camel-log project. Assuming you are still logged into OpenShift, change to the directory of the fuse77-karaf-camel-log project, and then build and deploy the project, as follows.

    cd fuse77-karaf-camel-log
    mvn fabric8:deploy -Popenshift
Note

For the full list of available Karaf archetypes, see Karaf Archetype Catalog.

11.2. Structure of the Camel Karaf application

The directory structure of a Camel Karaf application is as follows:

  ├── pom.xml 1
  ├── README.md
  ├── configuration
  │   └── settings.xml
  └── src
      ├── main
      │   ├── fabric8
      │   │   └── deployment.yml 2
      │   ├── java
      │   │   └── org
      │   │       └── example
      │   │           └── fis
      │   └── resources
      │       ├── assembly
      │       │   └── etc
      │       │       └── org.ops4j.pax.logging.cfg 3
      │       └── OSGI-INF
      │           └── blueprint
      │               └── camel-log.xml 4
      └── test
          └── java
              └── org
                  └── example
                      └── fis

Where the following files are important for developing a Karaf application:

1
pom.xml: Includes additional dependencies. You can add dependencies in the pom.xml file, for example for logging you can use SLF4J.
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
    </dependency>
2
src/main/fabric8/deployment.yml: Provides additional configuration that is merged with the default OpenShift configuration file generated by the fabric8-maven-plugin.
Note

This file is not used as part of the Karaf application, but it is used in all quickstarts to limit the resources such as CPU and memory usage.

3
org.ops4j.pax.logging.cfg: Demonstrates how to customize log levels, sets logging level to DEBUG instead of the default INFO.
4
camel-log.xml: Contains the source code of the application.

11.3. Karaf archetype catalog

The Karaf archetype catalog includes the following examples.

Table 11.1. Karaf Maven Archetypes

NameDescription

karaf-camel-amq-archetype

Demonstrates how to send and receive messages to an Apache ActiveMQ message broker, using the Camel amq component.

karaf-camel-log-archetype

Demonstrates a simple Apache Camel application that logs a message to the server log every 5th second.

karaf-camel-rest-sql-archetype

Demonstrates how to use SQL via JDBC along with Camel’s REST DSL to expose a RESTful API.

karaf-cxf-rest-archetype

Demonstrates how to create a RESTful(JAX-RS) web service using CXF and expose it through the OSGi HTTP Service.

11.4. Using Fabric8 Karaf features

Fabric8 provides support for Apache Karaf making it easier to develop OSGi apps for Kubernetes.

The important features of Fabric8 are as listed below:

  • Different strategies to resolve placeholders in Blueprint XML files.
  • Environment variables
  • System properties
  • Services
  • Kubernetes ConfigMap
  • Kubernetes Secrets
  • Using Kubernetes configuration maps to dynamically update the OSGi configuration administration.
  • Provides Kubernetes heath checks for OSGi services.

11.4.1. Adding Fabric8 Karaf features

To use the features, add fabric8-karaf-features dependency to the project POM file.

Procedure

  1. Open your project’s pom.xml file and add fabric8-karaf-features dependency.
<dependency>
  <groupId>io.fabric8</groupId>
  <artifactId>fabric8-karaf-features</artifactId>
  <version>${fabric8.version}</version>
  <classifier>features</classifier>
  <type>xml</type>
</dependency>

The fabric8 karaf features will be installed into the Karaf server.

11.4.2. Adding Fabric8 Karaf Core bundle functionality

The bundle fabric8-karaf-core provides the functionalities used by Blueprint and ConfigAdmin extensions.

Procedure

  1. Open your project’s pom.xml and add fabric8-karaf-core to startupFeatures section.

    <startupFeatures>
      ...
      <feature>fabric8-karaf-core</feature>
      ...
    </startupFeatures>

    This will add the fabric8-karaf-core feature in a custom Karaf distribution.

11.4.3. Setting the Property Placeholder service options

The bundle fabric8-karaf-core exports a service PlaceholderResolver with the following interface:

public interface PlaceholderResolver {
    /**
     * Resolve a placeholder using the strategy indicated by the prefix
     *
     * @param value the placeholder to resolve
     * @return the resolved value or null if not resolved
     */
    String resolve(String value);

    /**
     * Replaces all the occurrences of variables with their matching values from the resolver using the given source string as a template.
     *
     * @param source the string to replace in
     * @return the result of the replace operation
     */
    String replace(String value);

    /**
     * Replaces all the occurrences of variables within the given source builder with their matching values from the resolver.
     *
     * @param value the builder to replace in
     * @rerurn true if altered
     */
    boolean replaceIn(StringBuilder value);

    /**
     * Replaces all the occurrences of variables within the given dictionary
     *
     * @param dictionary the dictionary to replace in
     * @rerurn true if altered
     */
    boolean replaceAll(Dictionary<String, Object> dictionary);

    /**
     * Replaces all the occurrences of variables within the given dictionary
     *
     * @param dictionary the dictionary to replace in
     * @rerurn true if altered
     */
    boolean replaceAll(Map<String, Object> dictionary);
}

The PlaceholderResolver service acts as a collector for different property placeholder resolution strategies. The resolution strategies it provides by default are listed in the table Resolution Strategies. To set the property placeholder service options you can use system properties or environment variables or both.

Procedure

  1. To access ConfigMaps on OpenShift the service account needs view permissions. Add view permissions to the service account.

    oc policy add-role-to-user view system:serviceaccount:$(oc project -q):default -n $(oc project -q)
  2. Mount the secret to the Pod as access to secrets through API might be restricted.
  3. Secrets, available on the Pod as volume mounts, are mapped to a directory named as the secret, as shown below

    containers:
      -
       env:
       - name: FABRIC8_K8S_SECRETS_PATH
         value: /etc/secrets
         volumeMounts:
       - name: activemq-secret-volume
         mountPath: /etc/secrets/activemq
         readOnly: true
       - name: postgres-secret-volume
         mountPath: /etc/secrets/postgres
         readOnly: true
    
    volumes:
      - name: activemq-secret-volume
      secret:
      secretName: activemq
      - name: postgres-secret-volume
       secret:
      secretName: postgres

11.4.4. Adding a custom property placeholder resolver

You can add a custom placeholder resolver to support a specific need, such as custom encryption. You can also use the PlaceholderResolver service to make the resolvers available to Blueprint and ConfigAdmin.

Procedure

  1. Add the following mvn dependency to the project pom.xml.

    pom.xml

    ---
    <dependency>
      <groupId>io.fabric8</groupId>
      <artifactId>fabric8-karaf-core</artifactId>
    </dependency>
    ---

  2. Implement the PropertiesFunction interface and register it as OSGi service using SCR.

    import io.fabric8.karaf.core.properties.function.PropertiesFunction;
    import org.apache.felix.scr.annotations.Component;
    import org.apache.felix.scr.annotations.ConfigurationPolicy;
    import org.apache.felix.scr.annotations.Service;
    
    @Component(
        immediate = true,
        policy = ConfigurationPolicy.IGNORE,
        createPid = false
    )
    @Service(PropertiesFunction.class)
    public class MyPropertiesFunction implements PropertiesFunction {
        @Override
        public String getName() {
            return "myResolver";
        }
    
        @Override
        public String apply(String remainder) {
            // Parse and resolve remainder
            return remainder;
        }
    }
  3. You can reference the resolver in Configuration management as follows.

    properties

    my.property = $[myResolver:value-to-resolve]

11.4.5. List of resolution strategies

The PlaceholderResolver service acts as a collector for different property placeholder resolution strategies. The resolution strategies it provides by default are listed in the table.

  1. List of resolution strategies

Prefix

Example

Description

env

env:JAVA_HOME

look up the property from OS environment variables.

`sys

sys:java.version

look up the property from Java JVM system properties.

`service

service:amq

look up the property from OS environment variables using the service naming convention.

service.host

service.host:amq

look up the property from OS environment variables using the service naming convention returning the hostname part only.

service.port

service.port:amq

look up the property from OS environment variables using the service naming convention returning the port part only.

k8s:map

k8s:map:myMap/myKey

look up the property from a Kubernetes ConfigMap (via API)

k8s:secret

k8s:secret:amq/password

look up the property from a Kubernetes Secrets (via API or volume mounts)

11.4.6. List of Property Placeholder service options

The property placeholder service supports the following options:

  1. List of property placeholder service options
NameDefaultDescription

fabric8.placeholder.prefix

$[

The prefix for the placeholder

fabric8.placeholder.suffix

]

The suffix for the placeholder

fabric8.k8s.secrets.path

null

A comma delimited list of paths where secrets are mapped

fabric8.k8s.secrets.api.enabled

false

Enable/Disable consuming secrets via APIs

11.5. Adding Fabric8 Karaf Config admin support

11.5.1. Adding Fabric8 Karaf Config admin support

You can add Fabric8 Karaf Config admin support to your custom Karaf distribution.

Procedure

  • Open your project’s pom.xml and add fabric8-karaf-cm to startupFeatures section.

    pom.xml

    <startupFeatures>
      ...
      <feature>fabric8-karaf-cm</feature>
      ...
    </startupFeatures>

11.5.2. Adding ConfigMap injection

The fabric8-karaf-cm provides a ConfigAdmin bridge that inject ConfigMap values in Karaf’s ConfigAdmin.

Procedure

  1. To be added by the ConfigAdmin bridge, a ConfigMap has to be labeled with karaf.pid. The karaf.pid value corresponds to the pid of your component. For example,

    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: myconfig
      labels:
        karaf.pid: com.mycompany.bundle
    data:
      example.property.1: my property one
      example.property.2: my property two
  2. To define your configuration, you can use single property names. Individual properties work for most cases. It is same as the pid file in karaf/etc. For example,

    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: myconfig
      labels:
        karaf.pid: com.mycompany.bundle
    data:
      com.mycompany.bundle.cfg: |
        example.property.1: my property one
        example.property.2: my property two

11.5.3. Configuration plugin

The fabric8-karaf-cm provides a ConfigurationPlugin which resolves configuration property placeholders.

To enable property substitution with the fabric8-karaf-cm plug-in, you must set the Java property, fabric8.config.plugin.enabled to true. For example, you can set this property using the JAVA_OPTIONS environment variable in the Karaf image, as follows:

JAVA_OPTIONS=-Dfabric8.config.plugin.enabled=true

11.5.4. Config Property Placeholders

An example of configuration property placeholders is shown below.

my.service.cfg

    amq.usr = $[k8s:secret:$[env:ACTIVEMQ_SERVICE_NAME]/username]
    amq.pwd = $[k8s:secret:$[env:ACTIVEMQ_SERVICE_NAME]/password]
    amq.url = tcp://$[env+service:ACTIVEMQ_SERVICE_NAME]

my-service.xml

    <?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"
               xsi:schemaLocation="
                 http://www.osgi.org/xmlns/blueprint/v1.0.0
                 https://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 persistent-id="my.service" id="my.service" update-strategy="reload"/>

      <bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
         <property name="userName"  value="${amq.usr}"/>
         <property name="password"  value="${amq.pwd}"/>
         <property name="brokerURL" value="${amq.url}"/>
      </bean>
    </blueprint>

11.5.5. Fabric8 Karaf Config Admin options

Fabric8 Karaf Config Admin supports the following options.

NameDefaultDescription

fabric8.config.plugin.enabled

false

Enable ConfigurationPlugin

fabric8.cm.bridge.enabled

true

Enable ConfigAdmin bridge

fabric8.config.watch

true

Enable watching for ConfigMap changes

fabric8.config.merge

false

Enable merge ConfigMap values in ConfigAdmin

fabric8.config.meta

true

Enable injecting ConfigMap meta in ConfigAdmin bridge

fabric8.pid.label

karaf.pid

Define the label the ConfigAdmin bridge looks for (that is, a ConfigMap that needs to be selected must have that label; the value of which determines to what PID it gets associated)

fabric8.pid.filters

empty

Define additional conditions for the ConfigAdmin bridge to select a ConfigMap. The supported syntax is:

  • Conditions on different labels are separated by "," and are intended in AND between each other.
  • Inside a label, semicolons (;) are considered as OR and can be used as separators for conditions on the label value.

For example, a filter like -Dfabric8.pid.filters=appName=A;B,database.name=my.oracle.datasource translates to "give me all the ConfigMaps that have a label appName with values A or B and a label database.name equals to my.oracle.datasource".

Important

ConfigurationPlugin requires Aries Blueprint CM 1.0.9 or above.

11.6. Adding Fabric8 Karaf Blueprint support

The fabric8-karaf-blueprint uses Aries PropertyEvaluator and property placeholders resolvers from fabric8-karaf-core to resolve placeholders in your Blueprint XML file.

Procedure

  • To include the feature for Blueprint support in your custom Karaf distribution, add fabric8-karaf-blueprint to startupFeatures section in your project pom.xml.

    <startupFeatures>
      ...
      <feature>fabric8-karaf-blueprint</feature>
      ...
    </startupFeatures>

Example

The fabric8 evaluator supports chained evaluators, such as ${env+service:MY_ENV_VAR}. You need to resolve MY_ENV_VAR variable against environment variables. The result is then resolved using service function. For example,

<?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:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0"
           xsi:schemaLocation="
             http://www.osgi.org/xmlns/blueprint/v1.0.0
             https://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
             http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0
             http://aries.apache.org/schemas/blueprint-ext/blueprint-ext-1.3.xsd">

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

  <bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
     <property name="userName"  value="$[k8s:secret:$[env:ACTIVEMQ_SERVICE_NAME]/username]"/>
     <property name="password"  value="$[k8s:secret:$[env:ACTIVEMQ_SERVICE_NAME]/password]"/>
     <property name="brokerURL" value="tcp://$[env+service:ACTIVEMQ_SERVICE_NAME]"/>
  </bean>
</blueprint>
Important

Nested property placeholder substitution requires Aries Blueprint Core 1.7.0 or above.

11.7. Enabling Fabric8 Karaf health checks

It is recommended to install the fabric8-karaf-checks as a startup feature. Once enable, your Karaf server can expose http://0.0.0.0:8181/readiness-check and http://0.0.0.0:8181/health-check URLs which can be used by Kubernetes for readiness and liveness probes.

Note

These URLs will only respond with a HTTP 200 status code when the following is true:

  • OSGi Framework is started.
  • All OSGi bundles are started.
  • All boot features are installed.
  • All deployed BluePrint bundles are in the created state.
  • All deployed SCR bundles are in the active, registered or factory state.
  • All web bundles are deployed to the web server.
  • All created Camel contexts are in the started state.

Procedure

  1. Open you project’s pom.xml and add fabric8-karaf-checks feature in the startupFeatures section.

    pom.xml

    <startupFeatures>
      ...
      <feature>fabric8-karaf-checks</feature>
      ...
    </startupFeatures>

    The fabric8-maven-plugin:resources goal will detect if your using the fabric8-karaf-checks feature and automatically add the Kubernetes for readiness and liveness probes to your container’s configuration.

11.8. Adding custom health checks

You can provide additional custom heath checks to prevent the Karaf server from receiving user traffic before it is ready to process the requests. To enable custom health checks you need to implement the io.fabric8.karaf.checks.HealthChecker or io.fabric8.karaf.checks.ReadinessChecker interfaces and register those objects in the OSGi registry.

Procedure

  • Add the following mvn dependency to the project pom.xml file.

    pom.xml

    <dependency>
      <groupId>io.fabric8</groupId>
      <artifactId>fabric8-karaf-checks</artifactId>
    </dependency>

    Note

    The simplest way to create and registered an object in the OSGi registry is to use SCR.

Example

An example that performs a health check to make sure you have some free disk space, is shown below:

import io.fabric8.karaf.checks.*;
import org.apache.felix.scr.annotations.*;
import org.apache.commons.io.FileSystemUtils;
import java.util.Collections;
import java.util.List;

@Component(
    name = "example.DiskChecker",
    immediate = true,
    enabled = true,
    policy = ConfigurationPolicy.IGNORE,
    createPid = false
)
@Service({HealthChecker.class, ReadinessChecker.class})
public class DiskChecker implements HealthChecker, ReadinessChecker {

    public List<Check> getFailingReadinessChecks() {
        // lets just use the same checks for both readiness and health
        return getFailingHeathChecks();
    }

    public List<Check> getFailingHealthChecks() {
        long free = FileSystemUtils.freeSpaceKb("/");
        if (free < 1024 * 500) {
            return Collections.singletonList(new Check("disk-space-low", "Only " + free + "kb of disk space left."));
        }
        return Collections.emptyList();
    }
}