Simon Emms

Software Engineer, Technical Leader, Solutions Designer

Live Reload for OpenFaas

Published:

2020-08-15: Added GoLang example


This article assumes a degree of familiarity with OpenFaaS. I won't be covering how to get started in it or the key concepts in any detail. If you want to get started with it, please see their excellent documentation.

I am a big fan of "serverless" functions. They help your application scale to a theoretically unlimited capacity. They are so useful that there is a whole myriad of implementations - AWS, Azure and GCP all have their own native versions, you can use the Serverless framework and then there's the like of KNative. And these are just the ones I can remember off the top of my head.

One of my favourite versions is OpenFaaS by the very excellent Alex Ellis. This has the advantage of being open source, a vibrant community and really easy to get started. It's also cloud native, so you're not tied into a particular cloud provider (yes, I'm looking at you AWS/Azure/GCP) - if it runs in Kubernetes, it'll run in OpenFaaS.

One of my biggest gripes with OpenFaaS is that it can be painful developing new functions. It's fine if you want to stick it in the public Docker Registry and/or you don't mind waiting for a minute or two each time you change your code. I regularly work for companies that don't want their proprietary code being published on the internet for anyone to use.

Also, I'm impatient and hate compile time.

Compiling

The official workflow is:

faas-cli build # Build the image
faas-cli push # Push to Docker Hub
faas-cli deploy # Deploy to your cluster

Each of these steps could well take in excess of 30 seconds. Painful when you're hacking away.

Docker Compose To The Rescue

There is an accompanying Git repo with this. Please check that out to see the source code - gitlab.com/MrSimonEmms/openfaas-docker-compose

Docker Compose is perhaps a little unfashionable with Kubernetes-enabled teams, but it serves a great place for efficient local development.

IMPORTANT: this development workflow exists outside OpenFaaS. It uses the template that you will use when it moves to production, but when developing your function you won't have access to things like the async workflow. However, as you would be focusing on getting your function working rather than accessing it, that's usually an easy concession to make.

The Process Explained

If you are using the Classic Watchdog, there is nothing to do here and your function will automatically reflect any changes each time you invoke the function. This is because the Classic Watchdog runs the whole command each time the function is invoked.

The OF Watchdog is the modern and (in my opinion) the better watchdog. The function runs as a single HTTP endpoint on all methods. It's better because it allows improved error handling. However, because it's an application inside the function, we need to change how that application works - basically, you need to put a file watcher in that restarts the application each time you make a change to the code.

The important one here is the fprocess environment variable. This is the command that run the application. For example, in the node12 template the fprocess envvar is node index.js.

We also need to set the volumes and set the user to root. The reason we have to set the user to root is to be able to install any global dependencies to the container at runtime. Even though this is an anti-pattern in a production container, I would argue that this is ok in a dev-only container to avoid having to maintain duplicate Dockerfiles.

Secrets

OpenFaaS supports secrets by putting the file in /var/openfaas/secrets. Docker Compose stores secrets in /run/secrets so you will need to do some form of either/or load, appropriate to the language. I suggest putting the OpenFaaS version as the default to reduce load in the production environment.

IMPORTANT: You should never store sensitive data in a repo.

Getting Started

Let's start by creating a Makefile in the root of your project. The purpose of this is to be able to easily install all the templates found in the functions.yml file. Strictly speaking, this is optional, but it makes life a lot easier.

FUNC_FILE ?= './functions.yml'

templates:
  which faas-cli || (echo "Please install 'faas-cli' package" && exit 1)
  which jq || (echo "Please install 'jq' package" && exit 1)
  which yq || (echo "Please install 'yq' package" && exit 1)

  $(eval templates := $(shell cat ${FUNC_FILE} | yq r - -j | jq -r '.functions | values[].lang'))

  for template in $(templates) ; do \
    faas-cli template store pull $$template ; \
  done
.PHONY: templates

Run make templates to download all the templates you need.

We now need a functions.yml in the root of your project. This is what we're using as the definition of all the functions we're writing. This is the exact same format as the OpenFaaS yaml file.

version: 1.0
provider:
  name: openfaas
functions: {}

Language Implementations

Node12

functions.yml

version: 1.0
provider:
  name: openfaas
functions:
  node12:
    lang: node12
    handler: ./functions/node12
    image: node12:latest

docker-compose.yml

version: '3.7'
services:
  node12:
    build:
      context: ./template/node12
    ports:
      - 3000:3000
    environment:
      fprocess: nodemon index.js
    secrets:
      - example
    volumes:
      - ./functions/node12:/home/app/function
    user: root
    command: sh -c "npm i -g nodemon && fwatchdog"

secrets:
  my-secret:
    file: ./secrets/example

To get live reload in NodeJS we use the nodemon application. If you've ever done any NodeJS work, it's incredibly likely that you'll have used nodemon as it works really REALLY well.

Python3-Flask

functions.yml

version: 1.0
provider:
  name: openfaas
functions:
  python3-flask:
    lang: python3-flask
    handler: ./functions/python3-flask
    image: python:latest

docker-compose.yml

version: '3.7'
services:
  python3-flask:
    build:
      context: ./template/python3-flask
    ports:
      - 3001:8080
    environment:
      FLASK_APP: /home/app/index.py
      FLASK_ENV: development
      fprocess: flask run
    volumes:
      - ./functions/python3-flask:/home/app/function
    user: root

Unlike the node12 template, we don't need to install any dependencies at runtime. The fprocess needs to use flask, which is already installed as that's the framework used in this template. I've kept the user as root for consistency, but it probably isn't actually necessary.

GoLang-HTTP

functions.yml

version: 1.0
provider:
  name: openfaas
functions:
  golang-http:
    lang: golang-http
    handler: ./functions/golang-http
    image: golang-http:latest

docker-compose.yml

version: '3.7'
services:
  golang-http:
    build:
      context: ./template/golang-http
      target: build
    ports:
      - 3002:8080
    environment:
      fprocess: air -c /go/src/handler/function/.air.toml
      mode: http
      upstream_url: http://127.0.0.1:8082
    volumes:
      - ./functions/golang-http:/go/src/handler/function
    command: sh -c "go get -u github.com/cosmtrek/air && fwatchdog"

This one is a little more involved than NodeJS/Python because the Go template builds a binary and puts it into an empty Alpine container. We have to intercept the build process to use the GoLang template by setting the build.target parameter to build (see Docker Multi-Stage builds for more information). As the OpenFaaS configuration is done in the final container, we need to apply this configuration to the build target. This means setting the mode and upstream_url environment variables.

Finally, this uses the Go package Air to provide the live reload facility. This requires a config file, even if there's nothing inside it. As we won't need this in production, there's a .dockerignore file to not send it to the image when building. However, the Docker Compose volumes ignores this, which is exactly what we need.

Next Steps

I want to write a version for this for each officially supported OpenFaaS template in the template store. If you want to help with that, please fork the repo and add a PR.

GoLang (now done) and CSharp are the ones I'd really like to get done as they seem to be fairly popular within the community.


Credits

Photo by Braden Collum

Do you like this article?