Apache Karaf : Docker, Kubernetes

Apache Karaf : Docker, Kubernetes

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)

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 a docker on your machine, the compilation creates at least one Dockerfile. By default, the name of the docker's image is karaf. You can also pass the name of the image using the configuration imageName :

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

You can use this file 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 dockerprofile, you have a karaf Docker image ready:

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

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

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

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.hen 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:

yupiik-article-blog-apache-karaf-docker-kubernetes-2

Then, 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:

yupiik-article-blog-apache-karaf-docker-kubernetes-3

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

Using ECS

ECS directly run docker containers (tasks).

We create a ECS cluster:

yupiik-article-blog-apache-karaf-docker-kubernetes-4

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:

yupiik-article-blog-apache-karaf-docker-kubernetes-9

We can see the logs updated:

yupiik-article-blog-apache-karaf-docker-kubernetes-10

We can update the service to have multiple containers running:

yupiik-article-blog-apache-karaf-docker-kubernetes-11
yupiik-article-blog-apache-karaf-docker-kubernetes-12

We can see the service using 5 instances now:

yupiik-article-blog-apache-karaf-docker-kubernetes-13

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

Using EKS

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

yupiik-article-blog-apache-karaf-docker-kubernetes-14

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:

yupiik-article-blog-apache-karaf-docker-kubernetes-15

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-ct
      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, the Apache Karaf team is working to provide better tooling for dev and devops.

Stay tuned !

Blocked in your roadmaps?

Would you like to train your teams?

en_GBEnglish (UK)
fr_FRFrançais en_GBEnglish (UK)