faasd - lightweight Serverless for your Raspberry Pi

Kick the tires with faasd today, for a lightweight serverless experience that doesn't require Kubernetes. Instead it uses containerd and the existing OpenFaaS ecosystem.

You can run faasd anywhere, even on a Raspberry Pi, but why would you want to do that? faasd offers many of the benefits of containers and OpenFaaS, but without the complexity and operational costs of Kubernetes. containerd is a low-level tool for automating containers, and a CNCF project.

ofctr

The use-cases for Serverless / FaaS are fairly well-known, but you could use faasd at the edge, in a smart-car, as part of an IoT device, for crunching data before uploading samples to the cloud, for webhook receivers/alerting, bots, webservices, API integrations or even providing your own APIs. Compute is compute, and OpenFaaS with containerd makes it easy to both consume and provide.

Before we start

Use Raspbian or Raspbian Lite for this tutorial. Do not install Docker on the same Raspberry Pi as it may cause conflicts with networking and the version of containerd we are using.

You should use RPi 2, 3 or 4. If you have only one Raspberry Pi, then you'll be able to deploy pre-built images from the OpenFaaS Function Store, but if you have a second Raspberry Pi, then you can use it to build new functions too.

Installation

Installation only takes a few moments, and in the future should be quicker if we can secure binaries for containerd from the upstream project.

Get faas-cli

The faas-cli can be used to build, deploy, manage, and invoke functions with OpenFaaS. OpenFaaS offers a REST API, asynchronous invocations, authentication, metrics, and a UI out of the box.

curl -sLfS https://cli.openfaas.com | sudo sh

See also: OpenFaaS docs

Get containerd

Unfortunately the containerd maintainers only provide binaries for x86_64 (regular PCs), so we have to install Go and build containerd from source.

We'll install some dependencies that will help us in the tutorial and after installation

sudo apt update \
  && sudo apt install -qy \
    runc \
    bridge-utils \
    tmux \
    git

Pick option A or B.

A) Use my containerd armhf binaries (fast)

I have compiled binaries for containerd, which you can use if you wish. alexellis/containerd/armhf.

curl -sSL https://github.com/alexellis/containerd-armhf/releases/download/v1.3.2/containerd.tgz | sudo tar -xvz --strip-components=2 -C /usr/local/bin/

B) Build your own containerd binaries (slow)

Raspbian needs a few additional build-time dependencies for containerd:

sudo apt update \
  && sudo apt install -qy \
    build-essential \
    libbtrfs-dev \
    libseccomp-dev

You can use my utility script to build containerd now:

export GOPATH=$HOME/go/
cd $GOPATH/src/github.com/alexellis/faasd

./hack/build-containerd-armhf.sh

This could take a few moments on RPi2 or 3, and is a little quicker on RPi4.

Configure containerd to start on boot

Install a systemd unit file for containerd:

curl -SLfs https://raw.githubusercontent.com/containerd/containerd/v1.3.2/containerd.service | sudo tee /etc/systemd/system/containerd.service

sudo systemctl enable containerd
sudo systemctl start containerd

Install networking

  • Enable the Kernel's bridge module

    # One-off
    sudo modprobe br_netfilter
    sysctl net.bridge.bridge-nf-call-iptables=1
    
    # Make it permanent
    echo "br_netfilter" | sudo tee -a /etc/modules-load.d/modules.conf
    echo "net.bridge.bridge-nf-call-iptables=1" | sudo tee -a /etc/sysctl.conf
    
  • Install netns

    netns provides bridged container networking for us through a pre-start hook in runc so that containers can be adressed. This is configured for the container spec before containerd creates it.

    Note: netns will be removed in a future release and replaced with CNI, the networking used in Kubernetes. It's currently avaiable for faas-containerd, but not faasd.

    sudo curl -fSLs "https://github.com/genuinetools/netns/releases/download/v0.5.3/netns-linux-arm" \
      --output "/usr/local/bin/netns" \
      && sudo chmod a+x "/usr/local/bin/netns"
    
  • Install CNI plugins

    sudo mkdir -p /opt/cni/bin
    
    curl -sSL https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-arm-v0.7.5.tgz | sudo tar -xz -C /opt/cni/bin
    
  • Enable NAT

    Enable NAT between the container network and your wider network and the Internet:

    sudo /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
    
    echo "net.ipv4.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
    

    Optional step - if you have Docker running on your system, then disable it and reboot at this point.

    sudo systemctl disable docker
    sudo systemctl disable docker.sock # or docker.socket - You may also need this step
    

    Docker's IP address range conflicts with the one chosen by netns, this will be resolved when moving to CNI.

Get faasd and faas-containerd

faasd is a Go binary that runs on your system and packages all of OpenFaaS, it's a similar tool to docker-compose.

faas-containerd is an OpenFaaS provider which implements the OpenFaaS "CRUD" API for functions.

Clean-up old versions, if required:

# Remove old binaries if you have them
sudo rm -rf /usr/local/bin/faas-containerd
sudo rm -rf /usr/local/bin/faasd

# Stop systemd services, if you ahve them

sudo systemctl stop faasd
sudo systemctl stop faas-containerd

Download the faasd/faas-containerd binaries:

# Get the new ones

sudo curl -sSLf "https://github.com/alexellis/faas-containerd/releases/download/0.4.0/faas-containerd-armhf" \
    --output "/usr/local/bin/faas-containerd" \
    && sudo chmod a+x "/usr/local/bin/faas-containerd"

sudo curl -sSLf "https://github.com/alexellis/faasd/releases/download/0.4.4/faasd-armhf" \
    --output "/usr/local/bin/faasd" \
    && sudo chmod a+x "/usr/local/bin/faasd"

Now run the installation:

export GOPATH=$HOME/go/
mkdir -p $GOPATH/src/github.com/alexellis
cd $GOPATH/src/github.com/alexellis

git clone https://github.com/alexellis/faasd

cd faasd

sudo faasd install

Post-install

The faasd install command creates two systemd unit files which can be used to start/stop faasd and faas-containerd.

Check the installation

Check the services:

sudo systemctl status faasd
sudo systemctl status faas-containerd

Find logs:

sudo journalctl -u faasd --lines 40
sudo journalctl -u faas-containerd --lines 40

You'll find your login details for the gateway in /run/faasd/secrets, the user is admin and the password is in /run/faasd/secrets/basic-auth-password

Access OpenFaaS from your laptop

faasd deploys OpenFaaS along with faas-containerd, the component that uses containerd instead of Kubernetes to start functions.

The faasd process will proxy any incoming requests on HTTP port 8080 to the OpenFaaS gateway container, you can also find the IP of the container in your hosts file in the faasd directory.

Deploy a function from the store

Log in to the gateway, either on your RPi, or from your own computer.

# If connecting remotely
export OPENFAAS_URL=http://RPI:8080

sudo cat /run/faasd/secrets/basic-auth-password | faas-cli login --password-stdin

Look at what's available for armhf, then deploy something:

faas-cli store list --platform armhf

# Deploy figlet
faas-cli store deploy --platform armhf figlet

# Find the URLs for the function
faas-cli store inspect figlet

# Create some ASCII

echo "faasd" | faas-cli invoke figlet

Try a function that can generate an identicon like you may have seen for default avatar logos on GitHub.com

faas-cli store deploy --platform armhf identicon

echo $USER | faas-cli invoke identicon > avatar.png

Here's an example of the avatar for the user pi:

avatar

Run an async function

You can also run your functions in the background or asynchronously.

Create a webhook receiver using a public service such as:

faas-cli store deploy --platform armhf nodeinfo

curl -d "verbose" \
  http://127.0.0.1:8080/async-function/nodeinfo \
  --header "X-Callback-Url: https://postb.in/1578562711776-8760500776115"

You'll be able to refresh your webhook receiver and see the value.

Screenshot-2020-01-09-at-09.41.11

Build a new container image

You'll need a second Raspberry Pi for this step. You can also use one of my prebuilt functions such as ascii-2020, but you should only run the faas-cli deploy step.

  • Log into the Raspberry Pi with ssh and install the faas-cli and docker.

    curl -sLfS https://cli.openfaas.com | sudo sh
    
    curl -sLfS https://get.docker.com | sudo sh
    
    sudo usermod -aG docker pi
    newgrp
    
  • Log into the Docker Hub

    docker login
    
  • Create a Golang function

    export DOCKER_HUB_NAME="alexellis2"
    
    faas-cli store pull golang-middleware-armhf
    
    faas-cli new --lang golang-middleware-armhf \
      hello-world \
      --prefix $DOCKER_HUB_NAME
    
  • Now build and push the image

    faas-cli push -f hello-world.yml
    faas-cli deploy -f hello-world.yml
    

Deploy your new container

You can set your OpenFaaS gateway to point at the RPi where faasd is running.

export OPENFAAS_URL=http://$RPI_IP:8080

faas-cli deploy

Your new function will be pulled down on the first Raspberry Pi and start executing. Find the IP address / URL for invoking the function, both synchronous and asynchronous invocations are working:

faas-cli describe -f hello-world.yml hello-world

Now, try editing your function's source-code in hello-world/handler.go - get it to print a different message perhaps? The template we have chosen runs validation with gofmt, so if you've edited on the RPi, you may want to format the text before doing a build.

Your workflow is just faas-cli up, or the separate steps of faas-cli build, faas-cli push and faas-cli deploy.

Get a public IP for your functions

Inlets can help us to get a public IP and ingress to our functions, this is done through a tunnel to a host on a cloud IaaS or VPS provider such as DigitalOcean.

Download inlets - an OSS reverse proxy and service tunnel.

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

You'll need an access token for a cloud for one of the supported providers, see the list on: inletsctl create --help

I picked DigitalOcean and ran the create command to provision the exit server, this is the public-facing host with a public IP.

inletsctl create --provider digitalocean --access-token-file ~/do-token

After a few seconds the host will start up running the inlets server, now you can go ahead and run the inlets client on your RPi. I like to run commands with tmux or screen so that I can log out and they stay running. You can also make the inlets client permanent with a Cron entry @reboot or a systemd unit file.

This is the command printed out by inletsctl create, just edit the UPSTREAM so that it points to faasd on 127.0.0.1:8080, then hit enter.

  export UPSTREAM=http://127.0.0.1:8080
  inlets client --remote "ws://178.128.40.222:8080" \
        --token "525ed828fdfe11897b26b8a0d5359ef6c7ddd4ad" \
        --upstream $UPSTREAM

Your functions can now be accessed via the public Internet, feel free to share the URL with your friends.

Basic authentication means that the UI and API of OpenFaaS are protected, but by default functions can be accessed.

In my example I have a function running called ascii-2020 from this repo.

From anywhere in the world users can invoke the function:

http://178.128.40.222/function/ascii-2020?q=faasd+on+rpi3

Screenshot-2020-01-09-at-09.50.42

You can even add TLS to your exit node by logging into it via SSH and installing Nginx or Caddy.

I wrote a tutorial you can follow for Caddy called HTTPS for your local endpoints with inlets and Caddy
, here's what it looks like in action with a TLS certificate being served from the exit-node for my domain faasd.myfaas.club

Screenshot-2020-01-09-at-12.30.17

Today I prefer to use inlets-pro which means that I can run Caddy directly on the RPi and obtain a LetsEncrypt certificate on the device itself.

Notes on rebooting/restarting faasd

Now try rebooting your RPi, you should see that the gateway comes up again afterwards. The functions you have deployed will need to be removed manually and then deployed again, the same would be true if you restarted faasd.

export FN="figlet"
sudo ctr --namespace openfaas-fn task delete -f $FN
sudo ctr --namespace openfaas-fn container delete $FN
sudo ctr --namespace openfaas-fn snapshot remove $FN-snapshot

This is a temporary workaround and will become unnecessary in a future release.

Wrapping up

faasd brings a lightweight experience to your Raspberry Pi, cloud infrastructure and to bare metal, all without the need for Kubernetes. The solution is still under active development, but there's already support for asynchronous invocations through NATS and a PR ready to add basic-auth support.

You may be wondering what to run on your new RPi with faasd. Any OpenFaaS code should be able to run, whether that's an API, a function, a microservice or a simple webpage. Here's a few ideas off the top of my head:

  • A Slackbot that can receive webhooks from Slack and respond to users
  • A webhook receiver for GitHub to log or track statistics
  • A GitHub bot like Derek
  • Automation of home servers and IoT devices - use a function to send a call to your smart home?
  • Host a website or a blog using the node12 template or React
  • Download your favourite videos with the youtubedl function from the store
  • Set up cron jobs to execute functions on a regular basis

Get involved

What can you do to help?

For questions, comments, and suggestions ping me on Twitter @alexellisuk

Thank you to those who helped test and review this blog post and faasd.

Alex Ellis

Read more posts by this author.

Subscribe to alex ellis' blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!