Apache Karaf : static & dynamic approach

Apache Karaf : static & dynamic approach

Apache Karaf™ is not only a server, it can also be used in a "static" way. Most users are familiar with the "standard" distribution. Discover the other approaches to the solution.

Adaptation of the article:"Apache Karaf dynamic and static approach, docker and kubernetes» of blog Nanthrax written by Jean-baptiste Onofré (Technical Advisor)

yupiik-apache-karaf

Dynamic approch ("standard" distribution)

You can dowload Apache Karaf “standard” or “minimal” distributions, you download a “dynamic” distribution. By “dynamic”, it means that you start the Karaf runtime and, later, you deploy applications in it. The resolution is performed at runtime, at deployment time. It’s a “application container” approach (like Apache Tomcat among others).

You can create your own custom Karaf runtime (using boot features for instance), where the container starts a set of applications at bootstrap.

However, it’s not the only approach ! Apache Karaf is a complete polymorphic application runtime, meaning that it can take different form to match your expectations, use cases and devops requirements.

Static approach (likely immutable)

You can use a “static” approach with Apache Karaf. It’s similar to kind of spring-boot bootstrap and especially very convenient used with docker and on the cloud. The resolution is made at build time, predictable.

This approach is lightweight, autonomous - immutable and supports all Karaf features! New tools can directly generate dockerfile or even docker images

We will focus on: how to create an application running in Karaf "static" runtime? 

The development of the following solutions are responses to requests made on the GitHub by Jean-Baptiste Onofré.

Application

That’s where actually your business code will be located. It’s a regular Karaf application.

For the demo, we will create a very simple Servlet. The code, it uses SCR to expose the Servlet as a service.

package org.apache.karaf.examples.docker;

import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component(
        property = { "alias=/servlet-example", "servlet-name=Example"}
)
public class ExampleServlet extends HttpServlet implements Servlet {

    private final static Logger LOGGER = LoggerFactory.getLogger(ExampleServlet.class);

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        LOGGER.info("Client " + request.getRemoteAddr() + " request received on " + request.getRequestURL());

        try (PrintWriter writer = response.getWriter()) {
            writer.println("<html>");
            writer.println("<head>");
            writer.println("<title>Example</title>");
            writer.println("</head>");
            writer.println("<body align='center'>");
            writer.println("<h1>Example Servlet</h1>");
            writer.println("</body>");
            writer.println("</html>");
        }
    }

}

This application is packaged as a bundle defined in pom.xml.

As we need an XML of features to package in Karaf, we use karaf-maven-plugin features-generate-descriptor to automatically create the feature.xml :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.apache.karaf.examples</groupId>
        <artifactId>karaf-docker-example</artifactId>
        <version>4.3.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>karaf-docker-example-app</artifactId>
    <packaging>bundle</packaging>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.cmpn</artifactId>
            <version>6.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>karaf-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>features-generate-descriptor</goal>
                        </goals>
                        <configuration>
                            <includeProjectArtifact>true</includeProjectArtifact>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

The features XML were generated in the target folder and attached to the Maven project.

That’s it ! We now have an application module (that could be a service/micro-service as well) that you can either deploy in the regular Karaf container (dynamic approach) or a Karaf “static” runtime.

The runtime assembly

The "static" runtime will assemble and package a set of application modules. You just pick the modules you want. The assembly is a simple file pom.xml containing the following steps:

  1. assembly creates the runtime filesystem
  2. archive packages the runtime filesystem (generated by assemblyas a zip and archive tar.gz
  3. dockerfile creates a turnkey Dockerfile with your runtime.
  4. docker can use Docker directly to create a docker image using the dockerfile generated. We do this step only if the docker profile is enabled.

Here’s the complete pom.xml .

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
       <groupId>org.apache.karaf.examples</groupId>
        <artifactId>karaf-docker-example</artifactId>
        <version>4.3.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>karaf-docker-example-dist</artifactId>
    <packaging>pom</packaging>

    <dependencies>
        <dependency>
            <groupId>org.apache.karaf.features</groupId>
            <artifactId>static</artifactId>
            <type>kar</type>
        </dependency>

        <dependency>
            <groupId>org.apache.karaf.features</groupId>
            <artifactId>standard</artifactId>
            <classifier>features</classifier>
            <type>xml</type>
        </dependency>

        <dependency>
            <groupId>org.apache.karaf.services</groupId>
            <artifactId>org.apache.karaf.services.staticcm</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.karaf.examples</groupId>
            <artifactId>karaf-docker-example-app</artifactId>
            <type>xml</type>
            <classifier>features</classifier>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>karaf-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>process-resources</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>assembly</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>package</id>
                        <goals>
                            <goal>archive</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>dockerfile</id>
                        <goals>
                            <goal>dockerfile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <startupFeatures>
                        <startupFeature>static-framework</startupFeature>
                        <startupFeature>scr</startupFeature>
                        <startupFeature>http-whiteboard</startupFeature>
                        <startupFeature>karaf-docker-example-app</startupFeature>
                    </startupFeatures>
                    <framework>static</framework>
                    <useReferenceUrls>true</useReferenceUrls>
                    <environment>static</environment>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <profiles>
        <profile>
            <id>docker</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.karaf.tooling</groupId>
                        <artifactId>karaf-maven-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>docker-image</id>
                                <goals>
                                    <goal>docker</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

</project>

We can note in this pom.xml :

  • the function startupFeatures contains the static-framework is the core “static” runtime. Then we add the prerequisite features for our application: scr and http-whiteboard. Finally, we also add “our” generated application feature: karaf-docker-exemple-app.
  • Theenvironment is staticmeaning that the resolution is performed at build time
  • The useReferenceUrls disable Maven support and directly use the jar/resources populated in the runtime folder : system..

We now build the project with a simple mvn clean install.

In the dist/targetfolder, we can find:

  • The assembly directory containing the runtime filesystem
  • The archives zip and tar.gz
  • The ready to use Dockerfile for the runtime including your application
$ ls target
assembly
Dockerfile
karaf-docker-example-dist-4.3.0-SNAPSHOT.tar.gz
karaf-docker-example-dist-4.3.0-SNAPSHOT.zip

We can locally use the assembly or archive runtime.

Let’s use the archive : tar.gz. First we extract it:

$ tar zxvf karaf-docker-example-dist-4.3.0-SNAPSHOT.tar.gz
$ cd karaf-docker-example-dist-4.3.0-SNAPSHOT/

We go into the bin folder and we can use karaf run to start the runtime:

$ cd bin
$ ./karaf run
Mar 21, 2019 4:47:16 PM org.apache.karaf.main.Main launch
INFO: Installing and starting initial bundles
Mar 21, 2019 4:47:16 PM org.apache.karaf.main.Main launch
INFO: All initial bundles installed and set to start
Mar 21, 2019 4:47:16 PM org.apache.karaf.main.Main$KarafLockCallback lockAcquired
INFO: Lock acquired. Setting startlevel to 100
16:47:17.002 INFO  [FelixStartLevel] Logging initialized @892ms to org.eclipse.jetty.util.log.Slf4jLog
16:47:17.014 INFO  [FelixStartLevel] EventAdmin support is not available, no servlet events will be posted!
16:47:17.015 INFO  [FelixStartLevel] LogService support enabled, log events will be created.
16:47:17.016 INFO  [FelixStartLevel] Pax Web started
16:47:17.282 INFO  [paxweb-config-1-thread-1] No ALPN class available
16:47:17.282 INFO  [paxweb-config-1-thread-1] HTTP/2 not available, creating standard ServerConnector for Http
16:47:17.299 INFO  [paxweb-config-1-thread-1] Pax Web available at [0.0.0.0]:[8181]
16:47:17.304 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.ops4j.pax.web.pax-web-extender-whiteboard [48]] to http service
16:47:17.316 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.examples.karaf-docker-example-app [15]] to http service
16:47:17.329 INFO  [paxweb-config-1-thread-1] will add org.apache.jasper.servlet.JasperInitializer to ServletContainerInitializers
16:47:17.330 INFO  [paxweb-config-1-thread-1] Skipt org.apache.jasper.servlet.JasperInitializer, because specialized handler will be present
16:47:17.330 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer to ServletContainerInitializers
16:47:17.383 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer
16:47:17.383 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer to ServletContainerInitializers
16:47:17.383 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer
16:47:17.422 INFO  [paxweb-config-1-thread-1] registering context DefaultHttpContext [bundle=org.apache.karaf.examples.karaf-docker-example-app [15], contextID=default], with context-name: 
16:47:17.436 INFO  [paxweb-config-1-thread-1] registering JasperInitializer
16:47:17.466 INFO  [paxweb-config-1-thread-1] No DecoratedObjectFactory provided, using new org.eclipse.jetty.util.DecoratedObjectFactory[decorators=1]
16:47:17.540 INFO  [paxweb-config-1-thread-1] DefaultSessionIdManager workerName=node0
16:47:17.540 INFO  [paxweb-config-1-thread-1] No SessionScavenger set, using defaults
16:47:17.541 INFO  [paxweb-config-1-thread-1] node0 Scavenging every 600000ms
16:47:17.551 INFO  [paxweb-config-1-thread-1] Started HttpServiceContext{httpContext=DefaultHttpContext [bundle=org.apache.karaf.examples.karaf-docker-example-app [15], contextID=default]}
16:47:17.557 INFO  [paxweb-config-1-thread-1] jetty-9.4.12.v20180830; built: 2018-08-30T13:59:14.071Z; git: 27208684755d94a92186989f695db2d7b21ebc51; jvm 1.8.0_181-b13
16:47:17.602 INFO  [paxweb-config-1-thread-1] Started default@26472f57{HTTP/1.1,[http/1.1]}{0.0.0.0:8181}
16:47:17.602 INFO  [paxweb-config-1-thread-1] Started @1500ms
16:47:17.605 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.http.core [16]] to http service

We can see the runtime starting with our application.

In a browser, you can access the example servlet on http://localhost:8181/servlet-example.

Then, we can see in the log:

16:48:19.132 INFO  [qtp1285811510-36] Client 0:0:0:0:0:0:0:1 request received on http://localhost:8181/servlet-example

Our runtime is running locally, now, let’s use the docker form of our runtime.

Découvrez prochainement la suite de cet article – Apache Karaf : Docker, Kubernetes

Blocked in your roadmaps?

Would you like to train your teams?

en_GBEnglish (UK)
fr_FRFrançais en_GBEnglish (UK)