<!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
-->

# Apache Karaf Docker example

## Abstract

This example is a complete example how to easily create a Karaf powered application and package as a docker image, ready
to run anyway.

This example provides two kind of distributions:

* static distribution is very similar to Spring Boot approach, but supporting all Karaf powerful features.  The Karaf static (almost immutable) is a resolution performed at build time, and
we start all features (bundles set) at startup without the resolver. It's super fast, light and straight forward.
* dynamic distribution is applications container approach. It's the same kind of distribution as the Apache Karaf "vanilla" one: Karaf starts with a set of "boot" features. Then, you can deploy
new applications (at runtime) on the runtime instance.

## Artifacts

The project example is composed of three modules:

* `karaf-docker-example-app` is a regular Karaf application module. For this example, we implement a Servlet. We use the Karaf Maven plugin to automatically create a features XML that we will use for the distribution.
* `karaf-docker-example-static-dist` doesn't contain any code, just a `pom.xml`. This module creates a Karaf static distribution (almost immutable) ready to run, provide a turnkey Dockerfile and optionally create the corresponding Docker image.
* `karaf-docker-example-dynamic-dist` doesn't contain any code, just a `pom.xml`. This modules creates a Karaf dynamic distribution (applications container) ready to run, provide a turnkey Dockerfile and optionally create the corresponding Docker image.

### App

The `karaf-docker-example-app` is a regular Karaf application (packaged as a bundle). It uses SCR annotation to register
a `Servlet` service that the `http-whiteboard` feature will deploy.

It's possible to create a bunch of `app` modules like this one and include in several runtime.

To include the apps in the runtime (dist), we need a features XML file.

In the example, we simply use the Karaf Maven plugin `features-generate-descriptor` goal to automatically create the
features XML.

### Runtimes (distributions)

The `dist` modules create Karaf runtimes embedding the app.

#### Dynamic distribution

The `dynamic` distribution is the approach used by Apache Karaf vanilla distribution: it's a applications container approach.

You start the Karaf instance with "boot" applications, and then you can (at runtime) deploy new applications in the running instance. You can also update
the configuration, undeploy applications, etc at runtime.

We use the `karaf-maven-plugin` to create the distribution automatically:

1. the `assembly` goal creates the Karaf runtime filesystem, using the dependencies from the `pom.xml`
2. the `archive` goal creates an archive (tar.gz, zip) containing the Karaf runtime filesystem
3. the `dockerfile` goal creates a Dockerfile ready to create a Karaf Docker image
4. optionally, the `docker` goal can take the Dockerfile and directly use `docker` to create the Karaf Docker image.

#### Static distribution

On the other hand `static` distribution is a completely different approach where the resolution is made at build time.

We use the `karaf-maven-plugin` to create the distribution automatically:

1. the `assembly` goal creates the Karaf runtime filesystem, using the dependencies from the `pom.xml`
2. the `archive` goal creates an archive (tar.gz, zip) containing the Karaf runtime filesystem
3. the `dockerfile` goal creates a Dockerfile ready to create a Karaf Docker image
4. optionally, the `docker` goal can take the Dockerfile and directly use `docker` to create the Karaf Docker image.

The difference between dynamic and static distributions is just the configuration of the project and the `karaf-maven-plugin`.

## Build

The build uses Apache Maven. Simply use:

```
$ mvn clean install
```

You will find the runtime in `karaf-docker-example-dist/target` folder (tar.gz and zip), and also the `Dockerfile`.

You can eventually directly create the Docker image using the `docker` profile (assuming `docker` is installed on your machine):

```
$ mvn clean install -Pdocker
```

## Usage

### Static

Locally you can use `karaf-docker-example-static-dist/target/assembly`, with the `karaf run`:

```
$ karaf-docker-example-static-dist/target/assembly/bin/karaf run
```

You can also use the zip or tar.gz archive:

```
$ cd karaf-docker-example-static-dist/target
$ tar zxvf karaf-docker-example-static-dist-*.tar.gz
$ cd karaf-docker-example-static-dist*/bin
$ ./karaf run
```

You can build the docker image using the generated `Dockerfile`:

```
$ cd karaf-docker-example-static-dist/target
$ docker build -t mykaraf .
Sending build context to Docker daemon  78.08MB
Step 1/7 : FROM openjdk:8-jre
 ---> d60154a7d9b2
Step 2/7 : ENV KARAF_INSTALL_PATH /opt
 ---> Using cache
 ---> ff77d7ccbfe3
Step 3/7 : ENV KARAF_HOME $KARAF_INSTALL_PATH/apache-karaf
 ---> Using cache
 ---> 0ebb21305f76
Step 4/7 : ENV PATH $PATH:$KARAF_HOME/bin
 ---> Using cache
 ---> 31bbe4c15205
Step 5/7 : COPY assembly $KARAF_HOME
 ---> Using cache
 ---> a9a88b63244e
Step 6/7 : EXPOSE 8101 1099 44444 8181
 ---> Using cache
 ---> 274550b95b48
Step 7/7 : CMD ["karaf", "run"]
 ---> Using cache
 ---> 7dbc9b63c058
Successfully built 7dbc9b63c058
Successfully tagged mykaraf:latest
```

If you used the `docker` profile (`-Pdocker`) during the Maven build, you have the `karaf` docker image:

```
$ cd karaf-docker-example-static-dist
$ mvn clean install -Pdocker
```

```
$ docker images
REPOSITORY                                 TAG                 IMAGE ID            CREATED             SIZE
karaf-docker-example-static-dist           latest              226223b45024        6 seconds ago       463MB
```

You can use the docker image very easily (wherever you want):

```
$ docker run --name mykaraf -p 8181:8181 mykaraf
```

You can start the docker container in daemon mode:

```
$ docker run --name mykaraf -p 8181:8181 -d mykaraf
```

We can see our docker container running:

```
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                     NAMES
d5f14a025286        karaf               "karaf run"         4 minutes ago       Up 4 minutes        1099/tcp, 8101/tcp, 8181/tcp, 44444/tcp   mykaraf
```

Then you can see the logs using `docker logs`:

```
$ docker logs mykaraf
karaf: Ignoring predefined value for KARAF_HOME
Mar 21, 2019 11:09:54 AM org.apache.karaf.main.Main launch
INFO: Installing and starting initial bundles
Mar 21, 2019 11:09:54 AM org.apache.karaf.main.Main launch
INFO: All initial bundles installed and set to start
Mar 21, 2019 11:09:54 AM org.apache.karaf.main.Main$KarafLockCallback lockAcquired
INFO: Lock acquired. Setting startlevel to 100
11:09:54.871 INFO  [FelixStartLevel] Logging initialized @811ms to org.eclipse.jetty.util.log.Slf4jLog
11:09:54.882 INFO  [FelixStartLevel] EventAdmin support is not available, no servlet events will be posted!
11:09:54.883 INFO  [FelixStartLevel] LogService support enabled, log events will be created.
11:09:54.885 INFO  [FelixStartLevel] Pax Web started
11:09:55.110 INFO  [paxweb-config-1-thread-1] No ALPN class available
11:09:55.111 INFO  [paxweb-config-1-thread-1] HTTP/2 not available, creating standard ServerConnector for Http
11:09:55.127 INFO  [paxweb-config-1-thread-1] Pax Web available at [0.0.0.0]:[8181]
11:09:55.131 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.ops4j.pax.web.pax-web-extender-whiteboard [48]] to http service
11:09:55.143 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.examples.karaf-docker-example-app [15]] to http service
11:09:55.153 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer to ServletContainerInitializers
11:09:55.154 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer
11:09:55.154 INFO  [paxweb-config-1-thread-1] will add org.apache.jasper.servlet.JasperInitializer to ServletContainerInitializers
11:09:55.155 INFO  [paxweb-config-1-thread-1] Skipt org.apache.jasper.servlet.JasperInitializer, because specialized handler will be present
11:09:55.155 INFO  [paxweb-config-1-thread-1] will add org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer to ServletContainerInitializers
11:09:55.207 INFO  [paxweb-config-1-thread-1] added ServletContainerInitializer: org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer
11:09:55.241 INFO  [paxweb-config-1-thread-1] registering context DefaultHttpContext [bundle=org.apache.karaf.examples.karaf-docker-example-app [15], contextID=default], with context-name:
11:09:55.251 INFO  [paxweb-config-1-thread-1] registering JasperInitializer
11:09:55.282 INFO  [paxweb-config-1-thread-1] No DecoratedObjectFactory provided, using new org.eclipse.jetty.util.DecoratedObjectFactory[decorators=1]
11:09:55.354 INFO  [paxweb-config-1-thread-1] DefaultSessionIdManager workerName=node0
11:09:55.354 INFO  [paxweb-config-1-thread-1] No SessionScavenger set, using defaults
11:09:55.355 INFO  [paxweb-config-1-thread-1] node0 Scavenging every 600000ms
11:09:55.364 INFO  [paxweb-config-1-thread-1] Started HttpServiceContext{httpContext=DefaultHttpContext [bundle=org.apache.karaf.examples.karaf-docker-example-app [15], contextID=default]}
11:09:55.369 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
11:09:55.403 INFO  [paxweb-config-1-thread-1] Started default@437b18f2{HTTP/1.1,[http/1.1]}{0.0.0.0:8181}
11:09:55.403 INFO  [paxweb-config-1-thread-1] Started @1347ms
11:09:55.405 INFO  [paxweb-config-1-thread-1] Binding bundle: [org.apache.karaf.http.core [16]] to http service
```

Now, in a web brower, you can test the Servlet using [http://localhost:8181/servlet-example].

[NOTE]
===
Update the host if you run the container somewhere else, like on a cloud.
===

You can push this docker image wherever you want, on a cloud provider for instance.

We can see the logs updated:

```
$ docker logs mykaraf
...
11:23:39.064 INFO  [qtp810811468-37] Client 172.17.0.1 request received on http://localhost:8181/servlet-example
```

### Dynamic

Locally you can use `karaf-docker-example-dynamic-dist/target/assembly`, with the `karaf`:

```
$ karaf-docker-example-dynamic-dist/target/assembly/bin/karaf
```

You can also use the zip or tar.gz archive:

```
$ cd karaf-docker-example-dynamic-dist/target
$ tar zxvf karaf-docker-example-dynamic-dist-*.tar.gz
$ cd karaf-docker-example-dynamic-dist*/bin
$ ./karaf
```

You can build the docker image using the generated `Dockerfile`:

```
$ cd karaf-docker-example-dynamic-dist/target
$ docker build -t mykaraf .
Sending build context to Docker daemon  102.5MB
Step 1/7 : FROM openjdk:8-jre
 ---> 19c48cc84cc6
Step 2/7 : ENV KARAF_INSTALL_PATH /opt
 ---> Running in 8a9a42db2395
Removing intermediate container 8a9a42db2395
 ---> 45f002fb0def
Step 3/7 : ENV KARAF_HOME $KARAF_INSTALL_PATH/apache-karaf
 ---> Running in c25e2a10d78e
Removing intermediate container c25e2a10d78e
 ---> ce1f0d7bdb0a
Step 4/7 : ENV PATH $PATH:$KARAF_HOME/bin
 ---> Running in 0fd126ff3a5f
Removing intermediate container 0fd126ff3a5f
 ---> 3ac6aaa5b072
Step 5/7 : COPY assembly $KARAF_HOME
 ---> 9a6a003d9845
Step 6/7 : EXPOSE 8101 1099 44444 8181
 ---> Running in 8e24a41487fb
Removing intermediate container 8e24a41487fb
 ---> 5643c771527c
Step 7/7 : CMD ["karaf"]
 ---> Running in 0e35ac21467d
Removing intermediate container 0e35ac21467d
 ---> d0cbfa94125c
Successfully built d0cbfa94125c
Successfully tagged mykaraf:latest

```

If you used the `docker` profile (`-Pdocker`) during the Maven build, you have the `karaf` docker image:

```
$ cd karaf-docker-example-dynamic-dist
$ mvn clean install -Pdocker
```

```
$ docker images
REPOSITORY                                 TAG                 IMAGE ID            CREATED              SIZE
karaf-docker-example-dynamic-dist          latest              38628803d8b2        3 seconds ago        479MB
```

You can use the docker image very easily (wherever you want):

```
$ docker run -i -t --name mykaraf -p 8181:8181 mykaraf
karaf: Ignoring predefined value for KARAF_HOME
        __ __                  ____      
       / //_/____ __________ _/ __/      
      / ,<  / __ `/ ___/ __ `/ /_        
     / /| |/ /_/ / /  / /_/ / __/        
    /_/ |_|\__,_/_/   \__,_/_/         

  Apache Karaf (4.2.5)

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()>  
```

We started here with the shell console. You can directly type Karaf shell commands here and interact the running
Karaf instance. 

[NOTE]
==
You can also override the startup command to use `karaf server`.
==

Now, in a web brower, you can test the Servlet using [http://localhost:8181/servlet-example].

we can see the request performed on our running Karaf instance:

```
karaf@root()> log:display
08:43:36.497 INFO [qtp1056947824-99] Client 172.17.0.1 request received on http://localhost:8181/servlet-example
```