LiteFunctions

What - serverless on your cluster: Lambda-shaped, without the vendor

Why - building from first principles — cuz we can 😉

Hello 👋🏻





I'm Ashu

I do go/rust stuff for infra, building for the craft #trad coder
Day job: Sr. software - compute infra @ Rippling
2

buzzword - Serverless



Yeah, the popular kid before AI.. in a gist, serverless is just "delegated infra"
#good, pay as you go #bad, vendor/stack lock in, can be pricey
3

So we want self hosted lambda


what's it doing.. you write functions; you get apis — same “lambda” contract, on your cluster.
Artifact Hub: litefunctions Artifact Hub QR
helm install litefunctions oci://registry-1.docker.io/ashupednekar535/litefunctions
what we got
Kubernetes Go
curl -sS -X POST "https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/Spacelift_Test_Lambda_Function/invocations" \
  -H "Content-Type: application/json" \
  -d '{"key1":"world"}'
4

So what's infra, in the most generic sense


version control Git
CI Jenkins
CD Argo CD
orchestration Kubernetes
pubsub NATS
storage Crunchy Data
cache Redis
traffic Istio



So this is neither a dependency list nor undermining us infra folks ;) It’s just what’s in our toolbox for typical backends — storage, events, and the compute & traffic that runs them.
LiteFunctions is supposed to package all of this so it’s abstracted away.
5

Let's log in to see what it's got

WebAuthn into the Portal — pick a project, see functions and runs in one place.
6

Let's write an API

Function + route + language in the editor — save, and commit. Vim motions btw
7

There you go

we got ourselves an api
8

How about some websockets

Same stack — async path over NATS; WS / stream where the runtime exposes it.
9

What does lambda look like

At a high level it’s the layer between traffic and your code: it tracks what’s deployed, queues and routes work, and scales compute with traffic—so you ship functions and let the platform own inventory, concurrency, and runners instead of wiring that yourself.
Lambda-style architecture: control plane, invoke path, and runtimes
10

Let’s understand what needs to happen




edit Neovim
build Docker
spawn Pod
route Istio
run
Go Rust Python JavaScript


Bottom line, we version function code, build artifacts and spin up pods in response to traffic For dynamic languages, hot reload the code into pre-provisioned runtimes Kubernetes and node pools are where scaling and capacity land—scheduling, autoscaling, elasticity under those pods
11

Components

LiteFunctions architecture
Operator
Reconciles functions and wiring in the cluster from CRDs.
Gateway
TLS and routing from clients into invokes.
Ingestor
HTTP entry: handles sync invokes; publishes async work to the broker.
Runtimes
Runs your functions—Go, Rust, Python, TypeScript, Lua—behind one invoke contract.
Dependencies & state
Crunchy Postgres
Crunchy Postgres
Durable state and metadata
Valkey
Valkey
Cache, queues, fast key/value
NATS
Broker (NATS)
Pub/sub and event streams
12

Where does go come in


Well… everywhere! Right from litefunctions components like ingestor, operator, runtime, the ui to istio, nats, cruncy, and kubernetes itself...
13

litefunctions/ingestor

QR: request.go on GitHub
Ingestor data flow

Clients hit the ingestor first—sync calls map to a straight HTTP request/response path.

Async work goes over NATS (our pub/sub): the ingestor stays fast and dumb, publishes off the HTTP hot path, and runtimes subscribe and do the work—so the transport stays abstracted from your function code, same idea as in the lambda FaaS write-up.

The ingestor can also request the operator to warm a function, basically set is_active on the Function CRD to true so reconcile runs.

ingestor/pkg/broker/request.go
WebSocket → NATS · Req + Produce · subject …exec.lang.reqId
package broker

type Req struct {
	Project, Name, Lang, ReqId string
}

func Produce(
	nc *nats.Conn,
	w http.ResponseWriter,
	r *http.Request,
	lang string,
) (*websocket.Conn, *Req, error) {
	// Upgrade; goroutine: ReadMessage → Publish
}
14

Sync handler

Read r.Body, then write headers and the response body on w in one goroutine—no channels; the “plate” is what you Encode or Write.

package pkg

import (
	"encoding/json"
	"net/http"
)

func Handle(w http.ResponseWriter, r *http.Request) {
	var req map[string]any
	_ = json.NewDecoder(r.Body).Decode(&req)
	resp := map[string]any{"ok": true, "echo": req}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	_ = json.NewEncoder(w).Encode(resp) // response body
}
15

Async consumer

Chunks arrive on input; you spin a goroutine that sends on out—like plates from the belt.

Runtime / WS can drain out to the wire—decoupled from one HTTP read.

package pkg

func StreamHandler(input <-chan []byte) <-chan []byte {
	out := make(chan []byte)
	go func() {
		defer close(out)
		for raw := range input {
			c := transform(raw)
			out <- c // may block (backpressure)
		}
	}()
	return out
}

func transform(b []byte) []byte { return b }
16

Static runtimes

QR: Go consumer
go consumer
QR: Rust consumer
rust consumer

Ship small static binaries in distroless or scratch—images stay tiny (often tens of MB, few layers). Each function gets its own dedicated consumer pod that starts fast.

Go wants a single static Linux binary by default—drop-in for scratch. Rust can do the same (--target …-musl / static link), or emit a cdylib (.so) for plugins—then you’re in dynamic-linking / loader land. For these pods we stick to a static bin in both languages so the deploy story matches Go’s “one artifact.”

Scale each function up or down independently without dragging a shared interpreter along.

Gotcha · runtime
Since Go 1.22, default GOMAXPROCS follows Linux cgroup CPU limits—great in containers, but it breaks old assumptions that GOMAXPROCS tracked the whole host. Re-check load tests and pprof if you sized workers as “all CPUs.”
Gotcha · scratch & deps
FROM scratch means no libc—for HTTPS you need pure Go TLS (crypto/tls) and bundle CA certs if you verify remotes. Avoid CGO wrappers (e.g. libgit2): we used go-git with a billy-style filesystem so Git stays pure Go and the binary stays static-friendly. Same idea for any dep: prefer stdlib / pure Go over .so shims.
17

Dynamic runtimes

QR: Python consumer
py consumer

Python / JS share a long-lived runtime per project: pull sources from VCS, then hot-reload into the running process—iteration without a full image rebuild every time.

Tradeoff: larger images (hundreds of MB, many layers) and slower cold paths than static binaries—balanced by that reload loop.

pseudo · git event → reload
# Python
async def on_git_update(event):
    await sync_repo() # fetch / checkout
    importlib.invalidate_caches()
    mod = importlib.reload(sys.modules["functions." + name])
    # register handler; next request uses new code

// Node
async function onGitUpdate() {
  await git.pull();
  delete require.cache[require.resolve("./functions/" + name)];
  handlers.set(name, require("./functions/" + name));
}
Gotcha
Python: importlib + sys.modules—stale modules after hot swap; invalidate deliberately. GIL doesn’t fix races on shared globals across concurrent requests. JS (Node): require cache—bust or version modules on reload; one event loop, but shared mutable state still bites under load.
18

LiteFunctions Operator

An operator basically lets us have Kubernetes understand a custom YAML and do what we need to on the cluster
19

$ litefunctions/operator

QR: operator on GitHub
internal/controller/function_controller.go
Reconcile is the heart: read desired state from the API (backed by etcd), then create/update/delete Deployments and Services to match your Function CRD.
package controller

type FunctionReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

func (r *FunctionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	// Get Function; early-exit on NotFound
	// If !IsActive: maybe delete Deployment+Service (skip if shared runtime still in use)
	// If active: NewDeployment / NewService; SetControllerReference; Create or Update
	// Patch Function.Spec.DeProvisionTime (keep-warm)
}
Reconcile path function activation spin up deploy / HPA map services set deprovision time cleanup
20

Lots to improve

GitHub issues for litefunctions
QR: github.com/ashupednekar/litefunctions github.com/ashupednekar/litefunctions
function packages
gateway ingress policies
HTTP over pubsub
alternative runtimes like Firecracker / microVMs / unikernels
…and much more
issues / PRs welcome 👋
21

Than:q

That was it folks… questions?
come hang with me over at…
LinkedIn QR
X QR
Threads QR
Bluesky QR
👋
22
Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)