In this post, you’ll provision a DigitalOcean Droplet using Terraform, once it’s up and running, a TLS certificate will be installed and you can start deploying code with faasd.

Before we start

Before we start the tutorial, let’s start with a bit more background. faasd is a slimmed-down distribution of OpenFaaS that can run on a single virtual-machine, is easy to automate, and has almost no need for maintenance. It’s not that OpenFaaS needed a diet, it was just that to even run a single function, you required a whole Kubernetes cluster, and some people were understandably put off by that idea.

Read my introduction to faasd

If all you need is a few functions, webhook-receivers, or web pages, then why run a 3-5 node Kubernetes cluster, when you can run a single 5-10 USD VPS instead?

This tutorial will use a combination of Terraform and cloud-init to setup a new host on DigitalOcean, however it is not limited to any one provider. If your favourite cloud provider supports cloud-init and has a Terraform module, then with a few tweaks, you can deploy faasd there too. You can even deploy faasd manually, if you so wish.

A TLS certificate will be provided by LetsEncrypt, using Caddy.

Overview

Before you start, you’ll need to run through the pre-requisites:

Then here’s the outline of what we’ll do next:

  • Initialise Terraform and have it download any modules required
  • Edit a .tfvars file
  • Run terraform plan and apply
  • Wait a few moments for our Droplet to start
  • Create a DNS A record for the IP of the Droplet
  • Wait a few moments for Caddy to set up the TLS certificate
  • Log into our new faasd host with TLS enabled

Tutorial

Initialise Terraform

Download the GitHub repo:

git clone https://github.com/openfaas/faasd
cd faasd/docs/bootstrap/digitalocean-terraform

Make sure that you’re using Terraform 1.0.4 or newer. If you ignore this instruction, don’t complain if it doesn’t work for you :-)

Now run:

terraform init

Open main.tfvars, and remove the line do_token.

Now set the following:

do_domain         = "example.com"
letsencrypt_email = "webmaster@example.com"

The do_domain field will be prepended with “faasd”, so if you enter example.com, the actual host will be faasd.example.com and so on.

Now prepare an environment variable for your DigitalOcean access token:

export TF_VAR_do_token="$(cat ~/Downloads/do-access-token)"

Plan with Terraform, and then apply it:

terraform plan -var-file=main.tfvars
terraform apply -var-file=main.tfvars

Check the output

The output should be as follows, and if you need it again run terraform output.

droplet_ip = 165.232.76.94
gateway_url = https://faasd.example.com/
login_cmd = <sensitive>
password = <sensitive>

The terraform output command can also be used to view the sensitive output data.

terraform output login_cmd
faas-cli login -g https://faasd.example.com/ -p 6b1be68a8feba552c11beb2eeb7fcc7edee4627f

terraform output password
password = 6b1be68a8feba552c11beb2eeb7fcc7edee4627f

OpenFaaS will be starting up, but it won’t be served on a public adapter, it will only be available via Caddy on port 80 and 443.

There is a bit more work required before Caddy can get a TLS certificate for us though.

Create a DNS A record

Create a DNS A record in your admin panel, or with the DigitalOcean CLI:

doctl compute domain create \
  faasd.example.com \
  --ip-address 165.232.76.94

At this point, in the background Caddy will be trying to create a TLS certificate for us. Finally, the DNS name faasd.example.com will resolve correctly, and Caddy will be able to go through a proper exchange with LetsEncrypt and obtain a certificate.

Check that it worked

The next step is to check that everything worked, you can verify this with the faas-cli login command, using the output from terraform output login_cmd

# Prefix with two spaces, to prevent tracking in your bash
# history, or save to a file and use cat.
  echo 6b1be68a8feba552c11beb2eeb7fcc7edee4627f | faas-cli \
    login -g https://faasd.example.com/ --password-stdin

Calling the OpenFaaS server to validate the credentials...
credentials saved for admin https://faasd.example.com

Next, try out the UI using the HTTPS URL from above along with the password you used to log in.

The dashboard

Deploy a function from the store:

The dashboard

And invoke it, in this instance, you can see that we’re running on a 1GB droplet:

The dashboard

You can now access faasd just like any other OpenFaaS gateway.

Deploy a function

Now that you have faasd deployed, you can deploy a function in Node.js for instance.

# Customise as per your Docker Hub account or external registry
export OPENFAAS_PREFIX="alexellis2"

faas-cli new --lang node12 private-api

This generates three files:

private-api.yml
private-api/handler.js
private-api/requirements.json

Here’s our handler.js:

'use strict'

module.exports = async (event, context) => {
  return context
    .status(200)
    .succeed(
        {'status': 'Received input: ' + JSON.stringify(event.body)}
    )
}

To install dependencies, just cd into the private-api folder and run npm install --save.

Let’s create a secret API token for the function.

# Generate a secret token in token.txt
echo $(head -c 16 /dev/urandom |shasum |cut -d "-" -f1) > token.txt

Now point your CLI to your gateway and create the secret:

export OPENFAAS_URL=https://faasd.example.com/

faas-cli secret create private-api-token --from-file token.txt

Creating secret: private-api-token
Created: 200 OK

Let’s update the code so that it does a simple switch depending on whether the user passes in a matching header in the HTTP request for the token value.

Note: in OpenFaaS, secrets are read from /var/openfaas/secrets/.

Edit handler.js:

'use strict'

const fs = require('fs');
const fsPromises = fs.promises;

module.exports = async (event, context) => {
  let secret = await fsPromises.readFile("/var/openfaas/secrets/private-api-token", "utf8")

  // Headers are made lowercase due to the use of Caddy
  let auth = event.headers["authorization"]
  if(auth && auth == "Bearer: " + secret) {
    return context
            .status(200)
            .succeed("authorized")
  }
  return context
    .status(401)
    .succeed("unauthorized")
}

To request the new secret from your OpenFaaS function, edit the private-api.yml file and add the secret below:

...
functions:
  private-api:
    ...
    secrets:
    - private-api-token

Now run:

# Log into your Docker registry
docker login

# Followed by 
faas-cli up

The faas-cli up command builds a local container image, pushes it to your registry, and then requests faasd to deploy it as an endpoint.

Try the API out:

See the 401:

curl -i https://faasd.example.com/function/private-api

unauthorized

Try to add your key:

curl -i -H "Authorization: Bearer: $(cat token.txt)" \
  https://faasd.example.com/function/private-api

authorized

Try changing the successful login message, and then run faas-cli up again.

Force a change of the version of the code by editing private-api.yml and editing the image: field.

    image: alexellis2/private-api:latest
# To
    image: alexellis2/private-api:0.1.0

Testing and debugging

The easiest way to test and debug code is to write tests. If you want to add unit tests, they are run at the build-time on your local computer, or in your CI pipeline. Here’s a simple example using Mocha.js: openfaas-node12-mocha-unit-test.

You can also add console.log to your code, and then view the results via faas-cli logs private-api.

When you run faas-cli build, a local Docker image is stored on your computer, you can also run that with Docker and access it.

# Build the latest code
faas-cli build -f private-api.yml 

Successfully built 49b896c2f235
Successfully tagged alexellis2/private-api:0.2.1
Image: alexellis2/private-api:0.2.1 built.
[0] < Building private-api done in 0.36s.
[0] Worker done.

Total build time: 0.36s

# Make a temporary secret to mount into the container
mkdir -p /tmp/secrets
cp token.txt /tmp/secrets/private-api-token

# Run the container
docker run -p 8080:8080 \
  -v /tmp/secrets/:/var/openfaas/secrets/ \
  -ti alexellis2/private-api:0.2.1

Then run curl http://127.0.0.1/ instead of your full OpenFaaS URL.

You can even couple this with docker-compose for a live-reloading experience: Rapid OpenFaaS hacking with Node.js and docker-compose

Wrapping up

In a very short period of time, using standard infrastructure automation tools we were able to create a lightweight Serverless deployment. faasd provides both a cost effective starting-point, and a serverless experience that’s very easy to manage. You can use VM backups, or even just delete your instance and re-create it, you have very little else to worry about, just run faas-cli up again against the new machine.

If you want to remove your host, just run terraform destroy, but at ~5 USD / mo, you may as well keep it up and keep learning.

Announcing “Serverless For Everyone Else”

faasd ebook and workshop

I’ve released my first eBook and video workshop called “Serverless For Everyone Else”, in the book I cover how to build your own self-hosted Serverless Functions without needing AWS IAM or Kubernetes. It can run on commodity hardware like a Raspberry Pi or DigitalOcean Droplet.

In the labs you’ll learn how to deploy faasd, how to use the OpenFaaS UI, CLI and REST API (via curl). The book then chances pace into hands-on labs with Node.js where you get to build out functions to query HTTP APIs, add npm modules, configure behaviour with environment variables and then add secrets. To top if off, you write a small CRUD API with Postgresql - all on the same host. The second half of the book concentrates on operational concerns like setting up TLS, or a HTTPS tunnel when you’re running behind a firewall and gives detailed instructions on monitoring your functions.

Check it out on Gumroad

Taking it further

There are many other OpenFaaS templates for different languages, and faasd can even run regular containers, as long as they accept HTTP traffic on port 8080.

You’ll also find a few differences in faasd vs OpenFaaS on Kubernetes, find out more in the faasd repo

You can learn more about OpenFaaS at:

See other posts from the community on faasd:

Alex Ellis

Founder of @openfaas.