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...