Building a WASM Serverless Solution with KEDA HTTP Add-on and Slight Containerd Shim
Introduction
In this article, we will explore how to use KEDA, KEDA HTTP add-on, with Slight Containerd Shim to build a WASM serverless solution. The solution provides scale to/from zero support, reduced time for cold start, and cloud integration capabilities such as Azure blob, App Configuration, ServiceBus etc. The diagram below shows how it works.
- Incoming HTTP request is routed through Nginx Ingress Controller to KEDA-HTTP interceptor, the interceptor keeps track of the number of pending HTTP requests - HTTP requests that it has forwarded but the app hasn't returned.
- KEDA HTTP add-on operator runs insider of Kubernetes cluster, watches for HTTPScaledObject, it creates a ScaledObject for the Deployment specified in the HTTPScaledObject resource.
- ScaledObject points to interceptor as KEDA external scaler, uses GRPC to get the size of the pending queue. Based on this queue size, it reports scaling metrics as appropriate to KEDA. As the queue size increases, the scaler instructs KEDA to scale up as appropriate.
- KEDA manages 0<->1 scaling and leverages HPA for 1 <-> n scaling.
- Kubernetes watches for WASM Pod being created, notices its runtime is wasmtime-slight-v1, leverages slight containerd shim to start SpiderLightning host runtime for WASM application.
- WASM application receives HTTP request and replies back.
Before jump into the implementation, let's take a close look of those components used in the solution
WASM
WebAssembly (WASM) is an open standard that defines a portable binary-code format for executable programs. It is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
WASM allows serverless applications to be faster and more efficient. WASM enables the deployment of scripts and programs that can run on the serverless environment with fewer resources than traditional scripting languages. This makes serverless applications more cost-effective, as well as faster and more reliable. Additionally, using WASM to develop serverless applications allows for easier portability and integration with different platforms.
The Open Container Initiative (OCI) provides support to package WASM application into container image. Generally speaking, a minimal WASM container image is typically a few hundred kilobytes in size. For example, a sample WASM container image used in this solution is only 64.7KB.
IMAGE TAG IMAGE ID SIZE
...
ghcr.io/huangyingting/rust-slight main 8ce9ccccf9eea 64.7kB
...
KEDA and KEDA HTTP Add-on
KEDA is a Kubernetes-based Event Driven Autoscaler. With KEDA, you can drive the scaling of any container in Kubernetes based on the number of events needing to be processed.
KEDA provides a reliable and well tested solution to scaling your workloads based on external events. However KEDA doesn't provide an HTTP-based scaler. The KEDA HTTP Add-on allows Kubernetes users to automatically scale their HTTP servers up and down (including to/from zero) based on incoming HTTP traffic. Below diagram is copied from KEDA HTTP Add-on design page and shows how it works here
SpiderLightning and Slight Containerd Shim
SpiderLightning(Slight) is an open source WASM host(based on WasmTime), for building and running fast, secure, and composable cloud microservices with WebAssembly. It defines a set of WebAssembly Interface Types (i.e., WIT) files that abstract distributed application capabilities, such as state management, pub/sub, event driven programming, and more.
Slight Containerd Shim is a containerd shim powered by the SpiderLightning engine allows you to run WASM applications developed with SpiderLightning SDKs(C and Rust are supported currently)
Implementation
The solution implementation requires a few of components being deployed, includes
- Slight Containerd Shim
- Nginx Ingress Controller
- KEDA and KEDA HTTP add-on
- Redis - used by demo application to illustrate SpiderLightning capabilities
- Sample application and manifests
Detailed steps are
Install Slight Containerd Shim
Refer to "Deis Labs Containerd Wasm Shims" section from my previous article Run WASM applications from Kubernetes
Deploy Nginx Ingress Controller
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx
Deploy KEDA and KEDA HTTP add-on
Deploy KEDA
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
kubectl create namespace keda
helm install keda kedacore/keda --namespace keda
Deploy KEDA HTTP add-on
helm install http-add-on kedacore/keda-add-ons-http --namespace keda
Deploy Redis
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install redis bitnami/redis
Sample Application and Manifests
A sample WASM application is created to read/write/delete redis cache, the source code is located at this repo
To deploy this application, follow below steps
- Generate a yaml file with below content, replace
REPLACE_IT_WITH_REDIS_ADDRESS
with redis address, if you followed above redis deployment step, the redis address generally will be redis://redis-master.redis:6379. ReplaceREPLACE_IT_WIHT_FQDN
with a FQDN name for external access, for example, rust-slight.yourdomain.com
apiVersion: v1
kind: Namespace
metadata:
name: rust-slight
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rust-slight
namespace: rust-slight
spec:
replicas: 1
selector:
matchLabels:
app: rust-slight
template:
metadata:
labels:
app: rust-slight
spec:
runtimeClassName: wasmtime-slight
containers:
- name: rust-slight
image: ghcr.io/huangyingting/rust-slight:main
imagePullPolicy: IfNotPresent
command: ["/"]
env:
- name: REDIS_ADDRESS
value: REPLACE_IT_WITH_REDIS_ADDRESS
---
apiVersion: v1
kind: Service
metadata:
name: rust-slight
namespace: rust-slight
spec:
type: ClusterIP
ports:
- protocol: TCP
port: 3000
targetPort: 3000
selector:
app: rust-slight
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rust-slight
namespace: keda
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: REPLACE_IT_WIHT_FQDN
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: keda-add-ons-http-interceptor-proxy
port:
number: 8080
---
kind: HTTPScaledObject
apiVersion: http.keda.sh/v1alpha1
metadata:
name: rust-slight
namespace: rust-slight
spec:
host: REPLACE_IT_WIHT_FQDN
targetPendingRequests: 200
scaleTargetRef:
deployment: rust-slight
service: rust-slight
port: 3000
replicas:
min: 0
max: 10
- Run
kubectl apply -f manifest.yaml
to deploy the application - Notice the application is scaled to 0 as currently there is no HTTP request
kubectl get deploy -n rust-slight
NAME READY UP-TO-DATE AVAILABLE AGE
rust-slight 0/0 0 0 34s
- Send a HTTP request to see what happens
curl http://rust-slight.yourdomain.com/read
- After 1 or 2 seconds, our WASM application echoes HTTP request back
user-agent: curl/7.81.0
accept: */*
x-forwarded-for: X.X.X.X, 192.168.2.19
x-forwarded-host: rust-slight.yourdomain.com
x-forwarded-port: 80
x-forwarded-proto: http
x-forwarded-scheme: http
x-real-ip: X.X.X.X
x-request-id: 6ccba5facab304b26cbb129b92fca9f6
x-scheme: http
accept-encoding: gzip
- Check deployment again, it is scaled out to 1
kubectl get deploy -n rust-slight
NAME READY UP-TO-DATE AVAILABLE AGE
rust-slight 1/1 1 1 2s
- Application itself supports redis read, write and delete, here are some sample commands that you can play with
# Write
curl -X PUT -d '{"key": "version", "value": "1.0.0"}' http://rust-slight.yourdomain.com/create
# Update
curl -X POST -d '{"key": "version", "value": "1.0.1"}' http://rust-slight.yourdomain.com/update
# Delete
curl -X DELETE -d '{"key": "version"}' http://rust-slight.yourdomain.com/delete
# Read
curl http://rust-slight.yourdomain.com/read