Apache Karaf dynamic and static approach, docker and kubernetes

Most of the Karaf users know the Apache Karaf “standard” distribution.

Apache Karaf dynamic approach (“standard” distribution)

You can download 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, …).
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.

Apache Karaf 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 super light, standalone/immutable while supporting all Karaf features !

We also have new tools coming that can directly generate dockerfile or even docker images.

In this blog, I will show how to create a application running in Karaf “static” runtime.

As I have some work in progress, this blog is based on the current pull request: https://github.com/apache/karaf/pull/789.

Your application

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

For the demo, I’m creating a very simple Servlet.

The code is pretty simple: 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 as defined in the pom.xml. As we need a features XML to package in Karaf, we use the karaf-maven-plugin features-generate-descriptor goal 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 has been 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 pom.xml containing the following steps:

  1. assembly creates the runtime filesystem
  2. archive packages the runtime filesystem (generated by assembly) as a zip and tar.gz archive
  3. dockerfile creates a turnkey Dockerfile with your runtime
  4. docker optionally directly use Docker to create a docker image using the generated Dockerfile. 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 startupFeatures contains the static-framework which 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-example-app.
  • the environment is static meaning that the resolution is performed at build time
  • the useReferenceUrls disable Maven support and directly use the jar/resources populated in the runtime system folder.

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

In the dist/target folder, we can find:

  • the assembly directory containing the runtime filesystem
  • the zip and tar.gz archives
  • 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 tar.gz archive.

First we extract the tar.gz:

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

Docker

If you have Docker installed on your machine (the machine where you build), you can use the docker profile to directly create the Docker image:

$ mvn clean install -Pdocker

If you don’t have docker on your machine, the build at least creates a Dockerfile. By default, the Docker image name is karaf, but you can pass the image name using the imageName configuration:

<configuration>
  <imageName>${project.artifactId}</imageName>
</configuration>

You can use this Dockerfile (and the whole target) folder to create the Docker image with:

$ cd target
$ docker build -t mykaraf .
Sending build context to Docker daemon  57.41MB
Step 1/7 : FROM openjdk:8-jre
 ---> d60154a7d9b2
Step 2/7 : ENV KARAF_INSTALL_PATH /opt
 ---> Running in 9518c5e2141e
Removing intermediate container 9518c5e2141e
 ---> c49033d75fef
Step 3/7 : ENV KARAF_HOME $KARAF_INSTALL_PATH/apache-karaf
 ---> Running in 6a8f314162ea
Removing intermediate container 6a8f314162ea
 ---> 6bd1124f27c9
Step 4/7 : ENV PATH $PATH:$KARAF_HOME/bin
 ---> Running in ab00f87fda1d
Removing intermediate container ab00f87fda1d
 ---> cfa06b1e5bce
Step 5/7 : COPY assembly $KARAF_HOME
 ---> c74c5a3adda3
Step 6/7 : EXPOSE 8101 1099 44444 8181
 ---> Running in 667de77413bc
Removing intermediate container 667de77413bc
 ---> ee720e290d7f
Step 7/7 : CMD ["karaf", "run"]
 ---> Running in d283a0c53d93
Removing intermediate container d283a0c53d93
 ---> 23eb3c781a39
Successfully built 23eb3c781a39
Successfully tagged mykaraf:latest

You have a Docker image ready:

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mykaraf             latest              23eb3c781a39        26 seconds ago      463MB

If you used the docker profile, you have a karaf Docker image ready:

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
karaf               latest              f12b3148c33e        3 seconds ago       463MB

Now, you can run a Docker container using this image:

$ docker run --name mykaraf -p 8181:8181 karaf
karaf: Ignoring predefined value for KARAF_HOME
Mar 21, 2019 3:55:03 PM org.apache.karaf.main.Main launch
INFO: Installing and starting initial bundles
Mar 21, 2019 3:55:03 PM org.apache.karaf.main.Main launch
INFO: All initial bundles installed and set to start
Mar 21, 2019 3:55:03 PM org.apache.karaf.main.Main$KarafLockCallback lockAcquired
INFO: Lock acquired. Setting startlevel to 100
15:55:04.287 INFO  [FelixStartLevel] Logging initialized @957ms to org.eclipse.jetty.util.log.Slf4jLog
15:55:04.298 INFO  [FelixStartLevel] EventAdmin support is not available, no servlet events will be posted!
15:55:04.299 INFO  [FelixStartLevel] LogService support enabled, log events will be created.
15:55:04.301 INFO  [FelixStartLevel] Pax Web started
15:55:04.515 INFO  [paxweb-config-1-thread-1] No ALPN class available
15:55:04.515 INFO  [paxweb-config-1-thread-1] HTTP/2 not available, creating standard ServerConnector for Http
15:55:04.531 INFO  [paxweb-config-1-thread-1] Pax Web available at [0.0.0.0]:[8181]
15:55:04.536 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.http.core [16]] to http service
15:55:04.552 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.examples.karaf-docker-example-app [15]] to http service
15:55:04.564 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer to ServletContainerInitializers
15:55:04.564 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer
15:55:04.565 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer to ServletContainerInitializers
15:55:04.618 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer
15:55:04.619 INFO  [paxweb-config-1-thread-1] will add org.apache.jasper.servlet.JasperInitializer to ServletContainerInitializers
15:55:04.619 INFO  [paxweb-config-1-thread-1] Skipt org.apache.jasper.servlet.JasperInitializer, because specialized handler will be present
15:55:04.655 INFO  [paxweb-config-1-thread-1] registering context DefaultHttpContext [bundle=org.apache.karaf.examples.karaf-docker-example-app [15], contextID=default], with context-name: 
15:55:04.671 INFO  [paxweb-config-1-thread-1] registering JasperInitializer
15:55:04.716 INFO  [paxweb-config-1-thread-1] No DecoratedObjectFactory provided, using new org.eclipse.jetty.util.DecoratedObjectFactory[decorators=1]
15:55:04.801 INFO  [paxweb-config-1-thread-1] DefaultSessionIdManager workerName=node0
15:55:04.802 INFO  [paxweb-config-1-thread-1] No SessionScavenger set, using defaults
15:55:04.803 INFO  [paxweb-config-1-thread-1] node0 Scavenging every 600000ms
15:55:04.814 INFO  [paxweb-config-1-thread-1] Started HttpServiceContext{httpContext=DefaultHttpContext [bundle=org.apache.karaf.examples.karaf-docker-example-app [15], contextID=default]}
15:55:04.820 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-8u181-b13-2~deb9u1-b13
15:55:04.864 INFO  [paxweb-config-1-thread-1] Started default@28e475cc{HTTP/1.1,[http/1.1]}{0.0.0.0:8181}
15:55:04.865 INFO  [paxweb-config-1-thread-1] Started @1539ms
15:55:04.867 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.ops4j.pax.web.pax-web-extender-whiteboard [48]] to http service

By default, the runtime executes in foreground. We can use -d to run in daemon mode:

$ docker run --name mykaraf -p 8181:8181 -d karaf
c05645357cd17a0828ef7acaf619071cc3c94f316ca605217890371c0c1e4ab0

We can see our container running:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                                   NAMES
c05645357cd1        karaf               "karaf run"         27 seconds ago      Up 27 seconds       1099/tcp, 8101/tcp, 44444/tcp, 0.0.0.0:8181->8181/tcp   mykaraf

We can see the log of our container:

$ docker logs mykaraf
docker logs mykaraf
karaf: Ignoring predefined value for KARAF_HOME
Mar 21, 2019 3:56:45 PM org.apache.karaf.main.Main launch
INFO: Installing and starting initial bundles
Mar 21, 2019 3:56:45 PM org.apache.karaf.main.Main launch
INFO: All initial bundles installed and set to start
Mar 21, 2019 3:56:45 PM org.apache.karaf.main.Main$KarafLockCallback lockAcquired
INFO: Lock acquired. Setting startlevel to 100
15:56:45.831 INFO  [FelixStartLevel] Logging initialized @946ms to org.eclipse.jetty.util.log.Slf4jLog
15:56:45.844 INFO  [FelixStartLevel] EventAdmin support is not available, no servlet events will be posted!
15:56:45.845 INFO  [FelixStartLevel] LogService support enabled, log events will be created.
15:56:45.847 INFO  [FelixStartLevel] Pax Web started
15:56:46.055 INFO  [paxweb-config-1-thread-1] No ALPN class available
15:56:46.055 INFO  [paxweb-config-1-thread-1] HTTP/2 not available, creating standard ServerConnector for Http
15:56:46.071 INFO  [paxweb-config-1-thread-1] Pax Web available at [0.0.0.0]:[8181]
15:56:46.075 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.examples.karaf-docker-example-app [15]] to http service
15:56:46.093 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer to ServletContainerInitializers
15:56:46.093 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer
15:56:46.094 INFO  [paxweb-config-1-thread-1] will add org.apache.jasper.servlet.JasperInitializer to ServletContainerInitializers
15:56:46.094 INFO  [paxweb-config-1-thread-1] Skipt org.apache.jasper.servlet.JasperInitializer, because specialized handler will be present
15:56:46.094 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer to ServletContainerInitializers
15:56:46.132 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer
15:56:46.163 INFO  [paxweb-config-1-thread-1] registering context DefaultHttpContext [bundle=org.apache.karaf.examples.karaf-docker-example-app [15], contextID=default], with context-name: 
15:56:46.174 INFO  [paxweb-config-1-thread-1] registering JasperInitializer
15:56:46.203 INFO  [paxweb-config-1-thread-1] No DecoratedObjectFactory provided, using new org.eclipse.jetty.util.DecoratedObjectFactory[decorators=1]
15:56:46.272 INFO  [paxweb-config-1-thread-1] DefaultSessionIdManager workerName=node0
15:56:46.273 INFO  [paxweb-config-1-thread-1] No SessionScavenger set, using defaults
15:56:46.274 INFO  [paxweb-config-1-thread-1] node0 Scavenging every 660000ms
15:56:46.284 INFO  [paxweb-config-1-thread-1] Started HttpServiceContext{httpContext=DefaultHttpContext [bundle=org.apache.karaf.examples.karaf-docker-example-app [15], contextID=default]}
15:56:46.289 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-8u181-b13-2~deb9u1-b13
15:56:46.324 INFO  [paxweb-config-1-thread-1] Started default@28e475cc{HTTP/1.1,[http/1.1]}{0.0.0.0:8181}
15:56:46.324 INFO  [paxweb-config-1-thread-1] Started @1444ms
15:56:46.326 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.ops4j.pax.web.pax-web-extender-whiteboard [48]] to http service
15:56:46.328 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.http.core [16]] to http service

You can now access to http://localhost:8181/servlet-example in your browser.

Then you see the logs updated in the Docker container:

$ docker logs mykaraf
...
15:58:24.068 INFO  [qtp117150641-37] Client 172.17.0.1 request received on http://localhost:8181/servlet-example

We can stop our Docker container:

$ docker stop mykaraf
mykaraf

Running on AWS with Kubernetes

Now that we have our Docker image ready, we can push to AWS ECR (Docker container Registry).

First, we create a ECR repository on AWS:

Than, we tag and push our image to AWS ECR (using IAM user):

$ docker tag karaf:latest 295331841498.dkr.ecr.eu-west-1.amazonaws.com/karaf:latest
$ aws ecr get-login --no-include-email --region eu-west-1 
$ docker push 295331841498.dkr.ecr.eu-west-1.amazonaws.com/karaf:latest

We can now see our Karaf image on ECR:

Now that we have our Docker image on ECR, we can create cluster using it.

Let’s start with a simple ECS cluster.

Using ECS

ECS directly run docker containers (tasks).

We create a ECS cluster:

We add a new task there (Docker container):

We can see the public IP address on the task and so we can use it directly in a browser:

We can see the logs updated:

We can update the service to have multiple containers running:

We can see the service using 5 instances now:

Instead of ECS, you can use Kubernetes on EKS cluster.

Using EKS

First, let’s create the EKS cluster on AWS:

We can now access this cluster using our local kubectl:

$ aws eks update-kubeconfig ...
$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   50m

We now create some nodes in our cluster:

Once the nodes are part of our cluster, we create a POD descriptor with our Karaf image:

apiVersion: v1
kind: Pod
metadata:
  name: karaf-docker-example-dist
spec:
  containers:
    - name: karaf-docker-example-dist-ctr
      image: 295331841498.dkr.ecr.eu-west-1.amazonaws.com/karaf:latest
      resources:
        limits:
          memory: "500Mi"
        requests:
          memory: "250Mi"
      command: ["karaf", "run"]
      args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]

Then, we create the POD in the EKS cluster:

$ kubectl create -f karaf-docker-example-dist.yaml 
pod/karaf-docker-example-dist created

We can see our POD boostrapping on EKS:

$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
karaf-docker-example-dist   0/1     Pending   0          2m

Conclusion

We can see here how Apache Karaf is flexible, supporting two completely different approaches:

  1. The “dynamic/container” approach (aka “standard” distribution) allows you to start Karaf as a “container” and deploy dynamically at runtime new applications.
  2. The “static” approach allows you to package all at build time and easily bootstrap your application powered by Karaf.

Your applications are able to run in both mode, it’s just a matter of assembly/packaging/distribution.

We can see here the “polymorphic” part of Apache Karaf, where you can use it on premise, on the cloud, running as a container, running as a bootstrapper, for small to large production platform.

In the coming releases, we will work to provide even better tooling for both dev and devops.

Stay tuned !

You May Also Like

About the Author: jbonofre

ASF Member, PMC for Apache Karaf, PMC for Apache ServiceMix, PMC for Apache Archiva, PMC for Apache Felix, PMC for Apache Camel, PMC for Apache Syncope, PMC for Apache Beam, PMC for Apache CarbonData, PMC for Apache Bahir, PMC for Apache Brooklyn, PMC for Apache Falcon, PMC for Apache Guacamole, PMC for Apache Lens, Committer for Apache ActiveMQ and much more ! Twitter: jbonofre IRC: jbonofre on #servicemix,#karaf,#camel,#cxf on Freenode