Karaf Custom Distribution

How to create a custom Karaf distribution

Published: Mar 21, 2020 by F.Papon

In this tutorial, I will show you how to create a custom Apache Karaf distribution. We will use the karaf-maven-plugin to build the distribution and the jib-maven-plugin to build the docker image.

One of the advantage to create a custom distribution is the pre-packaging of the default features. It’s pretty convenient when you want to deploy in a closed network or to avoid downloading the features at the first startup of the instance.

Project structure

We only need a Maven pom.xml.

Because of security reason, the JMX ports are only available on localhost. For the example, we want to allow JMX access outside of the docker container without binding the port to localhost, so We change the host to 0.0.0.0 in the etc/org.apache.karaf.management.cfg config file.

We add a file src/main/karaf/assembly-property-edits.xml in the project.

<?xml version="1.0" encoding="UTF-8"?>
<property-edits xmlns="http://karaf.apache.org/tools/property-edits/1.0.0">
    <edits>
        <edit>
            <file>org.apache.karaf.management.cfg</file>
            <operation>put</operation>
            <key>rmiRegistryHost</key>
            <value>0.0.0.0</value>
        </edit>
        <edit>
            <file>org.apache.karaf.management.cfg</file>
            <operation>put</operation>
            <key>rmiServerHost</key>
            <value>0.0.0.0</value>
        </edit>
    </edits>
</property-edits>

Packaging

According to the karaf-maven-plugin we use the package type:

<packaging>karaf-assembly</packaging>

Dependencies

<dependencies>
    <dependency>
        <groupId>org.apache.karaf.features</groupId>
        <artifactId>framework</artifactId>
        <version>${karaf.runtime.version}</version>
        <type>kar</type>
    </dependency>
    <dependency>
        <groupId>org.apache.karaf.features</groupId>
        <artifactId>framework</artifactId>
        <version>${karaf.runtime.version}</version>
        <classifier>features</classifier>
        <type>xml</type>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.karaf.features</groupId>
        <artifactId>standard</artifactId>
        <version>${karaf.runtime.version}</version>
        <classifier>features</classifier>
        <type>xml</type>
    </dependency>
    <dependency>
        <groupId>org.apache.karaf.decanter</groupId>
        <artifactId>apache-karaf-decanter</artifactId>
        <version>${decanter.version}</version>
        <classifier>features</classifier>
        <type>xml</type>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

As a minimum runtime, we have to add the Karaf framework and standard features. For the example, we add the Karaf Decanter feature repository.

Build

Karaf-Maven-Plugin

<plugin>
    <groupId>org.apache.karaf.tooling</groupId>
    <artifactId>karaf-maven-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <finalName>${project.artifactId}</finalName>
        <bootFeatures>
            <feature>wrap</feature>
            <feature>bundle</feature>
            <feature>config</feature>
            <feature>system</feature>
            <feature>feature</feature>
            <feature>package</feature>
            <feature>log</feature>
            <feature>ssh</feature>
            <feature>instance</feature>
            <feature>shell</feature>
            <feature>management</feature>
            <feature>service</feature>
            <feature>jaas</feature>
            <feature>deployer</feature>
            <feature>diagnostic</feature>
            <feature>scr</feature>
            <feature>http</feature>
            <feature>war</feature>
            <feature>decanter-collector-rest-servlet</feature>
        </bootFeatures>
        <installedFeatures>
            <feature>aries-blueprint</feature>
            <feature>shell-compat</feature>
        </installedFeatures>
        <startupFeatures>
            <feature>eventadmin</feature>
        </startupFeatures>
        <archiveTarGz>false</archiveTarGz>
        <archiveZip>false</archiveZip>
        <workDirectory>${project.build.directory}/assembly/karaf</workDirectory>
        <pathPrefix>karaf</pathPrefix>
        <writeProfiles>true</writeProfiles>
    </configuration>
</plugin>

Define the feature list we want to include in the distribution:

  • <startupFeatures>: features loaded during the startup of the runtime.
  • <bootFeatures>: features installed and started after the startup of the runtime.
  • <installedFeatures>: features included in the distribution but not started.

We activate the profile output to check the feature configuration, they will be generated in $KARAF_HOME/etc/profiles folder:

<writeProfiles>true</writeProfiles>

As we want to use the distribution in Docker, we deactivate the <archiveZip> and <archiveTarGz> options:

<archiveZip>false</archiveZip>

Define the output directory:

<workDirectory>${project.build.directory}/assembly/karaf</workDirectory>

For the example, we want to install and start the Karaf Decanter rest collector, we add it in the bootFeature list:

<feature>decanter-collector-rest-servlet</feature>

Build the distribution:

mvn clean package

You can see the distribution structure in target/assembly/karaf:

└── karaf
    ├── bin
    │   └── contrib
    ├── data
    │   └── tmp
    ├── deploy
    ├── etc
    │   ├── profiles
    │   │   └── generated
    │   │       ├── boot.profile
    │   │       ├── installed.profile
    │   │       └── startup.profile
    │   └── scripts
    ├── lib
    │   ├── boot
    │   ├── endorsed
    │   ├── ext
    │   └── jdk9plus
    ├── META-INF
    └── system
        ├── javax
        ...

Check the etc/org.apache.karaf.management.cfg management config file:

#Modified by org.apache.karaf.tools.utils.KarafPropertiesFile
#Sat Mar 21 18:11:12 CET 2020
daemon=true
jmxRealm=karaf
jmxmpEnabled=false
jmxmpHost=127.0.0.1
jmxmpObjectName=connector\:name\=jmxmp
jmxmpPort=9999
jmxmpServiceUrl=service\:jmx\:jmxmp\://${jmxmpHost}\:${jmxmpPort}
objectName=connector\:name\=rmi
rmiRegistryHost=0.0.0.0
rmiRegistryPort=1099
rmiServerHost=0.0.0.0
rmiServerPort=44444
serviceUrl=service\:jmx\:rmi\://${rmiServerHost}\:${rmiServerPort}/jndi/rmi\://${rmiRegistryHost}\:${rmiRegistryPort}/karaf-${karaf.name}
threaded=true

We can test the instance and check the installation of the Karaf Decanter rest collector:

sh ./target/assembly/karaf/bin/karaf
        __ __                  ____      
       / //_/____ __________ _/ __/      
      / ,<  / __ `/ ___/ __ `/ /_        
     / /| |/ /_/ / /  / /_/ / __/        
    /_/ |_|\__,_/_/   \__,_/_/         

  Apache Karaf (4.3.0.RC1)

Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
Hit '<ctrl-d>' or type 'system:shutdown' or 'logout' to shutdown Karaf.

karaf@root()> list
START LEVEL 100 , List Threshold: 50
 ID │ State  │ Lvl │ Version   │ Name
────┼────────┼─────┼───────────┼────────────────────────────────────────────────────────────
 27 │ Active │  80 │ 2.2.0     │ Apache Karaf :: Decanter :: API
 28 │ Active │  80 │ 2.2.0     │ Apache Karaf :: Decanter :: Collector :: REST :: Servlet
 29 │ Active │  80 │ 2.2.0     │ Apache Karaf :: Decanter :: Marshaller :: CSV
 30 │ Active │  80 │ 2.2.0     │ Apache Karaf :: Decanter :: Marshaller :: Json
 31 │ Active │  80 │ 2.2.0     │ Apache Karaf :: Decanter :: Marshaller :: Raw
 32 │ Active │  80 │ 2.2.0     │ Apache Karaf :: Decanter :: Parser :: Identity
 33 │ Active │  80 │ 2.2.0     │ Apache Karaf :: Decanter :: Parser :: Regex
 34 │ Active │  80 │ 2.2.0     │ Apache Karaf :: Decanter :: Parser :: Split
 38 │ Active │  80 │ 4.3.0.RC1 │ Apache Karaf :: OSGi Services :: Event
 57 │ Active │  80 │ 4.14.0    │ Apache XBean OSGI Bundle Utilities
 58 │ Active │  80 │ 4.14.0    │ Apache XBean :: Classpath Resource Finder
 87 │ Active │  80 │ 1.0.4     │ JSR 353 (JSON Processing) Default Provider
 92 │ Active │  80 │ 7.2.0     │ org.objectweb.asm
 93 │ Active │  80 │ 7.2.0     │ org.objectweb.asm.commons
 94 │ Active │  80 │ 7.2.0     │ org.objectweb.asm.tree
108 │ Active │  80 │ 0.0.0     │ profiles

karaf@root()> http:list
ID │ Servlet       │ Servlet-Name   │ State       │ Alias             │ Url
───┼───────────────┼────────────────┼─────────────┼───────────────────┼──────────────────────
28 │ RestCollector │ ServletModel-2 │ Deployed    │ /decanter/collect │ [/decanter/collect/*]

karaf@root()>                                                                               

JIB-Maven-Plugin

<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>1.8.0</version>
    <configuration>
        <allowInsecureRegistries>true</allowInsecureRegistries>
        <from>
            <image>adoptopenjdk/openjdk11:alpine-jre</image>
        </from>
        <to>
            <image>fpaponapache.azurecr.io/fpaponapache/karaf-custom-distrib:${project.version}</image>
            <auth>
                <username>fpaponapache</username>
                <password></password>
            </auth>
        </to>
        <container>
            <entrypoint>
                <entrypoint>/karaf/bin/karaf</entrypoint>
                <entrypoint>run</entrypoint>
            </entrypoint>
            <workingDirectory>/karaf</workingDirectory>
            <ports>
                <port>8101</port>
                <port>1099</port>
                <port>44444</port>
                <port>8181</port>
            </ports>
        </container>
        <extraDirectories>
            <paths>
                <path>target/assembly</path>
            </paths>
            <permissions>
                <permission>
                    <file>/karaf/bin/karaf</file>
                    <mode>755</mode>
                </permission>
            </permissions>
        </extraDirectories>
    </configuration>
</plugin>

We are using the openjdk11 alpine jre.

Here we are using an image name to be deployed on Azure registry. To deploy on another registry, you can check the JIB maven plugin documentation here

We define the Karaf instance entrypoint to the karaf run and expose the ports:

  • 8101 for ssh
  • 1099 and 44444 for JMX
  • 8181 for http

Build the image on local registry:

mvn jib:dockerBuild

NB: To build and pull on the remote registry, execute mvn jib:build.

You can see the image on your local docker registry:

docker images
>
REPOSITORY                                                  TAG                   IMAGE ID            CREATED             SIZE
fpaponapache.azurecr.io/fpaponapache/karaf-custom-distrib   1.0.0-SNAPSHOT        8ed6cc4bae93        2 days ago          230MB

Now you can run a container:

docker run -it --name karaf-custom fpaponapache.azurecr.io/fpaponapache/karaf-custom-distrib:1.0.0-SNAPSHOT

Find the IP of the container:

docker inspect karaf-custom

...
    "Networks": {
        "bridge": {
            "IPAMConfig": null,
            "Links": null,
            "Aliases": null,
            "NetworkID": "1fb712c7d779bcf7c7bf8af14d926eb19b676e059f04ec06a01504a79bce2e80",
            "EndpointID": "281c001dc403cad585002d2f4567a2574fcc828dd2360b518fbd746d1e39c0fa",
            "Gateway": "172.17.0.1",
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "MacAddress": "02:42:ac:11:00:02",
            "DriverOpts": null
        }
    }

Check the availability of the Karaf Decanter rest collector:

curl --header "Content-Type: application/json" --request POST --data '{"message":"it works","level":"info"}' http://172.17.0.2:8181/decanter/collect

* Expire in 0 ms for 6 (transfer 0x558fd1d68f50)
*   Trying 172.17.0.2...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x558fd1d68f50)
* Connected to 172.17.0.2 (172.17.0.2) port 8181 (#0)
> POST /decanter/collect HTTP/1.1
> Host: 172.17.0.2:8181
> User-Agent: curl/7.64.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 37
> 
* upload completely sent off: 37 out of 37 bytes
< HTTP/1.1 201 Created
< Content-Length: 0
< Server: Jetty(9.4.22.v20191022)
< 
* Connection #0 to host 172.17.0.2 left intact

Connecting with VisualVM on remote docker container

  • Add a new remote host.
  • Add a new JMX connection.

Configuration:

  • Connection: service:jmx:rmi://172.17.0.2:1099/jndi/rmi://172.17.0.2:1099/karaf-root
  • Display-name: karaf-docker
  • Use security credentials: username=karaf / password=karaf

NB: at the end of the url connection, the karaf instance name is root and you can see your instance name in the $KARAF_HOME/instances/instance.properties

/karaf/instances # cat instance.properties 
count = 1
item.0.name = root
item.0.loc = /karaf
item.0.pid = 61
item.0.root = true

Conclusion

You now have the basic information to create a Karaf custom distribution and pull it to a Docker registry.

The source of the example are available on Github here

karaf docker