Knative + urunc: Deploying Serverless Unikernels🔗
This guide walks you through deploying Knative Serving with urunc support on Kubernetes. We provide pre-built binaries for quick setup, or you can build Knative from source using ko for custom configurations.
Prerequisites🔗
- A running Kubernetes cluster
uruncinstalled (follow the installation guide)
Install Knative Serving🔗
Option 1: Use Pre-built Knative (Recommended)🔗
Apply the pre-built Knative manifests with urunc support:
Note: There are cases where due to the large manifests, kubectl fails. Try a second time, or use
kubectl create -f https://s3.nbfc.io/knative/knative-v1.17.0-urunc-5220308.yaml
Option 2: Build Knative from Source🔗
If you prefer to build Knative yourself, follow these steps.
Prerequisites for building from source🔗
- A Docker-compatible registry (e.g. Harbor, Docker Hub)
- Ubuntu 20.04 or newer
- Basic
git,curl, andkubectlinstalled - Docker installed (needed for container registry interaction and
kobuilds) - Go (>= 1.23, tested with 1.26.4) installed
koinstalled
Set your container registry🔗
Note: You should be able to use Docker Hub for this. e.g.
<yourdockerhubid>/knative
Clone urunc-enabled Knative Serving and build🔗
git clone https://github.com/nubificus/serving -b feat_urunc
cd serving/
ko resolve -Rf ./config/core/ > knative-custom.yaml
Apply knative's manifests to the local k8s🔗
Setup Networking (Kourier)🔗
Install kourier, patch ingress and domain configs🔗
kubectl apply -f https://github.com/knative-extensions/net-kourier/releases/latest/download/kourier.yaml
kubectl patch configmap/config-network -n knative-serving --type merge -p '{"data":{"ingress.class":"kourier.ingress.networking.knative.dev"}}'
kubectl patch configmap/config-domain -n knative-serving --type merge -p '{"data":{"127.0.0.1.nip.io":""}}'
Enable RuntimeClass and urunc Support🔗
Install urunc🔗
You can follow the documentation to install urunc from: Installing
Enable runtimeClass for services, nodeSelector and affinity🔗
kubectl patch configmap/config-features --namespace knative-serving --type merge --patch '{"data":{
"kubernetes.podspec-affinity":"enabled",
"kubernetes.podspec-runtimeclassname":"enabled",
"kubernetes.podspec-nodeselector":"enabled"
}}'
Deploy a Sample urunc Service🔗
Create a simple httpreply service, based on a simple C program:
kubectl apply -f https://raw.githubusercontent.com/nubificus/c-httpreply/refs/heads/main/service.yaml
Check Knative Service🔗
Output should look like:
$ kubectl get ksvc -A -o wide
NAMESPACE NAME URL LATESTCREATED LATESTREADY READY REASON
default hellocontainerc http://hellocontainerc.default.127.0.0.1.nip.io hellocontainerc-00001 hellocontainerc-00001 True
Get the ingress IP🔗
Before testing the service, get the IP address of the Kourier internal service:
This command returns the internal ClusterIP (e.g., 10.244.9.220). Use this value in the next curl command.
Test the service🔗
Replace <INGRESS_IP> with the IP address from the previous step:
Now, let's create a urunc-compatible function. Create a file named unikernel-service.yaml with the following content (based on Unikraft's httpreply example):
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hellounikernelfc
spec:
template:
spec:
runtimeClassName: urunc
containers:
- name: user-container
image: harbor.nbfc.io/nubificus/knative-example-functions/httpreply-fc:latest
imagePullPolicy: Always
env:
- name: RUNTIMECLASS
value: "urunc"
Note: Naming the container
user-containeris required. If a custom name is used,uruncwill use dynamic networking (tc redirect filters), which redirects all container traffic to the VM and blocks thequeue-proxysidecar. Naming ituser-containeruses static networking (iptables NAT), allowing both containers to communicate correctly.
Apply the manifest:
You should be able to see this being created:
$ kubectl get ksvc -o wide
NAME URL LATESTCREATED LATESTREADY READY REASON
hellounikernelfc http://hellounikernelfc.default.127.0.0.1.nip.io hellounikernelfc-00001 hellounikernelfc-00001 True
Once it's in a Ready state, invoke the function using the ingress IP you obtained earlier:
$ curl -v -H "Host: hellounikernelfc.default.127.0.0.1.nip.io" http://10.244.9.220:80
* Trying 10.244.9.220:80...
* Connected to 10.244.9.220 (10.244.9.220) port 80 (#0)
> GET / HTTP/1.1
> Host: hellounikernelfc.default.127.0.0.1.nip.io
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 14
< content-type: text/html; charset=UTF-8
< date: Tue, 08 Apr 2025 15:47:45 GMT
< x-envoy-upstream-service-time: 774
< server: envoy
<
Hello, World!
* Connection #0 to host 10.244.9.220 left intact