Running Dropwizard Applications on Kubernetes

Dropwizard is a popular Java library for building RESTful web services. I am using this library for my user management application, Thunder, and it makes building the API much easier and more enjoyable. When it comes to deploying applications, Kubernetes is the new hot product. I wanted to run my Dropwizard application on Kubernetes, and decided to put together a guide for anyone else looking to do the same.

Building the Application

I will assume that if you’re reading this guide, you already have a Dropwizard project ready to deploy. If not, follow the getting started guide for v1.3.1 of Dropwizard. You can also check out my project, Thunder, for a working example that you can use. Just clone that repository to your computer using git.

Using Maven, build the application into a packaged jar that you can run anywhere.

$ mvn package

Create the Docker image

In order to run your application on Kubernetes, you need a Docker image with your application. So, let’s build one! Create a new file named Dockerfile in the base directory of your project (i.e. next to your parent pom.xml) and add the following content.

1
2
3
4
5
6
7
8
FROM openjdk:8-jre-alpine

LABEL maintainer "YOUR NAME youremail@example.com"

COPY ./application/target/application-*.jar application.jar

EXPOSE 80 81
ENTRYPOINT ["java", "-jar", "/application.jar", "server", "/home/config/config.yaml"]

Let’s go through this file line by line. First, we specify what existing Docker image to build this image FROM. We are using the openjdk:8-jre-alpine image since we are running our application using Java 8. If you are using a different Java version, check the available images. We use the Alpine flavor in order to have a smaller Docker image.

Next, we define the maintainer LABEL which provides your information. This line is optional.

Then, in order to get your application into the Docker image, we need to COPY it in. This line copies the jar that we built into the image. If your jar file is named differently, you will need to modify this line.

After copying the application, we have to EXPOSE the ports that our application will be listening on. If you set up your Dropwizard configuration to listen on different ports, change those here.

Finally, we define the ENTRYPOINT for running the image. This is the command to start your Dropwizard application. Notice that we reference config.yaml, which does not yet exist in our image. We will take care of adding the configuration file when we deploy the application. We don’t want to bake it into the image because our configuration could change at any time.

Now, let’s build our image. Make sure you have Docker installed. If you don’t, go here to install. Run the following command to tag and build your image.

$ sudo docker build -t yourname/application:test .

Feel free to change the tag to anything else. You should replace yourname with your Docker Hub username, and replace application with the name of your application.

Now we will push our image to Docker Hub. If you haven’t yet, create an account on Docker Hub. You can also use a private Docker registry, but I will not cover that in this post. Once you create an account, login on the command line.

$ sudo docker login

After entering your credentials, you’re set to push your image!

$ sudo docker push yourname/application:test

Check your Docker Hub account to verify that your application was pushed! Now that we have our application packed into a Docker image, we can run it using Kubernetes.

Create a Kubernetes Cluster

Kubernetes is growing quickly in popularity, and there are many great guides on how to get started. If you are not familiar with Kubernetes (or K8s), I recommend that you first learn about it and make sure that you are comfortable with the terminology.

To create a cluster, we can use minikube to run one locally. First, we need to install minikube. Minikube requires a hypervisor (i.e. VirtualBox, Hyper-V, or VMWare Fusion) to be installed, so make sure that you follow those directions. Install the hypervisor, then install the command line tools kubectl, then install minikube. The instructions differ based on what OS you are on, so please follow the linked instructions.

Once everything is installed, start the cluster.

$ minikube start

If you do not wish to use minikube locally, and want to jump straight into running in the cloud, follow guides for creating a K8s cluster on one of the following cloud providers:

Create Configuration File

Kubernetes supports ConfigMaps that provide a way to inject your container with configuration data. This is the approach we will use to provide the Dropwizard configuration file to our application.

First, create a new file called application-config.yaml, and add the following content.

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: ConfigMap
metadata:
  name: application-config
data:
  config.yaml: |
    server:
      applicationConnectors:
        - type: http
          port: 80
      adminConnectors:
        - type: http
          port: 81

This is a YAML file that defines a Kubernetes resource. The apiVersion tells Kubernetes what version of the API to use when processing the file. The kind of resource that we will create with this file is a ConfigMap. In the metadata, we define the name of the ConfigMap, and in the data, we define what the configuration actually is. In this case, we want the configuration to be a file called config.yaml. Inside that file, we want to define our Dropwizard configuration. Put your Dropwizard YAML configuration here. For this example, I have just defined the server configuration for Dropwizard, but you can see here for a full example based on my Thunder application.

Now that we have the file, we can create the ConfigMap in our running Kubernetes cluster. Make sure that you are connected to your cluster using kubectl, and then run the command.

$ kubectl apply -f application-config.yaml

You can then verify that it was created by running the get command.

$ kubectl get configmap

You should see the name of your ConfigMap as the output.

Running the Application

Now, we’re ready to deploy our application. Define another file called application-deployment.yaml, and add the following content.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: application
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: application
    spec:
      containers:
        - name: application
          image: yourname/application:test
          imagePullPolicy: Always
          ports:
            - containerPort: 80
            - containerPort: 81
          volumeMounts:
            - name: config-volume
              mountPath: /home/config
          livenessProbe:
            httpGet:
              path: /healthcheck
              port: 81
            initialDelaySeconds: 10
            timeoutSeconds: 1
          readinessProbe:
            httpGet:
              path: /healthcheck
              port: 81
            initialDelaySeconds: 10
            timeoutSeconds: 1
      volumes:
        - name: config-volume
          configMap:
            name: application-config
---
apiVersion: v1
kind: Service
metadata:
  name: application
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: application

This is where everything comes together. We first define a Kubernetes Deployment, which is the main application. Inside the spec, we define that we want 2 replicas of the application running at all times. Then, inside the template spec, we define the containers we want to run and the volumes that we want to create. For the containers, we only want to run one container - the Docker image that we built earlier. We define the image tag from earlier under the image property. We also have to tell Kubernetes that we have a few open ports on the container, 80 and 81. We also define a volumeMount that mounts a volume into the container. This volume will contain our Dropwizard configuration file, and so we specify the path as /home/config, since that is the path we assumed the configuration would exist in our ENTRYPOINT command in our Dockerfile. Finally, we define a livenessProbe that Kubernetes will probe to ensure our application is alive (and replace it if it is dead), and a readinessProbe that Kubernetes will probe before starting to serve traffic to that Pod. In both cases, we tell Kubernetes to probe the Dropwizard /healthcheck endpoint on the admin port, port 81.

Lastly, we define a volume called config-volume that holds the ConfigMap we created earlier. As mentioned in the paragraph above, we mount this volume into our container so that our application has access to the data.

The second resource that is defined is a Service. This acts as a load balancer, and Kubernetes will manage the relationship between our multiple running containers and the load balancer to spread traffic between them. We can specify the open ports on the load balancer, and what the targetPort should be (the port to proxy the traffic to on the container). The selector defines which set of containers to balance across, so we use the app: application label that we defined in the Deployment.

Note that the Service type LoadBalancer only works in the context of clusters running on cloud providers. If we are using minikube, we should use the NodePort type to expose the service on the IP of the running Node.

Let’s create the Deployment and Service in one go:

$ kubectl apply -f application-deployment.yaml

And your application is up! If you are using the LoadBalancer, wait a few minutes and then run the following command to get the external IP of the load balancer.

$ kubectl get svc application

Once you have the external IP, run a test using curl. For example, if your application has a /hello endpoint:

$ curl 56.77.45.34/hello

If everything worked, you will get your HTTP response back! If it doesn’t work, you can troubleshoot by checking to make sure your containers are running:

$ kubectl get deployment application

If they are not running, you should check the Dropwizard logs to see what went wrong. Get the name of one of your Pods:

$ kubectl get pods 

Find one of the application-* Pods (for example, application-6cffc7bb7-gjg5q), and then run:

$ kubectl logs application-6cffc7bb7-gjg5q

This will show you all of the logs output by Dropwizard.

Conclusion

Now you’ve learned how to containerize your Dropwizard application, keep your configuration separate, and create a Kubernetes ConfigMap and Deployment for your application. Deploying your Dropwizard application in this way will allow you to iterate and deploy quickly and effortlessly. Enjoy your containerized Dropwizard application, and reach out (rohannagar11@gmail.com) or leave a comment if you have any problems or questions.