I Ported Kubernetes to the Browser

mariuz1 pts0 comments

I ported Kubernetes to the browser | ngrok blog

Skip to main contentSearch…Control⌃KNewsletterRSS

Last week I released webernetes, a partial port of Kubernetes to TypeScript<br>to make it possible to run clusters in the browser. I ended up generating almost<br>100,000 lines of code in 552 commits across 629 files. It took me 2 months.

The demo below is a webernetes cluster. It runs entirely in your browser, and<br>it’s genuinely doing much of the same work a real Kubernetes cluster does: pod<br>lifecycles, cluster DNS and networking, container garbage collection, IP<br>allocation, Deployment and ReplicaSet tracking, and more. The blue dots<br>represent pods sending requests to each other.

Interactive Webernetes demo showing HTTP requests moving between three pods from a Deployment across three Kubernetes nodes.<br>A cluster in your browser<br>ResetPause

Starting simulated cluster.<br>node-1

node-2

node-3

Replicas9

Requests/sec/replica1

Bookmark this sectionWait, what am I looking at?

The question I’ve been getting most often is: “Did you compile Kubernetes to<br>WebAssembly?” The answer is no. A simple “hello, world!” Go program compiled to<br>WebAssembly is ~540KiB gzipped. That alone is already bigger than webernetes,<br>which is ~140KiB gzipped. Compiling all of Kubernetes to WebAssembly would no<br>doubt mean sending megabytes over the wire. I did try to check, but<br>unfortunately there are compile-time errors because Kubernetes calls system-level<br>APIs that aren’t available in the browser.

Instead, webernetes is:

A partial port of Kubernetes’ “kubelet” binary, enough to run pods and probe them.

Ports of several Kubernetes “controllers”: pod scheduler, namespace controller, kube-proxy, deployment controller, and a few more.

A browser-based take on a container network interface (CNI), so pods can talk to each other over a simulated network.

A browser-based container runtime, which the kubelet talks to over the container runtime interface (CRI) to run containers.

An API for interacting with your webernetes cluster to do things like apply manifests and watch resources.

As a result of the desire to keep webernetes small, it doesn’t pull real images<br>from a registry like Docker Hub. Instead, it has its own browser-based registry<br>and you define images using a TypeScript API. Images look like this:

Copy code1import * as w8s from "@ngrok/webernetes";2 3class HelloWorld extends w8s.BaseImage {⋯4 static readonly imageName = "hello-world";5 static readonly imageVersion = "1.0";6 7 async exec(ctx: w8s.ProcessContext, argv: string[]): Promisenumber> {⋯8 ctx.listenHttp(8080, async (_ctx, request) => {⋯9 return {⋯10 status: 200,11 body: "Hello, world!",12 };13 });14 return await ctx.waitUntilKilled();15 }16}

To deploy your image into a cluster, you do this:

Copy code1import * as w8s from "@ngrok/webernetes";2 3class HelloWorld extends w8s.BaseImage {⋯4 // as before ...5}6 7const cluster = new w8s.Cluster();8await cluster.registerImage(HelloWorld);9 10const [pod] = await cluster.apply([⋯11 {⋯12 apiVersion: "apps/v1",13 kind: "Deployment",14 metadata: { name: "hello-world-deployment" },15 spec: {⋯16 replicas: 1,17 selector: {⋯18 matchLabels: { app: "hello-world-pod" },19 },20 template: {⋯21 metadata: {⋯22 labels: { app: "hello-world-pod" },23 },24 spec: {⋯25 containers: [⋯26 {⋯27 name: "hello-world-container",28 image: "hello-world:1.0",29 },30 ],31 },32 },33 },34 },35]);

And then you can use the webernetes API to interact with the cluster, like this:

Copy code1// List all pods in the default namespace2const pods = await cluster.api.corev1.listNamespacedPod({⋯3 namespace: "default",4});5 6// Watch for changes to pods in all namespaces7const informer = cluster.informer("pods", (type, pod) => {⋯8 console.log(`pod ${type}: ${pod.metadata?.name}`);9});10 11// Stop the informer when you're done12await informer.stop();13 14// Listen to pods sending requests and responses to each other.15// This is how I visualise the moving dots above.16cluster.on("request", (event) => {⋯17 console.log(`request: ${event.request.method} ${event.request.url}`);18});19cluster.on("response", (event) => {⋯20 console.log(`response: ${event.response?.status}`);21});22 23// Use the cluster network to send a request to a pod. This will also trigger24// the request/response event handlers above.25const pod = pods.items[0];26const resp = await cluster.fetch(`http://${pod.status?.podIP}:8080/`);27console.log(resp.body); // "Hello, world!"

There are plenty more examples in the webernetes repository.

Webernetes is intended to be used to make interactive Kubernetes content; it’s<br>not a production-ready Kubernetes distribution. It doesn’t need to run real<br>images. It just needs a way for creators to set up specific workloads to<br>illustrate the thing they’re trying to teach.

Over time, it is my intention to expand webernetes to support more Kubernetes<br>features. Right now, it doesn’t support ConfigMaps, Secrets, pod resources,<br>persistent volumes, and a whole host of other things...

cluster kubernetes webernetes pods browser hello

Related Articles