Kubernetes and OpenFAAS


We can characterize a microservice architecture as a monolithic application decomposed into many smaller independent applications. Each microservice is responsible for a subset of the monolith's functionality and executes in a separate process.

In our reference application, each microservice maps to a specific domain's functional boundaries and exposes its publicly accessible methods via the service's REST API. This approach allows independent management, maintenance, and scaling of individual microservices. This independence increases development and deployment velocities and provides more granular scalability than we could achieve from a classic monolithic application. With finer-grained scalability, we can better manage resource consumption by elastically scaling individual service in response to their load.

The reference application's public API is composed of a collection of domain-specific microservices, each exposing a REST API endpoint employing synchronous Request/Response methods. These synchronous methods are called by the application's consumers using the traditional HTTP Requests/ HTTP Response pattern.

Each microservice is packaged as a Docker container to enable deployment to a Kubernetes cluster for application orchestration. We demonstrated this for both a local ( Microk8s) and cloud ( Amazon Web Service's Elastic Kubernetes Service (EKS)) Kubernetes cluster.

When many technology professionals hear the term microservices, this is often the architecture they conjure in their minds. However, this is not the only approach.


A function is a packaged sequence of instructions that performs a specific task. It is the smallest unit of computation exposed by a microservice. Up to this point, we have built our microservices from a collection of related domain-specific functions and packaged them as a unit. An alternate approach is to decompose our application microservices beyond domain boundaries down to the individual function (or event) level.

Single function microservices provide us with the highest degree of granularity. This increase in granularity gives us more control over an application's resource consumption, availability, and scalability. However, this increase in granularity comes with a corresponding increase in service management, discovery, and API Gateway overhead.

Function-As-A-Service (Faas)

To avoid the hassle of managing the administrative overhead, many cloud providers offer services that provide a runtime-framework that hosts these single-function microservices. These products are often referred to as Function-as-a-Service as well as the somewhat misleading Serverless Computing. Both terms refer to the idea of a standalone function deployed within a runtime-framework managed by a FaaS provider. The runtime framework provides the deployed function on-demand handling both function invocation and per-use billing.

This approach can often be more cost-effective when functions are called infrequently. Rather than paying for underutilized computing resources, the FaaS provider only charges for the computing resources used for the duration of the function's execution. Unlike the domain-specific microservices we encountered in the reference application, functions are generally event-driven and are better suited to short-lived, single-purpose, stateless, event-driven operations.

Additionally, FaaS simplifies deployment and scaling by delegating the management of the function's runtime environment and handling elastic scaling to the provider. The developer focuses solely on the development of the function.

A hybrid approach

When developing an application, the choice to use domain-specific microservices or functions is not mutually exclusive. It is often advantageous to employ both approaches. A good rule of thumb is to employ domain-specific microservices for logically-related functions. Use functions for simple microservices that perform a single action in response to an event. While this may slightly increase the architectural complexity, it can significantly decrease resource consumption leading to lower operational costs.

FaaS and Vendor Lock-in

At this point, you may be wondering why we haven't mentioned any specific FaaS providers. All of the major cloud providers have some form of FaaS offering. Amazon Web Services has AWS Lambda, Microsoft Azure has Azure Functions, Google Cloud has Cloud Functions, and IBM has IBM Cloud Functions. In addition to their FaaS offerings, each vendor also brings the risk of vendor lock-in. Whether it is by a proprietary API or a ight integration on other provider services, leveraging a cloud provider's FaaS offering poses potential complications if and when you need to migrate the application to another cloud provider.

A better way: Kubernetes & Open Source FaaS

We can avoid vendor lock-in by using an open-source FaaS framework deployed on a Kubernetes cluster. Thisapproach gives us a non-proprietary FaaS that we can deploy anywhere we can run Kubernetes. Since Kubernetes runs on all major cloud providers, we now have a portable FaaS platform that can also deploy our REST microservices. There are several open-source FaaS platforms available; however, in this article we will be focusing on OpenFaaS.

What is OpenFaas

OpenFaas is an open-source framework for building serverless functions that can run any CLI-driven program packaged in a Docker container. While most cloud provider FaaS products support a limited set of languages, OpenFaaS allows us to build our functions in whatever language we find most appropriate. We also have several options for deploying OpenFaaS. It can be deployed in standalone mode, in a Docker-Swarm, or as part of a Kubernetes cluster. This flexibility allows it to be deployed in a corporate data center or in a public/private cloud using Kubernetes (which is what we will be doing). OpenFaaS also have a large assortment of event-trigger types to invoke the function. These include CLI, Cron, HTTP/webhooks, Async/NATS streaming, Kafka, Redis, MQTT, and custom event types. Lastly, OpenFaaS provides function autoscaling for you (you can of course tweak it as necessary).

Deploy OpenFaaS on Kubernetes

Before we can begin writing our first OpenFaaS function, we will need to prepare our environment. We will be deploying OpenFaas to the local MicroK8S Kubernetes instance we created in an earlier article. If you don't already have a Microk8S cluster, you can follow the article's directions from From Docker-Compose to Kubernetes - Part I to set up your own cluster.

OpenFaas CLI

Once we have an operational cluster, we will need to install the OpenFaaS CLI. The CLI allows us to interact with OpenFaaS once deployed. To install on Linux or MacOS issue the following command:

 curl -sL https://cli.openfaas.com | sudo sh
To install on Windows, we will be using Git Bash, which provides BASH emulation. Enter the following command:

 curl -sL https://cli.openfaas.com | sh
We can verify the CLI works by invoking it with the help flag:

 faas-cli --help
When installed correctly, you should see the following:

Manage your OpenFaaS functions from the command line

  faas-cli [flags]
  faas-cli [command]

Available Commands:
  auth           Obtain a token for your OpenFaaS gateway
  build          Builds OpenFaaS function containers
  cloud          OpenFaaS Cloud commands
  completion     Generates shell auto completion
  deploy         Deploy OpenFaaS functions
  describe       Describe an OpenFaaS function
  generate       Generate Kubernetes CRD YAML file
  help           Help about any command
  invoke         Invoke an OpenFaaS function
  list           List OpenFaaS functions
  login          Log in to OpenFaaS gateway
  logout         Log out from OpenFaaS gateway
  logs           Tail logs from your functions
  namespaces     List OpenFaaS namespaces
  new            Create a new template in the current folder with the name given as name
  push           Push OpenFaaS functions to remote registry (Docker Hub)
  remove         Remove deployed OpenFaaS functions
  secret         OpenFaaS secret commands
  store          OpenFaaS store commands
  template       OpenFaaS template store and pull commands
  up             Builds, pushes and deploys OpenFaaS function containers
  version        Display the clients version information

      --filter string   Wildcard to match with function names in YAML file
  -h, --help            help for faas-cli
      --regex string    Regex to match with function names in YAML file
  -f, --yaml string     Path to YAML file describing function(s)

Use "faas-cli [command] --help" for more information about a command.

Deploying OpenFaaS with Helm3

Our next step is to deploy OpenFaaS using Helm3. If you haven't installed Helm3 in your MicroK8S cluster, refer to the earlier article Packaging Kubernetes applications with Helm. Now that we have Helm3 installed and a working MicroK8S cluster, we are ready to begin.

We begin by creating two namespaces in our cluster, one for OpenFaas's core services and a second for our functions. We can do this using the following kubectl command:

microk8s kubectl apply -f https://raw.githubusercontent.com/openfaas/faas-netes/master/namespaces.yml
Which applies the following namespaces.yml:


Our MicroK8S cluster now contains two additional namespaces: openfaas and openfaas.

We can now use Helm to add the OpenFaaS chart repository with the following command:

microk8s helm3 repo add openfaas https://openfaas.github.io/faas-netes/ 
We should see that openfaas has been added to our repositories:

"openfaas" has been added to your repositories
We can now deploy the OpenFaaS Helm chart using the following command:

helm repo update \
 && helm upgrade openfaas --install openfaas/openfaas \
    --namespace openfaas  \
    --set functionNamespace=openfaas-fn \
    --set generateBasicAuth=true 
You should see output similar to following:

Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "openfaas" chart repository
Update Complete. ⎈ Happy Helming!⎈ 
Release "openfaas" does not exist. Installing it now.
NAME: openfaas
LAST DEPLOYED: Tue Dec  8 17:16:08 2020
NAMESPACE: openfaas
STATUS: deployed
To verify that openfaas has started, run:

  kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas"
To retrieve the admin password, run:

  echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode)
Included in the helm notes section are instructions for verifying that OpenFaas has started and how to retrieve the admin password. Remember, we are running in MicroK8S!. You will need to include the microk8s prefix to each kubectl command.

To verify OpenFaaS, we use the command:

 microk8s kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas"
You should see output similar to this:

 gateway             1/1     1            1           41s
 prometheus          1/1     1            1           41s
 basic-auth-plugin   1/1     1            1           41s
 nats                1/1     1            1           41s
 alertmanager        1/1     1            1           41s
 queue-worker        1/1     1            1           41s
 faas-idler          1/1     1            1           41s
We can now obtain the password for our OpenFaaS instance using the following command:

 PASSWORD=$(microk8s kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode) && \
 echo "OpenFaaS admin password: $PASSWORD"
You should see a string of characters similar to this (yours will be different):

We now want to get the address of the OpenFaas gateway. We can accomplish this with the following command:

k get svc -n openfaas gateway-external -o wide
You should see something like this:

NAME               TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE   SELECTOR
gateway-external   NodePort   <none>        8080:31112/TCP   14m   app=gateway
We can see from the output that our external gateway is of type NodePort with an IP address of on ports 8080 & 31112. To interact with OpenFaas using the faas-cli, we need to set the OPENFAAS_URL environment variable. We can do this using the following command (you will need to set your Cluster IP address):

We can now finally login using faas-cli:

faas-cli login -g $OPENFAAS_URL -u admin --password <your generated password>
Yu should see output similar to the following:

WARNING! Using --password is insecure, consider using: cat ~/faas_pass.txt | faas-cli login -u user --password-stdin
Calling the OpenFaaS server to validate the credentials...
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.
credentials saved for admin
Let's check that everything is working correctly by calling the following command:

faas-cli version
And you should see output similar to below:

  ___                   _____           ____
 / _ \ _ __   ___ _ __ |  ___|_ _  __ _/ ___|
| | | | '_ \ / _ \ '_ \| |_ / _` |/ _` \___ \
| |_| | |_) |  __/ | | |  _| (_| | (_| |___) |
 \___/| .__/ \___|_| |_|_|  \__,_|\__,_|____/

 commit:  c12d57c39ac4cc6eef3c9bba2fb45113d882432f
 version: 0.12.14

 version: 0.20.2
 sha:     9bbb25e3c7c4cc5cd355edb3a122f8c7812e32db
 commit:  Fix a bug that caused the services list to keep growing

 name:          faas-netes
 orchestration: kubernetes
 version:       0.12.9 
 sha:           c402b912ce21f0bf01bc3aa45ebc330decc41ac5
Your faas-cli version (0.12.14) may be out of date. Version: 0.12.19 is now available on GitHub.
Congratulations, you now have a working instance of OpenFaaS deployed on your MicroK8s cluster!

Building your first function

With OpenFaas, building functions is extremely easy. The faas-cli provides a project scaffolding feature that creates a skeleton project for your function in one of twelve Classic OpenFaaS templates. These templates can generate function projects in csharp, dockerfile, go, java11, java11-vert-x, node, node12, php7, python, python3, python3-debian, ruby. Additionally, while out of scope for this article, OpenFaaS supports custom templates. With custom templates, we can support languages and frameworks not included in the set of Classic templates. For more information on custom templates, check out Going Serverless with OpenFaaS and Golang - Building Optimized Templates.

Generating a Function Project with FAAS-CLI and Java

The goal of our first function example is to reproduce the canonical Hello World program as a function. We will build our first function using the java11 template. Open a terminal and navigate to a directory where you want your function project to reside. In that directory, execute the following command:

faas-cli new hello-world --lang java11 
The output should be similar to this:

2020/10/04 22:01:21 No templates found in current directory.
2020/10/04 22:01:21 Attempting to expand templates from https://github.com/openfaas/templates.git
2020/10/04 22:01:21 Fetched 12 template(s) : [csharp dockerfile go java11 java11-vert-x node node12 php7 python python3 python3-debian ruby] from https://github.com/openfaas/templates.git
Folder: hello-world created.
  ___                   _____           ____
 / _ \ _ __   ___ _ __ |  ___|_ _  __ _/ ___|
| | | | '_ \ / _ \ '_ \| |_ / _` |/ _` \___ \
| |_| | |_) |  __/ | | |  _| (_| | (_| |___) |
 \___/| .__/ \___|_| |_|_|  \__,_|\__,_|____/

Function created in folder: hello-world
Stack file written: hello-world.yml

You have created a function using the java11 template which uses an LTS
version of the OpenJDK.

When the faas-cli executes with the new command, it checks the local templates directory for the referenced --lang template. Since we ran the command from a pristine directory, faas-cli downloaded the Classic OpenFaaS templates. If we list the contents of our directory, we will see that faas-cli created a file and two directories:

hello-world  hello-world.yml  template

Hello-World Directory

The hello-world directory contains the project's source files. If we tree the hello-world directory, we see the contents of the function project:

├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── openfaas
    │               └── function
    │                   └── Handler.java
    └── test
        └── java
            └── HandlerTest.java

10 directories, 8 files

The java11 template creates a Gradle build file for the project and includes the necessary Gradle support files. We also see that the project created the com.openfaas.function Java package that contains a single Handler.java source file.

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 extends com.openfaas.model.AbstractHandler { public IResponse Handle(IRequest req) { Response res = new Response(); res.setBody("Hello, world!"); return res; } }
This Handler class contains the Handle(IRequest req) method which provides the entry point to function. In the Handle method, we create a new Response instance and set our "Hello World" string in the response body. We complete the handler method by returning the response object.


The hello-world.yml contains project metadata used by the faas-cli to build, push, deploy, and invoke the function.

version: 1.0
  name: openfaas
    lang: java11
    handler: ./hello-world
    image: hello-world:latest

The faas-cli new command generates a hello-world.yml file that contains the project's metadata. We must edit this file before the faas-cli can use it to build, package, or deploy the function. . Fortunately, we only need to edit the image name. Change the name value to include the fully qualified name of the Docker image that should be generated.

In our example, we change the image name value to thinkmicroservices/hello-world:latest. The faas-cli uses the image name during the build to generate a local container image. When the faas-cli push command is invoked, it uses your local Docker credentials to push the image to your Docker Hub repository. When the faas-cli deploy command is invoked, the image is retrieved from Docker Hub and deployed as the function.


The templates directory includes the downloaded "Classic OpenFaaS templates" used to generate the function project. If you choose to create a custom OpenFaasS template, you need to place it in the templates directory.

Building the OpenFaaS function

Once you have edited the function to your requirements, you will need to build the function. This process includes any compilation and packaging of the application and the generation of the function's container image. We invoke the build using the following command:

faas-cli build -f hello-world.yml 
We pass the build command along with the name of the project yaml file (e.g., hello-world.yml) into the faas-cli. We will see output similar to this:

Building hello-world.
Clearing temporary build folder: ./build/hello-world/
Preparing: ./hello-world/ build/hello-world/function
Building: thinkmicroservices/hello-world:latest with java11 template. Please wait..
Sending build context to Docker daemon  142.3kB
Step 1/30 : FROM openjdk:11-jdk-slim as builder
 ---> 8206ad34e1d2
Step 2/30 : ENV GRADLE_VER=6.1.1
 ---> Using cache
 ---> 18c06a84f662
Step 3/30 : RUN apt-get update -qqy   && apt-get install -qqy    --no-install-recommends    curl    ca-certificates    unzip
 ---> Using cache
 ---> 05e5916317db
Step 4/30 : RUN mkdir -p /opt/ && cd /opt/     && echo "Downloading gradle.."     && curl -sSfL "https://services.gradle.org/distributions/gradle-${GRADLE_VER}-bin.zip" -o gradle-$GRADLE_VER-bin.zip     && unzip gradle-$GRADLE_VER-bin.zip -d /opt/     && rm gradle-$GRADLE_VER-bin.zip
 ---> Using cache
 ---> 66955611c5b2
Step 5/30 : ENV GRADLE_HOME=/opt/gradle-$GRADLE_VER/
 ---> Using cache
 ---> 368e4ac89c18
 ---> Using cache
 ---> f5e11b760256
Step 7/30 : RUN mkdir -p /home/app/libs
 ---> Using cache
 ---> cf4cb4b714e2
Step 8/30 : ENV GRADLE_OPTS="-Dorg.gradle.daemon=false"
 ---> Using cache
 ---> 6b3bbe9ecccd
Step 9/30 : WORKDIR /home/app
 ---> Using cache
 ---> 37932c680a95
Step 10/30 : COPY . /home/app/
 ---> Using cache
 ---> a32f55434996
Step 11/30 : RUN gradle build
 ---> Using cache
 ---> a1edbd6d6fbc
Step 12/30 : RUN find .
 ---> Using cache
 ---> 154502290bfa
Step 13/30 : FROM openfaas/of-watchdog:0.7.6 as watchdog
 ---> 7580c7c70fce
Step 14/30 : FROM openjdk:11-jre-slim as ship
 ---> e5a708708f23
Step 15/30 : RUN apt-get update -qqy   && apt-get install -qqy    --no-install-recommends    unzip
 ---> Using cache
 ---> ccd0718ad4f2
Step 16/30 : RUN addgroup --system app     && adduser --system --ingroup app app
 ---> Using cache
 ---> 1fd9a0b44dba
Step 17/30 : COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog
 ---> Using cache
 ---> 2852d0ac3661
Step 18/30 : RUN chmod +x /usr/bin/fwatchdog
 ---> Using cache
 ---> 6cd7d7e7da35
Step 19/30 : WORKDIR /home/app
 ---> Using cache
 ---> 42c69362bc95
Step 20/30 : COPY --from=builder /home/app/function/build/distributions/function-1.0.zip ./function-1.0.zip
 ---> Using cache
 ---> faa45df3478a
Step 21/30 : user app
 ---> Using cache
 ---> 36b8d6c447dd
Step 22/30 : RUN unzip ./function-1.0.zip
 ---> Using cache
 ---> 4978c9a3ef65
Step 23/30 : WORKDIR /home/app/
 ---> Using cache
 ---> 5d4a1d513399
Step 24/30 : ENV upstream_url=""
 ---> Using cache
 ---> 50a871ab0470
Step 25/30 : ENV mode="http"
 ---> Using cache
 ---> f89263342f01
Step 26/30 : ENV CLASSPATH="/home/app/function-1.0/function-1.0.jar:/home/app/function-1.0/lib/*"
 ---> Using cache
 ---> 117202b5a3ef
Step 27/30 : ENV fprocess="java -XX:+UseContainerSupport com.openfaas.entrypoint.App"
 ---> Using cache
 ---> 6ff10d0906c2
Step 28/30 : EXPOSE 8080
 ---> Using cache
 ---> a8f5ccfe2a36
Step 29/30 : HEALTHCHECK --interval=5s CMD [ -e /tmp/.lock ] || exit 1
 ---> Using cache
 ---> 6dff58f99847
Step 30/30 : CMD ["fwatchdog"]
 ---> Using cache
 ---> 695dd08045c9
Successfully built 695dd08045c9
Successfully tagged thinkmicroservices/hello-world:latest
Image: thinkmicroservices/hello-world:latest built.
[0] < Building hello-world done in 0.79s.
[0] Worker done.

Total build time: 0.79s

In the output of the build command, we see that the Gradle build executes, and the container image (in this case, thinkmicroservices/hello-world:latest) was built for our function.

Pushing the Function

The next step is to push the newly create function container image to our container repository. This is accomplished using the following command:

faas-cli push -f hello-world.yml
The output will look similar to this:

Pushing hello-world [thinkmicroservices/hello-world:latest].
The push refers to repository [docker.io/thinkmicroservices/hello-world]
cef7d5fd6a57: Pushed 
36618d9e1b78: Pushed 
50114cd3f456: Pushed 
cdcd7a9b55b1: Layer already exists 
867657f68396: Pushed 
5f07bab336e0: Pushed 
167efff21776: Layer already exists 
fee20f1b745d: Pushed 
d0fe97fa8b8c: Pushed 
latest: digest: sha256:46f5e4fef03ec1da303c8d1e36cb350b0be83e83f928e8a880e2e98fee20aece size: 2423
[0] < Pushing hello-world [thinkmicroservices/hello-world:latest] done.
[0] Worker done.

Here we see the hello-world function container image published to the target repository. Now that the container image is in our repository, we can finally deploy it to the OpenFaaS instance running on our MicroK8S cluster.

Deploying the Hello-world function

We are now ready to deploy our function packaged as a container image and published to our container repository. Again, we will use the faas-cli command with the deploy command, the hello-world.yml file, and the gateway URL.

faas-cli deploy -f hello-world.yml --gateway
Your output should look similar to:

Deploying: hello-world.
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.

Deployed. 202 Accepted.

We can verify that function was deployed using the following command:

faas-cli list --gateway
You should see the following:

Function                      	Invocations    	Replicas
hello-world                   	0              	1  
Here we see the hello-world function has been deployed with a single replica. We are now ready to invoke our function.

Invoking the Hello-World function

The final step of our process is to invoke our function. Once again, we can use the faas-cli to test that our function is working.

faas-cli invoke -f hello-world.yml hello-world --gateway
The function is invoked and will display the following:

Reading from STDIN - hit (Control + D) to stop.

Hello, world!
We aren't passing any data from STDIN, so you can press Control-D to complete the invocation> You will see the following response returned from the function:

Hello, world!
If we run the faas-cli list command, we can see that the number of invocations has incremented:

faas-cli list --gateway
Function                      	Invocations    	Replicas
hello-world                   	1              	1    
Here we see that the OpenFaaS application is capturing the number of times our function has been invoked.

In addition to using the the faas-cli, we can also invoke the function using the function's HTTP endpoint. We will use cURL to call the function, but you can use any mechanism capable of making an HTTP request (including your browser). We simply pass cURL the gateway address (in this case) and the path to the function ( /function/hello-world.openfaas-fn):

Hello, world!
If we rerun the faas-cli list command, we see that the number of invocations has incremented again:

faas-cli list --gateway
Function                      	Invocations    	Replicas
hello-world                   	2              	1    

As mentioned earlier, the HTTP endpoint is only one mechanism for invoking the function. For a comprehensive list of available event triggers, refer to the OpenFaaS Triggers page.

Redeploying the function

Over time, defect fixes and requirement changes will likely necessitate modifications and redeployment of the function. After making the necessary changes, perform another build, and deploy as described above. OpenFaas will replace the current function with the new function for subsequent invocations.

Removing the function

If or when the function is no longer required, we can remove it simply by calling:

faas-cli rm -f hello-world.yml --gateway
The output should appear as follows:

Deleting: hello-world.openfaas-fn
Removing old function.
We can verify the function has been removed by calling the faas-cli list command.

faas-cli list --gateway
We will get the following empty list of functions:

Function                      	Invocations    	Replicas


Functions and OpenFaaS give us a new tool in our microservice toolbox that allows us finer-grain control over our applications' development, deployment, and administration. Using a hybrid architecture that includes both domain-specific microservices and functions, we can better manage application resources while also delivering high availability and scalability.