Chapter 9. Develop an Application for the Karaf Image

9.1. Create a Karaf Project using Maven Archetype

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

  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-720018-redhat-00001/archetypes-catalog-2.2.0.fuse-720018-redhat-00001-archetype-catalog.xml \
      -DarchetypeGroupId=org.jboss.fuse.fis.archetypes \
      -DarchetypeArtifactId=karaf-camel-log-archetype \
      -DarchetypeVersion=2.2.0.fuse-720018-redhat-00001
  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': : fuse72-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: fuse72-karaf-camel-log
    version: 1.0-SNAPSHOT
    package: org.example.fis
     Y: :

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

Then, follow the instructions in the quickstart on how to build and deploy the example.

Note

For the full list of available Karaf archetypes, see Section 9.3, “Karaf Archetype Catalog”.

9.2. Structure of the Camel Karaf Application

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

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

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

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>
org.ops4j.pax.logging.cfg
Demonstrates how to customize log levels, sets logging level to DEBUG instead of the default INFO.
camel-log.xml
Contains the source code of the application.
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.

9.3. Karaf Archetype Catalog

The Karaf archetype catalog includes the following examples.

Table 9.1. Karaf Maven Archetypes

NameDescription

karaf-camel-amq-archetype

Demonstrates how to send and recieve 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.

9.4. 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.

9.4.1. Adding Fabric8 Karaf Features

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

<dependency>
  <groupId>io.fabric8</groupId>
  <artifactId>fabric8-karaf-features</artifactId>
  <version>${fabric8.version}</version>
  <classifier>features</classifier>
  <type>xml</type>
</dependency>

These features will be installed into the Karaf server.

9.4.2. Fabric8 Karaf Core Bundle functionalities

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

To add the feature in a custom Karaf distribution, add it to startupFeatures in the project pom.xml

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

9.4.2.1. Property placeholders resolvers

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.

  1. List of resolution strategies

Prefix

Example

Description

env

env:JAVA_HOME

lookup the property from OS environment variables.

sys

sys:java.version

lookup the property from Java JVM system properties.

service

service:amq

lookup the property from OS environment variables using the service naming idiom.

service.host

service.host:amq

lookup the property from OS environment variables using the service naming idiom returning the hostname part only.

service.port

service.port:amq

lookup the property from OS environment variables using the service naming idiom returning the port part only.

k8s:map

k8s:map:myMap/myKey

lookup the property from a Kubernetes ConfigMap (via API)

k8s:secret

k8s:secret:amq/password

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

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 were secrets are mapped

fabric8.k8s.secrets.api.enabled

false

Enable/Disable consuming secrets via APIs

To set the property placeholder service options you can use system properties or environment variables or both.

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

    oc policy add-role-to-user view system:serviceaccount:$(oc project -q):default -n $(oc project -q)
  2. Mount 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

9.4.2.2. Adding a custom property placeholders resolvers

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.

To add a custom property placeholders resolvers, follow these steps:

  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]

9.4.3. Adding Fabric8 Karaf Config Admin Support

To include Config Admin Support feature in your custom Karaf distribution, add fabric8-karaf-cm to startupFeatures in your project pom.xml

pom.xml

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

9.4.3.1. Adding ConfigMap injection

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

To be added by the ConfigAdmin bridge, a ConfigMap has to be labeled with karaf.pid, where its values corresponds to the pid of your component.

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

Individual properties work for most cases. But to define your configuration, you can use a single property names. 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

9.4.3.2. 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

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>

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, separated by ";" there might be conditions on the label value which are considered in OR with each other.

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.

9.4.4. 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.

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

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

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.

9.4.5. 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.

You can add the Karaf health checks feature to the project pom.xml using startupFeatures.

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.

9.4.5.1. Adding Custom Heath 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.

Your project will need to 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.

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();
    }
}