How to run Rust in OpenFaaS

Mark Sta Ana - Aug 5 '18 - - Dev Community

I’ve been getting into Kubernetes in a big way, this is partly thanks to it being bundled in Docker for Mac Edge edition.

Once I'd learnt the basics via Kubernetes by Example, I wanted to learn a bit more about the specifics of the Kubernetes that is bundled with Docker. I found Alex Ellis' blog post incredibly helpful.

I also credit this blog post for getting me into OpenFaaS and Serverless Functions.

This blog post expands on a tweet I wrote last sunday.

I had just created a proof of concept Rust function template for OpenFaas.

Since then I've been able to clean up the template and come up with a more substantial example. At the time I tweeted my success, Alex (OpenFaaS maintainer) told about Erik Stoekl's version. It's reassuring to see we didn't differ too greatly.

What are Functions

Functions in the context of Functions as a Service, or Serverless Functions, becomes the next level of abstraction after Platforms as a Service (PaaS). If you think of PaaS as a way to simplify the software release process: you make changes to code, you merge them into your master branch, PaaS handles packaging, delivery of the software as well as providing servers tailored to your application.

Functions take this abstraction further, but removing concerns around how your software interacts with its environment. You no longer consider issues of RBAC, Presentation (webserver, stdio), you just write the minimum amount of code to get things done.

The best summation of what FaaS and to an extent what Microservices are was coined by Ian Cooper. To paraphrase him: Your unit tests are your functions. You do not have concerns about things outside of the function's task.

What is OpenFaaS

OpenFaaS is an open source implementation of Function as a Service (Serverless Functions, microservices) that you can self host. Rather than list all the various offerings in this space, I'll refer you to the Cloud Native Computing Foundation, in particular the interactive Landscape.

You can either deploy existing functions or create new ones. If you create new ones, there's a big list of officially supported languages. Alternative you could turn a CLI into function.

Once I'd given Python and Ruby a go as an introduction, I wanted to see how easy it would be to create a Rust template.

Anatomy of an OpenFaaS template

A template requires the following:

  • a script/program to act as a driver for the function. This code will not be seen or used by end users using your tempate
  • a script/program with a handle function that will provide the boilerplate for your end users
  • a Dockerfile that will bundle and build the driver and function, as well as installing a watchdog process (more on this later).
  • a template.yml which at minimum should provide the programming language of the template and the code to start the handler.

Decisions behind the design of the Rust template

To create a Rust template, I created two crates (we'll discuss the functionality later on):

  • the main crate acts as the driver for the function
  • the function crate contains the library with the handle function

I used cargo new to create the new crates and to provide all the necessary plumbing required for a Rust project.

Important note about the function crate. OpenFaaS expects to find a folder called function, if you call it anything else it will not copy the boilerplate code when a new function is created using the template.

The main crate pulls in the function crate as a dependency using a relative path.

Cargo.toml (some sections omitted)

[dependencies]
handler = { path = "../function" }
Enter fullscreen mode Exit fullscreen mode

For the Dockerfile, I went for the official stable image of Rust.

The flow of function

The driver reads standard input and passes to the function.

use std::io::{self, Read};

extern crate handler;

fn main() -> io::Result<()> {
    let mut buffer = String::new();
    let stdin = io::stdin();
    let mut handle = stdin.lock();

    handle.read_to_string(&mut buffer)?;

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

I copied this code from the standard library documentation.

The function parses the input and returns its output to the driver.

pub fn handle(req : String) -> String {
    req
}
Enter fullscreen mode Exit fullscreen mode

Demonstration

faas-cli template pull https://github.com/booyaa/openfaas-rust-template
faas-cli new trustinrust --lang rust
Enter fullscreen mode Exit fullscreen mode

Here's what our folder structure looks like.

.
├── trustinrust
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── trustinrust.yml
Enter fullscreen mode Exit fullscreen mode

You want to edit trustinrust.yml and update the following values (other parts of the code have been omitted):

provider:
  gateway: http://127.0.0.1:31112

functions:
  shuffle:
    image: DOCKER_ID/shuffle
Enter fullscreen mode Exit fullscreen mode

The gateway assumes you're running on port 8080, kubernetes runs on a different port.

I also found the containers couldn't find the local repository so adding your docker id to the image ensures it uploads to Docker Hub instead.

The trustinrust folder contains the boilerplate code from the function crate.

For our demo, we're going to make a function that shuffle a list of items that are comma separated.

We'll add rand crate to our dependancies in the Cargo.toml. As you can see we're pretty much writing standard Rust, add in libraries as we need them.

[dependencies]
rand = "0.5.1"
Enter fullscreen mode Exit fullscreen mode

Here's the code that's going to go in src/lib.rs (apologies for my awful Rust)

extern crate rand;

use rand::{thread_rng, Rng};

pub fn handle(req : String) -> String {
    let mut rng = thread_rng();
    let split = req.split(",");
    let mut vec: Vec<&str> = split.collect();
    rng.shuffle(&mut vec);
    let joined = vec.join(", ");
    format!("{:?}", joined)
}
Enter fullscreen mode Exit fullscreen mode

If you wanted, you can run cargo build to see if the code will build. If you had tests, this would be testable via cargo test.

Now we can build, push the image to Docker Hub and deploy to OpenFaaS.

faas-cli build -f trustinrust.yml
faas-cli push -f trustinrust.yml    # pushes image to docker
faas-cli deploy -f trustinrust.yml
Enter fullscreen mode Exit fullscreen mode

Finally we can test our function!

curl -d 'alice,bob,carol,eve' \
> http://localhost:31112/function/trustinrust

"bob, alice, eve, carol"
Enter fullscreen mode Exit fullscreen mode

Summary

It was a lot of fun experimenting with serverless functions and Rust. I can see the appeal of serverless functions, reducing functionality to the absolutely minimum. I think my next foray into this space is to see if I can convert my Slack slash commands into serverless functions (they're currently hosted on Heroku).

If you have to go for yourself, the template and demo function can be found below:

Further reading

If you want to learn more you should look out for videos by the Kubernetes Legend Kelsey Hightower. I can highly recommend Kubernetes For Pythonistas, which features the now-famous Tetris analogy for DevOps. Also seek out Alex Ellis on the youtubes and his excellent collection of Docker, Kubernetes and OpenFaaS videos.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .