Cloud computing is growing, and with it a whole bunch of new frameworks and terms, such as “serverless” and “Function as a Service.” While a lot of these frameworks are supplied by big companies like Amazon and Google, there are projects such as OpenFaaS which allow you to run a Function as a Service platform on your own machine(s). What’s more, OpenFaaS also allows for easily extending the platform to support other languages. Because OpenFaaS revolves around functions, it made perfect sense to add support for the first functional programming language, Lisp, by using the wonderful Racket environment. And now you too can make your own serverless Racket function! Let’s try it out.
Setting Up the Environment
If you already have OpenFaaS set up, great, you can skip this section! If you have Docker on your machine already, you can skip the next paragraph. We’re going to be setting up OpenFaaS to run on Docker. If you’d like to run it on something else, check out the official documentation. OpenFaas supports Kubernetes and Docker Swarm. You can also use Play With Docker if you don’t want to install Docker on your local machine, and instead just want to try out OpenFaaS temporarily in an online ephemeral environment. While you don’t need it, it may help to have a Docker Hub account, in the case you wish to make your function available to the rest of the world, or are going to build your function on a machine different from the one you shall deploy to.
Docker is a tool that allows you to build and launch containers: File system images with all the libraries and code for a given application. A Docker container can run on any machine with Docker installed, which makes it powerful for shipping and deploying applications. We’re going to use it to run and build OpenFaaS functions. If you have a Linux system, you can do
curl https://get.docker.com | bash or
curl -o docker.sh https://get.docker.com && bash docker.sh to install Docker. Note the output of the command, especially the note about the
docker group. If you have a Mac, see here to install Docker, which installs like any other app. If you have Windows, and you don’t have the Pro version of Windows 10, you can use Docker Toolbox.
Side Note: If you’d like a more permanent and manageable means of testing OpenFaaS, I highly suggest using Docker Machine to create a VM just for OpenFaaS.
Since OpenFaaS is designed to run in a cluster of multiple machines, we’re going to make our machine a Docker Swarm manager. It’s okay if you only have one machine for testing, but if you’d like to deploy OpenFaaS in production, it’s highly suggested to have some fault tolerant cluster with multiple machines. To make your machine a Swarm manager, you can run
docker swarm init or
docker swarm init --advertise-addr eth0, depending on the output from the former command. Now we can deploy OpenFaaS with
git clone https://github.com/openfaas/faas && cd faas && git checkout 0.7.5 && ./deploy_stack.sh. You can see the parts of OpenFaaS spinning up with
docker service ls. You can now visit
http://<docker host ip>:8080 with your web browser. If you’re running Docker on Linux or Mac,
localhost is your Docker host. On Windows, with Docker Toolbox, it will be some
192.168.0.0/16 IP. You can view the IP with
docker system info | grep 'Node Address'. If you’re using Play With Docker, you should see a link to port 8080 near the top of the page.
From the OpenFaaS dashboard, you can deploy new functions from the function store, as well as invoke functions. You can also invoke these functions with
curl http://<docker host ip>:8080/function/<function name> or
curl -XPOST -d <data> http://<docker host ip>:8080/function/<function name> if the function expects some input.
Now that the platform is set up, we need to to get the client tool to interact with OpenFaaS. You can do so with
curl -sL cli.openfaas.com | sh. This will give you the
faas-cli tool, which is used to build, deploy, and invoke functions.
Creating Our Racket Function
Now that our environment is set up, we can start function creation. Create a new folder for your function, and in it run
faas-cli template pull https://github.com/dvdmuckle/racket-template. This will pull in skeleton code for Racket functions, as well as some supporting files. Next, run
faas-cli new <function name> --lang racket, where
<function name> is the name of the function you’re creating. Don’t worry about it matching the folder you created earlier.
This will create a few artifacts: A folder with the name of your function, and a
<function name>.yml file. The YAML file specifies the name of the function, what language it is, the name of the Docker image that will be built, the URL for where you can access your OpenFaaS setup, and a few other things. Depending on your setup, you may wish to change the value for the image, if your OpenFaaS setup isn’t on the same machine where you’ll be building your functions.
In the folder that was created, you’ll find a
handler.rkt file. Let’s see what it has.
#lang racket (provide handle) (define (handle) (define in (read-line)) (display "You said ") (displayln in))
While all this does is echo whatever input it receives, it gives us a good skeleton to work off of. Two things must always exist here: A language declaration, where this is using
(provide handle), which simply says, “This file provides this function.” This is necessary as this handler file is used by another file to grab the
handle function definition, which must always be present and be a top level function. Beyond that, this file can contain whatever else you want. If you’d like, you can add any more files in the same folder as
provide to make sure they’re all aware of each other. Note that input is gathered from
read-line. The function will fail to build if
handle has any parameters.
While it would be perfectly fine to build and deploy this function as is, it’s kind of boring. How about something to calculate Fibonacci numbers?
#lang scheme (provide handle) (define (handle) (define in (read-line)) (define (fib n) (define (fof n a b) (cond ((= n 0) a) ((= n 1) b) (#t (fof (- n 1) b (+ a b))))) (fof n 0 1)) (define num (string->number (if (eof-object? in) (error "Requires input") in))) (if (number? num) (displayln (fib num)) (error "Input not a number")))
This is a tail recursive Fibonacci sequence, which we feed error-checked input. It will then print to
stdout the results. Note that
stdout basically become HTTP data POSTs and response bodies respectively once the function is built and deployed. This is part of what makes OpenFaaS rather nifty. You can create web-based functions without having to worry about the web part, just the function part.
If you have a local Racket environment, you can test this function by loading it into a Racket session, and calling the function that way. But, because I like to fly by the seat of my pants, we’re going to build this and deploy it now.
Building and Deploying the Function
Once your function is all set up, we can build it. Use
faas-cli build -f <function name>.yml to build the function. Make sure you made any necessary edits to the YAML file, such as changing the image value, before you build. If you’re building on and deploying to the same machine, this doesn’t matter. Now that your function is built, you can deploy it with
faas-cli deploy -f <function name>.yml --gateway http://<docker host ip>:8080. If you already edited the gateway value in the YAML file, you can leave out the
--gateway flag. You can also leave it out if the default value,
http://localhost:8080, is indeed where OpenFaaS is.
Now, time to see the fruits of our labor! First, try
curl -XPOST -d 3 http://<docker host ip>:8080/function/<function name>. If your function does indeed calculate the Fibonacci number of a given number, you should get
2 back. You can also try this out in the OpenFaaS UI which you opened before. A new function will be present, your function! You can put in the same input there, invoke it, and get the same response. You can also use
faas-cli invoke <function name> --gateway http://<docker host ip>:8080 to call your function
Now that you’ve seen how you can create serverless Racket functions, I’d suggest creating more! Lisp, and Racket, are both excellent in my opinion, and I personally wish to continue adding more features to this template, such as installing external Racket libraries. Lisp as a language may seem syntactically dense at first with all its parenthesis, but stick with it long enough and you’ll soon see its brilliance. Or maybe you’ll just end up seeing parenthesis in your sleep…