blambda icon indicating copy to clipboard operation
blambda copied to clipboard

Blambda! is a custom runtime for AWS Lambda that lets you write functions using Babashka

Blambda

Blambda is a custom runtime for AWS Lambda that lets you write functions using Babashka. It is based on the fantastic work that Tatu Tarvainen did on taking care of the heavy lifting of interacting with the Lambda runtime API to process function invocations in bb-lambda. I'm using the bootstrap.clj from that project directly, and have just rewritten the machinery around it to remove Docker in favour of zip files, which I think are simpler (but maybe not easier).

Blambda also owes a huge debt to Karol Wójcik's awesome Holy Lambda, which is a full-featured and production-grade runtime for Clojure on AWS Lambda. I've read a lot of Holy Lambda code to figure out how to do the complicated bits of Babashka-ing on lambda. 💜

Using Blambda

Blambda is meant to be used as a library from your Babashka project. The easiest way to use it is to add tasks to your project's bb.edn.

This example assumes a basic bb.edn like this:

{:deps {net.jmglov/blambda
        #_"You use the newest SHA here:"
        {:git/url "https://github.com/jmglov/blambda.git"
         :git/sha "b9a8b32c41e72ca3619e8b7ab839eebfb133d79c"}}
 :tasks
 {:requires ([blambda.cli :as blambda])
  blambda {:doc "Controls Blambda runtime and layers"
           :task (blambda/dispatch)}}}

Building

To build Blambda with the default Babashka version and platform, run:

bb blambda build-runtime-layer

To see what the default Babashka version and platform are, run:

bb blambda build-runtime-layer --help

To build a custom runtime with Babashka 0.8.2 on amd64, run:

bb blambda build-runtime-layer --bb-version 0.8.2 --bb-arch arm64

Deploying

To deploy Blambda, run:

bb blambda deploy-runtime-layer

To deploy an arm64 runtime so that you can use AWS Graviton 2 lamdbas (which AWS say will give you up to "34%" better price performance), run:

bb blambda build-runtime-layer --bb-arch arm64 && \
bb blambda deploy-runtime-layer --bb-arch arm64

Note that if you do this, you must configure your lambda as follows:

  • Runtime: Custom runtime on Amazon Linux 2
  • Architecture: arm64

Dependencies

All but the most basic lambda functions will depend on Clojure libraries. Blambda has support for keeping these dependencies in a separate layer so that your function deployment contains only the source of your lambda itself.

Your lambda should declare its dependencies in bb.edn or deps.edn as normal; for example, a lambda function that interacts with S3 using awyeah-api might have a src/bb.edn that looks like this:

{:paths ["."]
 :deps {com.cognitect.aws/endpoints {:mvn/version "1.1.12.206"}
        com.cognitect.aws/s3 {:mvn/version "822.2.1109.0"}
        com.grzm/awyeah-api {:git/url "https://github.com/grzm/awyeah-api"
                             :git/sha "0fa7dd51f801dba615e317651efda8c597465af6"}
        org.babashka/spec.alpha {:git/url "https://github.com/babashka/spec.alpha"
                                 :git/sha "433b0778e2c32f4bb5d0b48e5a33520bee28b906"}}}

To build your dependencies layer:

bb blambda build-deps-layer --deps-path src/bb.edn

And then to deploy it:

bb blambda deploy-deps-layer --deps-layer-name my-lambda-deps

Basic example

I'm planning on adding example tasks for deploying layers and functions, but for now, you can do it the hard way with the AWS CLI.

AWS CLI

This section assumes you have the AWS Command Line Interface version 1 installed.

Assuming you're standing in the root of the Blambda repo, you will have an example directory that contains a hello.clj that looks something like this:

(ns hello)

(defn hello [{:keys [name] :or {name "Blambda"} :as event} context]
  (prn {:msg "Invoked with event",
        :data {:event event}})
  {:greeting (str "Hello " name "!")})

You can create a function that uses Blambda like this:

# The ARN will be printed by the `bb blambda deploy-runtime-layer` command
layer_arn=arn:aws:lambda:eu-west-1:123456789100:layer:blambda:1

cd example

zip hello-blambda.zip hello.clj

aws iam create-role \
  --role-name hello-blambda \
  --assume-role-policy-document file://trust.json

# Set this to the value of `Role.Arn` from the output of the previous command
role_arn=arn:aws:iam::123456789100:role/hello-blambda

aws iam create-policy \
  --policy-name hello-blambda \
  --policy-document file://policy.json

# Set this to the value of `Policy.Arn` from the output of the previous command
policy_arn=arn:aws:iam::123456789100:policy/hello-blambda

aws iam attach-role-policy \
  --role-name hello-blambda \
  --policy-arn=$policy_arn 

aws lambda create-function \
  --function-name hello-blambda \
  --runtime provided \
  --role $role_arn \
  --handler hello/hello \
  --layers $layer_arn \
  --zip-file fileb://hello-blambda.zip

You can invoke the function like this:

aws lambda invoke \
  --function-name hello-blambda \
  --payload '{}' \
  /dev/stdout

You should see something like this:

{"greeting":"Hello Blambda!"}{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

Of course, you can also get more personal:

aws lambda invoke \
  --function-name hello-blambda \
  --payload '{"name": "Josh"}' \
  /dev/stdout
{"greeting":"Hello Josh!"}{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}