6 minutes
Faasd in LXC
TL/DR
This post explores running faasd in LXC containers.
- As a consumer of Faasd on a linux workstation I wanted an options to run in native containers.
- Faasd Developers can benefit from a native runtime environment, as well as cheap/fast sandbox environments provided by LXC.
- OpenFaas function function developers can benefit from a tighter inner-loop that doesnt invole pushing docker images to a remote registry just to test their functions
Introduction
First lets discuss what exactly is faasd and what it can be used for. Faasd is a provider implementation of OpenFaas run as a systemd daemon. There are a handful of providers that each plug into OpenFaas which enable it to run in different environments. For example there is a provider for docker, kubernetes and even an in-memory provider for an ephemeral experience. Faasd is really geared for low resource boards like Raspberry Pi, where the overhead of running a docker daemon is too high (faasd interfaces directly with containerd using runc). So what does this mean? Well faasd makes a nice controller-manager for very small containerized processes.
At the time of this post, faasd runs purely as systemd processes, the components are:
- Faasd/faas-cli - systemd daemon and go CLI for managing functions of http
- Containerd/Runc - container runtime (all functions must be containerized)
- CNI - I havn't dug too deep here but I assume this is for creating and cleaning up IP addresses as all functions must be network callable
Why LXC?
Okay cool so it sounds like the functions are already in containers, why bring LXC into the mix? First let's take a look at how the components are deployed. The current set up is quite simple, just a few binaries and some systemd templates. These are all glued together in a simple cloud-config. This is awesome, basically our only requirement is a fresh linux OS that can execute the script.
But what if we want to run faasd on an existing linux system? Of course the simplest option, we can run a Virtual Machine, but we will have to pay the penalty of virtualization.
confession: I do not actually know the overhead of running a VM these days. It's probable the host would never know the diffence since I understand it is quite low.
Another option would be to run the processes directly on the host. This gets super messy without config managment tools like ansible. Given the current deployment is a handful of bootstrap scripts it would be a total pain to track things down if something breaks or we need to upgrade.
Which brings us to LXC. Linux Containers are simply namespaced filesystems running on top of a shared linux kernel. Some advantages to running faasd in LXC (beyond the above mentioned):
- We can easily spin up
n+
environments including different versions for testing - Containers are extremely fast to boot once the initial file system is pulled down.
- When something breaks we can blow away the entire container with minimal risk to the host.
- LXC actually has a network daemon included (LXD) which makes managing containers over the wire very convenient. See Next steps for a thought experiment involving LXD.
Demo
Okay enough yapping let's look at some code..
Pre-requisites
You will need to install LXD and then run lxd init
to set up the network and storage pool. I stuck with dir
for the storage backend to keep things simple and lxdbr0
for the network (bridge to the host).
warning: don't use ZFS storage pool yet, containerd support is not quite there.
Creating a Container
The basic steps are:
create a LXC profile with the faasd cloud-init script and
security.nesting
enabled (since we are using containerd inside of linux namespaces)LXD_PROFILE=faasd LXD_USERDATA="/tmp/cloud-config.yml" wget -qO $LXD_USERDATA https://gist.githubusercontent.com/gabeduke/7ccb3f3147d79ac30e2187432808060c/raw/4028ea0658e9aa0a37f6ac44473a8092e3824406/cloud-init.yml lxc profile set $LXD_PROFILE user.user-data - < $LXD_USERDATA lxc profile set $LXD_PROFILE security.nesting=true lxc launch ubuntu:18.04 faasd -p $LXD_PROFILE
Once the container is finished bootstrapping we can fetch the faasd password and connect to the OPENFAAS_GATEWAY over the network
FAASD_PASSWORD=$(lxc exec faasd -- cat /var/lib/faasd/secrets/basic-auth-password) FAASD_IP=$(lxc exec faasd -- /sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1) echo $FAASD_PASSWORD | faas-cli login --password-stdin --gateway http://${FAASD_IP}:8080
There should now be a container running:
» lxc list +-------+---------+-----------------------+----------------------------------------------+-----------+-----------+ | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | +-------+---------+-----------------------+----------------------------------------------+-----------+-----------+ | faasd | RUNNING | 10.62.0.1 (openfaas0) | fd42:ad54:4bbd:9f9:216:3eff:fe50:66ee (eth0) | CONTAINER | 0 | | | | 10.225.76.18 (eth0) | | | | +-------+---------+-----------------------+----------------------------------------------+-----------+-----------+
And OpenFaas should be addressable on the gateway IP:
» faas store deploy figlet WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates. Deployed. 200 OK. URL: http://faasd.local:8080/function/figlet » faas list Function Invocations Replicas figlet 0 1
Here is the full convenience script (I am using hostess to update /etc/hosts
with the new gateway IP):
#!/bin/bash
# Set up the environment
export OPENFAAS_URL=http://faasd.local:8080
LXD_PROFILE=${1:-dukemon}
LXD_USERDATA="/tmp/cloud-config.yml"
LXD_CONTAINER=${2:-faasd}
if ! (lxc info $LXD_CONTAINER > /dev/null 2>&1)
then
echo "ensure lxc profile"
wget -qO $LXD_USERDATA https://gist.githubusercontent.com/gabeduke/7ccb3f3147d79ac30e2187432808060c/raw/4028ea0658e9aa0a37f6ac44473a8092e3824406/cloud-init.yml
lxc profile set $LXD_PROFILE user.user-data - < $LXD_USERDATA
lxc profile set $LXD_PROFILE security.nesting=true
echo "launching LXC container ${LXD_CONTAINER}.."
lxc launch ubuntu:18.04 $LXD_CONTAINER -p $LXD_PROFILE
fi
# Wait for cloud-init sequence to finish before moving on to the configuration steps
until (lxc exec $LXD_CONTAINER -- cat /var/lib/cloud/instance/boot-finished > /dev/null 2>&1)
do
echo "Waiting for cloud-init complete signal.."
sleep 5
done
# let's be safe and wait for the last thread to finish
wait
echo "fetch faasd password from $LXD_CONTAINER"
FAASD_PASSWORD=$(lxc exec $LXD_CONTAINER -- cat /var/lib/faasd/secrets/basic-auth-password)
FAASD_IP=$(lxc exec $LXD_CONTAINER -- /sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)
echo "faasd IP is $FAASD_IP"
if which hostess
then
echo "Updating /etc/hosts"
sudo hostess add "faasd.local" $FAASD_IP
wait
until (echo $FAASD_PASSWORD | faas-cli login --password-stdin > /dev/null 2>&1 && echo "login successful")
do
echo "Attempt faasd login.."
sleep 5
done
else
echo "install hostess to /usr/local/bin to automatically update /etc/hosts (https://github.com/cbednarski/hostess)"
fi
echo "init complete!"
Hot loading images
This is great we now have a container running with all of the faasd components and can easily add copies or swap out a new version. There is one last issue which is shortening the development loop for buildling functions. Containerd does not re-pull an image so once we publish a tag we cannot update it. It is common in development cycles to use a tag like latest
to keep publishing images to. But we are running the containerd socket locally so why would we even want to push to a remote registry just to pull the same image back down to a different location on our machine? We can do better..
Let's go over the steps to hot load images directly into containerd:
When developing OpenFaas functions we can just build the images locally and save them to OCI compatiable images using docker
faas build -f fn.yml docker save [registry]/[image]:[tag] -o fn.tar
Now we can push the tarball into the LXC container and import it to the correct namespace:
lxc file push fn.tar faasd/ lxc exec faasd -- ctr -n openfaas-fn images import /fn.tar
Now we can simply run
faas deploy
to restart the container with the updated image.faasd deploy
Next steps
I had a lot of fun playing around with and containerizing Faasd, on a closing note here are some more things to try:
- Use LXD to create an autoscaling group of containers (need to ship container metrics and scale on capacity events)
- Use
distrobuilder
to pack a sharable image for more of a docker or ISO like experience.
1254 Words
2020-02-27 14:30 +0000