Follow this tutorial to get started with Java 11 and Vert.x on Kubernetes with OpenFaaS without worrying about mastering YAML or having to optimize a lengthy Docker build.

Before we start

You’ll need access to the following:

  • An Intel computer or a remote cluster
  • Kubernetes - install with your preferred local tooling such as KinD, minikube, or k3d. Or use a cloud service like Amazon EKS, GKE or DigitalOcean Kubernetes. A single VM running k3s is also fine.
  • OpenFaaS - we’ll install OpenFaaS in the guide using a developer setup, you can read past blog posts and the documentation for how to best tune your setup for production

Tutorial

We’ll first of all get OpenFaaS installed using the easiest way possible. Then we’ll build two different functions, one will use a function-like template called java11 and the other java11-vert-x will use Vert.x from the Eclipse Foundation. Both OpenFaaS templates are based upon Debian Linux and have built-in support for external dependencies from artifact repositories such as jCenter. The chosen build system for the templates is Gradle, so that you can see a worked example and start making use of your Kubernetes clusters to solve real problems.

OpenFaaS templates are fully customisable and you can fork them and update to use Maven, or AdoptOpenJDK, if you wish. See the link at the end of the post.

Get OpenFaaS

Make sure that you have the Kubernetes CLI (kubectl) available.

Download arkade, which is an installer for helm charts for any Kubernetes cluster. We will install OpenFaaS using arkade install and the OpenFaaS helm chart:

curl -sSLf https://get.arkade.dev | sudo sh

Now install openfaas:

arkade install openfaas

You can also customise values from the helm chart’s README by passing in --set, for instance, or by using a user-friendly flag shown below:

Install openfaas

Usage:
  arkade install openfaas [flags]

Examples:
  arkade install openfaas --loadbalancer

Flags:
  -a, --basic-auth                    Enable authentication (default true)
      --clusterrole                   Create a ClusterRole for OpenFaaS instead of a limited scope Role
      --direct-functions              Invoke functions directly from the gateway (default true)
      --function-pull-policy string   Pull policy for functions (default "Always")
      --gateways int                  Replicas of gateway (default 1)
  -h, --help                          help for openfaas
  -l, --load-balancer                 Add a loadbalancer
  -n, --namespace string              The namespace for the core services (default "openfaas")
      --operator                      Create OpenFaaS Operator
      --pull-policy string            Pull policy for OpenFaaS core services (default "IfNotPresent")
      --queue-workers int             Replicas of queue-worker (default 1)
      --set stringArray               Use custom flags or override existing flags 
                                      (example --set=gateway.replicas=2)
      --update-repo                   Update the helm repo (default true)

At the end of the installation you’ll get instructions for how to:

  • install the OpenFaaS CLI (faas-cli)
  • port-forward the gateway to your local machine
  • and to log-in using faas-cli login

If you lose this information just type in arkade info openfaas at any time.

Example 1) The java11 function

Create a new function named find-a-quote:

faas-cli new --lang java11 \
  find-a-quote

OpenFaaS builds immutable Docker or OCI-format images, so you will need to push your image to a registry such as the Docker Hub, you can edit your find-a-quote.yml file and add your Docker Hub username like image: alexellis2/find-a-quote:latest.

If you’re new to working with Docker and Kubernetes, then I would recommend taking up the workshop listed at the end of this tutorial which explains all of the above in detail.

You’ll now get the following familiar Java file-system with a place for unit tests and for your function’s code.

├── find-a-quote
│   ├── build.gradle
│   ├── gradle
│   │   └── wrapper
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── settings.gradle
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── openfaas
│       │               └── function
│       │                   └── Handler.java
│       └── test
│           └── java
│               └── HandlerTest.java
└── find-a-quote.yml

This is the handler, which you can customise.

package com.openfaas.function;

import com.openfaas.model.IHandler;
import com.openfaas.model.IResponse;
import com.openfaas.model.IRequest;
import com.openfaas.model.Response;

public class Handler implements com.openfaas.model.IHandler {

    public IResponse Handle(IRequest req) {
        Response res = new Response();
	    res.setBody("Hello, world!");

	    return res;
    }
}

We’re going to query QuoteGarden and use the query-string to form a URL https://quote-garden.herokuapp.com/quotes/search/:query

This template uses Gradle, and we’ll need to add a dependency to fetch HTTP pages such as okhttp.

Edit build.gradle, add a dependency:

dependencies {
    // This dependency is exported to consumers, that is to say found on their compile classpath.
    api 'org.apache.commons:commons-math3:3.6.1'

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
    implementation 'com.google.guava:guava:23.0'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'

    compile project(':model')

    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
    implementation 'com.squareup.okio:okio:1.14.1'
}

Let’s see the new code for the Handler.java file.

First we need to get the “query-string” from the IRequest in the function handler, the interface looks like this:

public interface IRequest {
    String getBody();
    Map<String, String> getHeaders();
    String getHeader(String key);
    String getQueryRaw();
    Map<String, String> getQuery();
    String getPathRaw();
    Map<String, String> getPath();
}

In the URL we will pass a querystring of ?q=phrase for our search, so let’s see what that looks like:

package com.openfaas.function;

import com.openfaas.model.IHandler;
import com.openfaas.model.IResponse;
import com.openfaas.model.IRequest;
import com.openfaas.model.Response;
import java.util.Map;

import java.io.IOException;

import okhttp3.OkHttpClient;

public class Handler implements IHandler {

    public IResponse Handle(IRequest req) {
        IResponse res = new Response();

        try {
            OkHttpClient client = new OkHttpClient();

            Map<String, String> query = req.getQuery();
            String q = query.get("q");

            String url = "https://quote-garden.herokuapp.com/quotes/search/" + q;
            okhttp3.Request request = new okhttp3.Request.Builder()
                .url(url)
                .build();

            okhttp3.Response response = client.newCall(request).execute();
            String ret = response.body().string();
            res.setBody(ret);

        } catch(Exception e) {
            e.printStackTrace();
            res.setBody(e.toString());
        }

        return res;
    }
}

Now we can run a build and deploy our function, but first let’s enable Docker’s new Buildkit container builder which can dramatically reduce the time taken to build images.

export DOCKER_BUILDKIT=1
faas-cli up -f find-a-quote.yml

My build took around 15s to complete on a modest Intel i7 micro-PC.

You’ll be given a URL and Kubernetes will have already pulled the image and started a Pod for your function.

kubectl get deploy -n openfaas-fn -o wide
curl -sSLf http://192.168.0.26:31112/function/find-a-quote?q=tree | jq

In the example above I used the jq utility to format the output, it looks like there were 10 results for “tree”.

{
  "count": 18,
  "results": [
    {
      "_id": "5d91b45d9980192a317c8acc",
      "quoteText": "Notice that the stiffest tree is most easily cracked, while the bamboo or willow survives by bending with the wind.",
      "quoteAuthor": "Bruce Lee"
    },
    {
      "_id": "5d91b45d9980192a317c8a62",
      "quoteText": "Notice that the stiffest tree is most easily cracked, while the bamboo or willow survives by bending with the wind.",
      "quoteAuthor": "Bruce Lee"
    }
  ]
}

As an extension of this example, why don’t you customise the code to return a random index of the quotes found? You’ll also want to find yourself a JSON parsing library and then to add it to your build.gradle file.

In my blog post Java comes to OpenFaaS from 2018, I used Gson from Google. You’ll find an example of how to use the library in that post.

Example 2) The java11-vert-x service

In this example we’ll build a service using Vert.x which determine the latest download URL for a GitHub project. We’ll set the name and owner of the project via the deployment YAML file, rather than taking it in via the query string.

Why do this? Well downloading binaries from GitHub’s releases page is a common task that needs to be automated in many workflows, unfortunately the GitHub API that gives this data back is very heavily rate-limited. Fortunately there’s a work-around to send a HTTP HEAD request to the GitHub HTTP server instead.

faas-cli new --lang java11-vert-x \
  github-release-finder

Edit github-release-finder.yml and add the following for your service:

    environment:
      owner: openfaas
      repo: faas-cli

We can read this deployment data at runtime using System.getenv(), for confidential data like API tokens, we would use a Kubernetes secret created via faas-cli secret create or kubectl.

Here’s what our basic Handler.java file looks like with the Vert.x template:

package com.openfaas.function;

import io.vertx.ext.web.RoutingContext;
import io.vertx.core.json.JsonObject;

public class Handler implements io.vertx.core.Handler<RoutingContext> {

  public void handle(RoutingContext routingContext) {
    routingContext.response()
      .putHeader("content-type", "application/json;charset=UTF-8")
      .end(
        new JsonObject()
          .put("status", "ok")
          .encodePrettily()
      );
  }
}

You can see that we have the ability to take much more control over the HTTP request and response and to use middleware.

Let’s update the Handler with my sample code:

package com.openfaas.function;

import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.core.json.JsonObject;

import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;

public class Handler implements io.vertx.core.Handler<RoutingContext> {

  public void handle(RoutingContext routingContext) {

    WebClientOptions options = new WebClientOptions();
    options.setFollowRedirects(false);

    WebClient client = WebClient.create(routingContext.vertx(), options);

    String repo = System.getenv("repo");
    String owner = System.getenv("owner");

    client
      .head(443, "github.com", "/"+owner+"/"+repo+"/releases/latest")
      .ssl(true)
      .send(ar -> {
        if (ar.succeeded()) {
          HttpResponse<Buffer> response = ar.result();

          System.out.println("Received response with status code " + response.statusCode());
          String location = response.getHeader("Location");

          routingContext.response()
          .putHeader("content-type", "application/json;charset=UTF-8")
          .end(
            new JsonObject()
              .put("releaseUrl", location)
              .encodePrettily()
          );

        } else {
          System.out.println("Something went wrong " + ar.cause().getMessage());

          routingContext.response()
          .putHeader("content-type", "application/json;charset=UTF-8")
          .end(
            new JsonObject()
              .put("releaseUrl", "")
              .encodePrettily()
          );

        }
      });
  }
}

I’m using the Vert.x WebClient instead of okhttp in this example to show you the versatility of the OpenFaaS templates.

Now update your build.gradle file again and add in the following for io.vertx:vertx-web-client:3.8.5:

dependencies {
    // Vert.x project
    compile 'io.vertx:vertx-web:3.5.4'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'    

    compile 'io.vertx:vertx-web-client:3.8.5'
}

Now run faas-cli up again:

faas-cli up -f github-release-finder.yml

My build took around 15s with Buildkit enabled.

I worked with a customer earlier in the week who was incurring a massive 2m30s for every rebuild of his simple Springboot API. We can enjoy a much faster build, even when adding a framework like Vert.x.

Let’s try the function:

curl http://127.0.0.1:8080/function/github-release-finder ; echo
{
  "releaseUrl" : "https://github.com/openfaas/faas-cli/releases/tag/0.11.7"
}

As an extension to the task, why don’t you edit the environment section of your github-release-finder.yml file and select a different repo like alexellis/arkade, or the main Kubernetes repo.

Wrapping up

In a short period of time we were able to build a function and a Vert.x service using OpenFaaS and then to deploy that to a Kubernetes cluster of our choosing. We didn’t have to worry about hiring a DevOps expert to hand-craft lengthy YAML files or to tune our Dockerfiles (a common cause of contention for Java development teams).

So what does OpenFaaS offer over “vanilla Kubernetes”?

When deployed to Kubernetes, OpenFaaS offers an application stack just like MEAN, LAMP or JAMStack, you can watch my video from KubeCon on the PLONK stack which goes into a bit more detail.

Here’s an overview from 10,000ft:

  • community-supported and (for customers, commercially-supported) templates for popular languages, optimized and hand-tuned
  • a template store ecosystem to find community templates, see faas-cli template store list
  • optional auto-scaling from 0 to many and back down to zero again
  • a simple API to deploy to Kubernetes with best practices, which let your team ship changes quickly
  • the ability to ship functions or services, without worrying about all the Kubernetes YAML files that would normally be a concern
  • a welcoming and helpful community of developers, sponsors, and end-users with over 2.5k members and 20k GitHub stars

Try this next

Perhaps next you’d like to move to a managed Kubernetes service, or add a TLS certificate and a custom domain to your OpenFaaS functions?

Find out more about OpenFaaS and Vert.x

Acknowledgements: Thank you to Paulo Lopes from the Vert.x project for his input and guidance for this blog post and for the OpenFaaS Java templates.

Alex Ellis

Founder of @openfaas.