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 targetassemblyDockerfilekaraf-docker-example-dist-4.3.0-SNAPSHOT.tar.gzkaraf-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 runMar 21, 2019 4:47:16 PM org.apache.karaf.main.Main launchINFO: Installing and starting initial bundlesMar 21, 2019 4:47:16 PM org.apache.karaf.main.Main launchINFO: All initial bundles installed and set to startMar 21, 2019 4:47:16 PM org.apache.karaf.main.Main$KarafLockCallback lockAcquiredINFO: Lock acquired. Setting startlevel to 10016:47:17.002 INFO  [FelixStartLevel] Logging initialized @892ms to org.eclipse.jetty.util.log.Slf4jLog16: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 started16:47:17.282 INFO  [paxweb-config-1-thread-1] No ALPN class available16:47:17.282 INFO  [paxweb-config-1-thread-1] HTTP/2 not available, creating standard ServerConnector for Http16: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 service16:47:17.316 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.examples.karaf-docker-example-app [15]] to http service16:47:17.329 INFO  [paxweb-config-1-thread-1] will add org.apache.jasper.servlet.JasperInitializer to ServletContainerInitializers16:47:17.330 INFO  [paxweb-config-1-thread-1] Skipt org.apache.jasper.servlet.JasperInitializer, because specialized handler will be present16:47:17.330 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer to ServletContainerInitializers16:47:17.383 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer16:47:17.383 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer to ServletContainerInitializers16:47:17.383 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer16: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 JasperInitializer16: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=node016:47:17.540 INFO  [paxweb-config-1-thread-1] No SessionScavenger set, using defaults16:47:17.541 INFO  [paxweb-config-1-thread-1] node0 Scavenging every 600000ms16: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-b1316: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 @1500ms16: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.41MBStep 1/7 : FROM openjdk:8-jre ---> d60154a7d9b2Step 2/7 : ENV KARAF_INSTALL_PATH /opt ---> Running in 9518c5e2141eRemoving intermediate container 9518c5e2141e ---> c49033d75fefStep 3/7 : ENV KARAF_HOME $KARAF_INSTALL_PATH/apache-karaf ---> Running in 6a8f314162eaRemoving intermediate container 6a8f314162ea ---> 6bd1124f27c9Step 4/7 : ENV PATH $PATH:$KARAF_HOME/bin ---> Running in ab00f87fda1dRemoving intermediate container ab00f87fda1d ---> cfa06b1e5bceStep 5/7 : COPY assembly $KARAF_HOME ---> c74c5a3adda3Step 6/7 : EXPOSE 8101 1099 44444 8181 ---> Running in 667de77413bcRemoving intermediate container 667de77413bc ---> ee720e290d7fStep 7/7 : CMD ["karaf", "run"] ---> Running in d283a0c53d93Removing intermediate container d283a0c53d93 ---> 23eb3c781a39Successfully built 23eb3c781a39Successfully tagged mykaraf:latest

You have a Docker image ready:

$ docker imagesREPOSITORY          TAG                 IMAGE ID            CREATED             SIZEmykaraf             latest              23eb3c781a39        26 seconds ago      463MB

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

$ docker imagesREPOSITORY          TAG                 IMAGE ID            CREATED             SIZEkaraf               latest              f12b3148c33e        3 seconds ago       463MB

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

$ docker run --name mykaraf -p 8181:8181 karafkaraf: Ignoring predefined value for KARAF_HOMEMar 21, 2019 3:55:03 PM org.apache.karaf.main.Main launchINFO: Installing and starting initial bundlesMar 21, 2019 3:55:03 PM org.apache.karaf.main.Main launchINFO: All initial bundles installed and set to startMar 21, 2019 3:55:03 PM org.apache.karaf.main.Main$KarafLockCallback lockAcquiredINFO: Lock acquired. Setting startlevel to 10015:55:04.287 INFO  [FelixStartLevel] Logging initialized @957ms to org.eclipse.jetty.util.log.Slf4jLog15: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 started15:55:04.515 INFO  [paxweb-config-1-thread-1] No ALPN class available15:55:04.515 INFO  [paxweb-config-1-thread-1] HTTP/2 not available, creating standard ServerConnector for Http15: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 service15:55:04.552 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.examples.karaf-docker-example-app [15]] to http service15:55:04.564 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer to ServletContainerInitializers15:55:04.564 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer15:55:04.565 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer to ServletContainerInitializers15:55:04.618 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer15:55:04.619 INFO  [paxweb-config-1-thread-1] will add org.apache.jasper.servlet.JasperInitializer to ServletContainerInitializers15:55:04.619 INFO  [paxweb-config-1-thread-1] Skipt org.apache.jasper.servlet.JasperInitializer, because specialized handler will be present15: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 JasperInitializer15: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=node015:55:04.802 INFO  [paxweb-config-1-thread-1] No SessionScavenger set, using defaults15:55:04.803 INFO  [paxweb-config-1-thread-1] node0 Scavenging every 600000ms15: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-b1315: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 @1539ms15: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 karafc05645357cd17a0828ef7acaf619071cc3c94f316ca605217890371c0c1e4ab0

We can see our container running:

$ docker psCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                                   NAMESc05645357cd1        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 mykarafdocker logs mykarafkaraf: Ignoring predefined value for KARAF_HOMEMar 21, 2019 3:56:45 PM org.apache.karaf.main.Main launchINFO: Installing and starting initial bundlesMar 21, 2019 3:56:45 PM org.apache.karaf.main.Main launchINFO: All initial bundles installed and set to startMar 21, 2019 3:56:45 PM org.apache.karaf.main.Main$KarafLockCallback lockAcquiredINFO: Lock acquired. Setting startlevel to 10015:56:45.831 INFO  [FelixStartLevel] Logging initialized @946ms to org.eclipse.jetty.util.log.Slf4jLog15: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 started15:56:46.055 INFO  [paxweb-config-1-thread-1] No ALPN class available15:56:46.055 INFO  [paxweb-config-1-thread-1] HTTP/2 not available, creating standard ServerConnector for Http15: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 service15:56:46.093 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer to ServletContainerInitializers15:56:46.093 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer15:56:46.094 INFO  [paxweb-config-1-thread-1] will add org.apache.jasper.servlet.JasperInitializer to ServletContainerInitializers15:56:46.094 INFO  [paxweb-config-1-thread-1] Skipt org.apache.jasper.servlet.JasperInitializer, because specialized handler will be present15:56:46.094 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer to ServletContainerInitializers15:56:46.132 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer15: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 JasperInitializer15: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=node015:56:46.273 INFO  [paxweb-config-1-thread-1] No SessionScavenger set, using defaults15:56:46.274 INFO  [paxweb-config-1-thread-1] node0 Scavenging every 660000ms15: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-b1315: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 @1444ms15:56:46.326 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.ops4j.pax.web.pax-web-extender-whiteboard [48]] to http service15: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 mykarafmykaraf

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 svcNAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGEkubernetes   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: v1kind: Podmetadata:  name: karaf-docker-example-distspec:  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 podNAME                        READY   STATUS    RESTARTS   AGEkaraf-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 !

Comments

Popular posts from this blog

Quarkus and "meta" extension

Getting started with Apache Karaf Minho

Apache Karaf Minho and OpenTelemetry