AssemblyLift alpha latest: easy API Gateway for Kubernetes functions, Ruby language support 💎
Tags: kubernetes, cloud, webassembly, ruby
Published: 2022-06-22 16:41:59

Hello again y'all! 👋🏻

Since our [last post introducing the AssemblyLift v0.4-alpha series](https://dev.to/akkoro/assemblylift-v040-alpha-released-with-kubernetes-support-wasi-webassembly-on-k8s-5bj8), we've had two new alpha [releases](https://github.com/akkoro/assemblylift/releases) which introduced some major additions & improvements!

In this post we'll talk about each change and what it means, and then we'll walk through a short example demonstrating the  new features. 🙂

## Revised TOML manifest spec

Manifests (`assemblylift.toml` and `service.toml`) now use arrays to describe most entries which used to be written using tables.

For example, the `[services]` table in `assemblylift.toml` is now the `[[services]]` array.

### Why?

The primary data structures in [TOML](https://toml.io/en/) are arrays and tables.

Previous releases leaned heavily on _tables_. A typical `assemblylift.toml` for example might be:
```toml
[project]
name = "my-project"

[services]
my_service = { name = "my-service" }
another = { name = "another-service" }
```

Both `[project]` and `[services]` are tables. The `my_service` key also points to a table. Expressing services this way (as well as functions, authorizers, etc.) is a bit verbose considering that these things are also named.

By comparison, using arrays to add multiple services to `assemblylift.toml` for example looks like:
```toml
[project]
name = "project"

[[services]]
name = "service1"

[[services]]
name = "service2"
```

Which avoids redundant information (the table key) and becomes easier to read.

## Ruby language support

AssemblyLift is now bilingual! Functions can now be written in Ruby in addition to Rust.

To declare a function a Ruby function, set the `language` parameter.

For example:
```toml
[[api.functions]]
name     = "my-ruby-func"
language = "ruby"
http     = { verb = "GET", path = "/my-service/my-ruby-func" }
```

A Ruby function requires `handler.rb` to be the entrypoint. AssemblyLift ships a custom build of the Ruby runtime which includes a module for interop with the AssemblyLift platform.

We'll explore this in more detail in the demo section below!

### Why?

We ❤️ Ruby! Most commonly associated with the Rails framework, Ruby is a powerful & mature scripting language suitable for many purposes. 

Ruby recently gained support for WebAssembly, and we almost immediately went about integrating it into AssemblyLift! 💨

While you can't (yet?) lift your existing Rails app to a Function, the Ruby language is familiar to many devs. Ruby could especially be preferable in cases where performance is less of a concern, such as automation tasks for example.

## Support for Gloo Edge API Gateway

Another feature addition we're very excited about is HTTP API support for the new Kubernetes provider. The previous alpha release could deploy functions to a K8s cluster, but there was no practical way yet to invoke them!

This release will automatically install [Gloo Edge](https://docs.solo.io/gloo-edge/latest/introduction/) on the target cluster. AssemblyLift will deploy a Gateway, and set up routing for any functions running in a K8s service which define an HTTP API.

### Why?

An API Gateway is central to the AssemblyLift architecture. AWS Lambda has a natural pairing with Amazon API Gateway, but Kubernetes of course is open-ended.

For this first iteration, Gloo Edge was chosen as it is open-source, easy to automate, and very full-featured. Being itself based on [Envoy Proxy](https://www.envoyproxy.io/) we can eventually explore its support for WASM traffic filters in Gloo Edge Enterprise.

A potential drawback is that Gloo may be heavier than needed for your application (our test deployment required 2vCPU per node to run Gloo + an AssemblyLift service). We'll soon look at support for other (lighter) reverse proxies. Eventually we want to support multiple API Gateway providers per backend, and also allow for cross deployment (Amazon APIGW in front of K8s, Gloo in front of Lambda).

## A short demonstration

Let's look at an example! If you want to try AssemblyLift yourself and follow along, you will need the prerequisites below. Otherwise if you just want the gist of things, you can skip that and keep reading 🙂

In this example we'll deploy a basic Ruby function to a Kubernetes cluster that writes to the function log, and `curl` it via an API Gateway.

### Prereqs

You will need a running Docker daemon on your development machine. You will also need a configured k8s config at the default `~/.kube/config`. So far AssemblyLift has been tested against Docker Desktop Kubernetes as well as Digital Ocean Kubernetes. It isn't strictly necessary, but is recommended for testing and debugging, to have `kubectl` installed.

You will also need a Docker image registry. Right now AssemblyLift supports Docker Hub and AWS ECR, however ECR is recommended as your registries can be provisioned/destroyed automatically. Currently, AssemblyLift will attempt to use AWS credentials from your default profile in `~/.aws/credentials`.

You can grab AssemblyLift for macOS with
```bash
curl -O public.assemblylift.akkoro.io/cli/0.4.0-alpha.3/x86_64-apple-darwin/asml
```

Or for Linux with
```bash
curl -O public.assemblylift.akkoro.io/cli/0.4.0-alpha.3/x86_64-linux-gnu/asml
```

Make the binary executable by running `chmod +x asml` and ensure it is available on your system `PATH`.

If your Linux system's glibc isn't compatible with the asml binary you may need to build from source instead. The easiest way to do this is to install with `cargo` by running `cargo install assemblylift --version 0.4.0-alpha.3`.

### Create a Ruby function

We'll create a new project with a Ruby template:
```bash
asml init -n ruby-example -l ruby
cd ruby-example
```

This will create a basic Ruby function called `my-function` inside a service `my-service`. These can be renamed as you like, however the directory names also currently need to be updated manually to match (we'll have this sync in a future release :)).

Add an HTTP route definition to the function in `service.toml`:
```toml
[[api.functions]]
name = "my-function"
language = "ruby"
registry = "ecr" # or "dockerhub" if using Docker Hub
http = { verb = "ALL", path = "/my-service/my-function" }
```

### Set provider and configure container registry

Again in `service.toml`, add the following to use the Kubernetes backend provider:
```toml
[service.provider]
name = "k8s"
```

Then in `assemblylift.toml`, configure the container registry you wish to use:
```toml
[[registries]]
host = "ecr"
options = { aws_account_id = "12345890", aws_region = "us-east-1" }

[[registries]]
host = "dockerhub"
options = { registry_name = "<name>", username = "<username>", password = "<password>" }
```

### Write some Ruby!

Next, let's open up the function's `handler.rb`:
```ruby
require 'asml'
require 'base64'
require 'json'

def main(input)
    # TODO implement your function code here!
    Asml.success(input.to_s)
end

main(JSON.parse(Asml.get_function_input()))
```

The `asml` module includes the `Asml` class, which exposes basic methods for interacting with the AssemblyLift runtime environment.

The `input` object has shape:
```json
{
  method: String,
  headers: Object,
  body_encoding: String,
  body: String
}
```

We can decode the body and log it:
```ruby
def main(input)
    body = input['body_encoding'] == 'base64' ? Base64.decode64(input['body']) : nil
    if body != nil then
        Asml.log(body)
    end
    
    Asml.success("OK")
end
```

### Build, deploy, test!

Generate your deployment by running `asml cast`. If there are no errors, you should see a Terraform plan in the output. 

Deploy your service with `asml bind`! Once deployed successfully, your cluster should have a Gloo Edge installation in the `gloo-system` namespace and a function deployed as `ClusterIP`s in an `asml-*` namespace for your service (you can check this by running `kubectl get namespaces`).

The Gloo Edge Gateway will have created a LoadBalancer resource, which should be deployed by your Kubernetes host (e.g. Digital Ocean). You should be able to `curl` the LoadBalancer public IP with your function's route!

```bash
curl -X POST -d "Hello World" http://<ip>/my-service/my-function
```

## Reach out!

Whether you have questions about using AssemblyLift or just want to chat, you can find us [on Discord](https://discord.gg/pVSCqYgra3)!