Chapter 10. Developing an application for the Karaf image
This tutorial shows how to create and deploy an application for the Karaf image.
10.1. Creating a Karaf project using Maven archetype
To create a Karaf project using a Maven archetype, follow these steps.
Procedure
- Go to the appropriate directory on your system.
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-750020-redhat-00002/archetypes-catalog-2.2.0.fuse-750020-redhat-00002-archetype-catalog.xml \ -DarchetypeGroupId=org.jboss.fuse.fis.archetypes \ -DarchetypeArtifactId=karaf-camel-log-archetype \ -DarchetypeVersion=2.2.0.fuse-750020-redhat-00002
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': : fuse75-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: fuse75-karaf-camel-log version: 1.0-SNAPSHOT package: org.example.fis Y: : Y
When prompted, enter
org.example.fis
for thegroupId
value andfuse75-karaf-camel-log
for theartifactId
value. Accept the defaults for the remaining fields.-
If the above command exited with the BUILD SUCCESS status, you should now have a new Fuse on OpenShift project under the
fuse75-karaf-camel-log
subdirectory. You are now ready to build and deploy the
fuse75-karaf-camel-log
project. Assuming you are still logged into OpenShift, change to the directory of thefuse75-karaf-camel-log
project, and then build and deploy the project, as follows.cd fuse75-karaf-camel-log mvn fabric8:deploy -Popenshift
For the full list of available Karaf archetypes, see Karaf Archetype Catalog.
10.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.
10.3. Karaf archetype catalog
The Karaf archetype catalog includes the following examples.
Name | Description |
---|---|
| Demonstrates how to send and receive messages to an Apache ActiveMQ message broker, using the Camel amq component. |
| Demonstrates a simple Apache Camel application that logs a message to the server log every 5th second. |
| Demonstrates how to use SQL via JDBC along with Camel’s REST DSL to expose a RESTful API. |
| Demonstrates how to create a RESTful(JAX-RS) web service using CXF and expose it through the OSGi HTTP Service. |
10.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.
10.4.1. Adding Fabric8 Karaf features
To use the features, add fabric8-karaf-features
dependency to the project POM file.
Procedure
-
Open your project’s
pom.xml
file and addfabric8-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.
10.4.2. Adding Fabric8 Karaf Core bundle functionality
The bundle fabric8-karaf-core
provides the functionalities used by Blueprint and ConfigAdmin extensions.
Procedure
Open your project’s
pom.xml
and addfabric8-karaf-core
tostartupFeatures
section.<startupFeatures> ... <feature>fabric8-karaf-core</feature> ... </startupFeatures>
This will add the
fabric8-karaf-core
feature in a custom Karaf distribution.
10.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
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)
- Mount the secret to the Pod as access to secrets through API might be restricted.
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
10.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
Add the following mvn dependency to the project
pom.xml
.pom.xml
--- <dependency> <groupId>io.fabric8</groupId> <artifactId>fabric8-karaf-core</artifactId> </dependency> ---
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; } }
You can reference the resolver in Configuration management as follows.
properties
my.property = $[myResolver:value-to-resolve]
10.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.
- List of resolution strategies
Prefix | Example | Description |
|
| look up the property from OS environment variables. |
`sys |
| look up the property from Java JVM system properties. |
`service |
| look up the property from OS environment variables using the service naming convention. |
|
| look up the property from OS environment variables using the service naming convention returning the hostname part only. |
|
| look up the property from OS environment variables using the service naming convention returning the port part only. |
|
| look up the property from a Kubernetes ConfigMap (via API) |
|
| look up the property from a Kubernetes Secrets (via API or volume mounts) |
10.4.6. List of Property Placeholder service options
The property placeholder service supports the following options:
- List of property placeholder service options
Name | Default | Description |
---|---|---|
| $[ | The prefix for the placeholder |
| ] | The suffix for the placeholder |
| null | A comma delimited list of paths where secrets are mapped |
| false | Enable/Disable consuming secrets via APIs |
10.5. Adding Fabric8 Karaf Config admin support
10.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 addfabric8-karaf-cm
tostartupFeatures
section.pom.xml
<startupFeatures> ... <feature>fabric8-karaf-cm</feature> ... </startupFeatures>
10.5.2. Adding ConfigMap injection
The fabric8-karaf-cm
provides a ConfigAdmin
bridge that inject ConfigMap
values in Karaf’s ConfigAdmin
.
Procedure
To be added by the ConfigAdmin bridge, a ConfigMap has to be labeled with
karaf.pid
. Thekaraf.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
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
10.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
10.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>
10.5.5. Fabric8 Karaf Config Admin options
Fabric8 Karaf Config Admin supports the following options.
Name | Default | Description |
---|---|---|
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:
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". |
ConfigurationPlugin
requires Aries Blueprint CM 1.0.9
or above.
10.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
tostartupFeatures
section in your projectpom.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>
Nested property placeholder substitution requires Aries Blueprint Core 1.7.0
or above.
10.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.
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
Open you project’s
pom.xml
and addfabric8-karaf-checks
feature in thestartupFeatures
section.pom.xml
<startupFeatures> ... <feature>fabric8-karaf-checks</feature> ... </startupFeatures>
The
fabric8-maven-plugin:resources
goal will detect if your using thefabric8-karaf-checks
feature and automatically add the Kubernetes for readiness and liveness probes to your container’s configuration.
10.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>
NoteThe 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(); } }