Skip to content

Repository files navigation

Webernetes

Kubernetes that runs in your browser.

To see it in action, check out the demo!

Wait, what?

This project is a port of a subset of the Kubernetes project to make it such that clusters can be booted up in the browser, without any backend server components.

But why?

At ngrok, we want to make visual and interactive content about Kubernetes. We didn't want to create and maintain infrastructure for spinning up real clusters, so we decided to create a browser-based simulator instead. The hope and dream is that this will make it possible for us (and you!) to create interactive Kubernetes content that lives for a long time, because the maintenance burden is much smaller.

Please note: This is very experimental. The API is subject to change, the level of support for different resources is subject to change. I'm kinda figuring this out as I go.

How does it work?

First, install webernetes as a dependency:

npm install @ngrok/webernetes

Then define an image to run in your cluster. Webernetes does not run real images from Docker Hub, nor is it a goal to do so.

import{BaseImage,typeProcessContext}from"@ngrok/webernetes";classMyImageextendsBaseImage{// The imageName and imageVersion variables are what make up the image label// you'll use in your container definition. Here we have my-image:1.0 but// webernetes also knows what to do if you specify just my-image or// my-image:lateststaticreadonlyimageName="my-image";staticreadonlyimageVersion="1.0";// If no other command is specified in your container manifest, this is the// command that will be passed in as argv below.readonlydefaultCommand=["server"];// exec is the main entrypoint for your image. It will be called with the// command-line arguments passed in from your container definition.overrideasyncexec(ctx: ProcessContext,argv: readonlystring[]): Promise<number>{if(argv[0]!=="server"){// The base image defines a bunch of core utils (cat, false, printenv,// etc.) so if we don't recognize the command, fall back to the base// image.returnawaitsuper.exec(ctx,argv);}// Binds to port 8080 on this container.ctx.listenHttp(8080,async(request)=>{return{statusCode: 200,body: "hello, world\n",};});// Required for long-running processes to be cancellable when clusters shut// down. If we returned an exit code of 0 here, the listener above would be// unregistered because this container will have exited.returnawaitctx.waitUntilKilled();}}

Then we create a cluster and register our image with it.

import{Cluster}from"@ngrok/webernetes";constcluster=newCluster();cluster.registerImage(MyImage);

And then we can run the cluster and spawn a pod using our image in it.

// By default this spins up a 3-node cluster. This can't currently be changed.awaitcluster.init();awaitcluster.apply([{apiVersion: "v1",kind: "Pod",metadata: {name: "my-pod",labels: {app: "my-pod"},},spec: {containers: [{name: "my-container",image: "my-image:1.0",},],},},]);

To send a request to your pod, you'll need to create a Service to talk to it. In this case, a NodePort service gives us the easiest route.

awaitcluster.apply([{apiVersion: "v1",kind: "Service",metadata: {name: "my-service"},spec: {type: "NodePort",ports: [{port: 80,targetPort: 8080,nodePort: 31000,protocol: "TCP",},],selector: {app: "my-pod",},},},]);constresp=awaitcluster.fetch("http://node-1:31000");consttext=awaitresp.text();// hello, world

Pods are also able to talk to each other over HTTP. To see how this works in a few runnable examples, check out the code under the examples/ directory. For the full visual demo, check out the code under the demo/ directory.

What's implemented and what isn't

I've scoped this so far to the bits I need to make the first piece of content I want to make, which is about probing.

I'll also preface this by saying I am by no means an exhaustive expert on every detail of Kubernetes, so it is likely that I'm missing some things or I haven't fully implemented the things I believe I have.

Nodes

  • Cluster spins up a 3-node cluster (node-1, node-2, node-3) and that isn't configurable yet. I would like to suppose arbitrarily adding and removing nodes in the future.

Namespaces

Supported, including special handling for deleting the resources within a namespace via a namespace controller (separate to the garbage collector controller that handles deleting everything else).

Pods

Basics are supported: Pods can have Containers and those containers can listen for HTTP traffic on ports. They get a pod name, an IP address, they can speak to other pods by their DNS name or IP address. They can accept environment variables. They get probed.

What's not supported yet:

  • Init or ephemeral containers.
  • gRPC probing.
  • Volume mounts.
  • Any sort of affinity rules.
  • Resources.
  • Probably a lot of other things, but those are the big ones that come to mine.

Services

Support for ClusterIP and NodePort services is in, LoadBalancer and ExternalName services are not yet supported. Pods can talk to service DNS names and the requests will be load balanced across the Pods in the service using round robin.

UDP isn't supported. TCP kinda sorta isn't either if you think about it, I'm not emulating that far down the network stack. Stuff can talk HTTP and DNS to each other and that's it. I don't anticipate ever wanting or needing to change this. As a result, the distinction between IP families also isn't really modeled.

EndpointSlices

A fun implementation detail of Services I had no idea existed until starting this project. These are created to track sets of Pods that are part of a Service. They're usually sharded into 100 Pods each but I haven't done that, purely for simplicity. They exist, they work how they should, but the sharding isn't there for now.

Events

Supported for the most part, and I've tried to make sure we fire the same events as Kubernetes does. I'm not doing any event aggregating, and it's possible not all fields are present and correct, but events with messages do get fired and can be inspected.

ReplicaSets

Supported and usually created by Deployments. ReplicaSet controller is also in place and largely at parity with the upstream Kubernetes ReplicaSet controller.

Deployments

Supported, including RollingUpdate and Recreate strategies. Deployment controller is in place and largely at parity with the upstream Kubernetes Deployment controller.

Development

This repo uses mise to pin the toolchain (Node, pnpm, ast-grep, ripgrep) so it's reproducible across machines. Node is read from .nvmrc and pnpm is single-sourced from package.json#packageManager.

After installing mise, from a fresh clone:

mise install # install the pinned tools (and write mise.lock)
mise run setup # install workspace dependencies from the lockfile

Available mise tasks:

  • mise run installpnpm install --frozen-lockfile.
  • mise run setup — prepare the repo after a fresh clone (runs install).
  • mise run relock — refresh mise.lock to match .nvmrc and package.json#packageManager.
  • mise run doctor — verify the active tools match the committed pins.

To bump a pinned version, edit .nvmrc, package.json#packageManager, or mise.toml and run mise run relock. The package scripts (pnpm test, pnpm build, pnpm vibe-check, etc.) are unchanged and run as usual once dependencies are installed.

About

Kubernetes in the browser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - DecOperations/webernetes: Kubernetes in the browser. · GitHub
Skip to content

Repository files navigation

Webernetes

Kubernetes that runs in your browser.

To see it in action, check out the demo!

Wait, what?

This project is a port of a subset of the Kubernetes project to make it such that clusters can be booted up in the browser, without any backend server components.

But why?

At ngrok, we want to make visual and interactive content about Kubernetes. We didn't want to create and maintain infrastructure for spinning up real clusters, so we decided to create a browser-based simulator instead. The hope and dream is that this will make it possible for us (and you!) to create interactive Kubernetes content that lives for a long time, because the maintenance burden is much smaller.

Please note: This is very experimental. The API is subject to change, the level of support for different resources is subject to change. I'm kinda figuring this out as I go.

How does it work?

First, install webernetes as a dependency:

npm install @ngrok/webernetes

Then define an image to run in your cluster. Webernetes does not run real images from Docker Hub, nor is it a goal to do so.

import{BaseImage,typeProcessContext}from"@ngrok/webernetes";classMyImageextendsBaseImage{// The imageName and imageVersion variables are what make up the image label// you'll use in your container definition. Here we have my-image:1.0 but// webernetes also knows what to do if you specify just my-image or// my-image:lateststaticreadonlyimageName="my-image";staticreadonlyimageVersion="1.0";// If no other command is specified in your container manifest, this is the// command that will be passed in as argv below.readonlydefaultCommand=["server"];// exec is the main entrypoint for your image. It will be called with the// command-line arguments passed in from your container definition.overrideasyncexec(ctx: ProcessContext,argv: readonlystring[]): Promise<number>{if(argv[0]!=="server"){// The base image defines a bunch of core utils (cat, false, printenv,// etc.) so if we don't recognize the command, fall back to the base// image.returnawaitsuper.exec(ctx,argv);}// Binds to port 8080 on this container.ctx.listenHttp(8080,async(request)=>{return{statusCode: 200,body: "hello, world\n",};});// Required for long-running processes to be cancellable when clusters shut// down. If we returned an exit code of 0 here, the listener above would be// unregistered because this container will have exited.returnawaitctx.waitUntilKilled();}}

Then we create a cluster and register our image with it.

import{Cluster}from"@ngrok/webernetes";constcluster=newCluster();cluster.registerImage(MyImage);

And then we can run the cluster and spawn a pod using our image in it.

// By default this spins up a 3-node cluster. This can't currently be changed.awaitcluster.init();awaitcluster.apply([{apiVersion: "v1",kind: "Pod",metadata: {name: "my-pod",labels: {app: "my-pod"},},spec: {containers: [{name: "my-container",image: "my-image:1.0",},],},},]);

To send a request to your pod, you'll need to create a Service to talk to it. In this case, a NodePort service gives us the easiest route.

awaitcluster.apply([{apiVersion: "v1",kind: "Service",metadata: {name: "my-service"},spec: {type: "NodePort",ports: [{port: 80,targetPort: 8080,nodePort: 31000,protocol: "TCP",},],selector: {app: "my-pod",},},},]);constresp=awaitcluster.fetch("http://node-1:31000");consttext=awaitresp.text();// hello, world

Pods are also able to talk to each other over HTTP. To see how this works in a few runnable examples, check out the code under the examples/ directory. For the full visual demo, check out the code under the demo/ directory.

What's implemented and what isn't

I've scoped this so far to the bits I need to make the first piece of content I want to make, which is about probing.

I'll also preface this by saying I am by no means an exhaustive expert on every detail of Kubernetes, so it is likely that I'm missing some things or I haven't fully implemented the things I believe I have.

Nodes

  • Cluster spins up a 3-node cluster (node-1, node-2, node-3) and that isn't configurable yet. I would like to suppose arbitrarily adding and removing nodes in the future.

Namespaces

Supported, including special handling for deleting the resources within a namespace via a namespace controller (separate to the garbage collector controller that handles deleting everything else).

Pods

Basics are supported: Pods can have Containers and those containers can listen for HTTP traffic on ports. They get a pod name, an IP address, they can speak to other pods by their DNS name or IP address. They can accept environment variables. They get probed.

What's not supported yet:

  • Init or ephemeral containers.
  • gRPC probing.
  • Volume mounts.
  • Any sort of affinity rules.
  • Resources.
  • Probably a lot of other things, but those are the big ones that come to mine.

Services

Support for ClusterIP and NodePort services is in, LoadBalancer and ExternalName services are not yet supported. Pods can talk to service DNS names and the requests will be load balanced across the Pods in the service using round robin.

UDP isn't supported. TCP kinda sorta isn't either if you think about it, I'm not emulating that far down the network stack. Stuff can talk HTTP and DNS to each other and that's it. I don't anticipate ever wanting or needing to change this. As a result, the distinction between IP families also isn't really modeled.

EndpointSlices

A fun implementation detail of Services I had no idea existed until starting this project. These are created to track sets of Pods that are part of a Service. They're usually sharded into 100 Pods each but I haven't done that, purely for simplicity. They exist, they work how they should, but the sharding isn't there for now.

Events

Supported for the most part, and I've tried to make sure we fire the same events as Kubernetes does. I'm not doing any event aggregating, and it's possible not all fields are present and correct, but events with messages do get fired and can be inspected.

ReplicaSets

Supported and usually created by Deployments. ReplicaSet controller is also in place and largely at parity with the upstream Kubernetes ReplicaSet controller.

Deployments

Supported, including RollingUpdate and Recreate strategies. Deployment controller is in place and largely at parity with the upstream Kubernetes Deployment controller.

Development

This repo uses mise to pin the toolchain (Node, pnpm, ast-grep, ripgrep) so it's reproducible across machines. Node is read from .nvmrc and pnpm is single-sourced from package.json#packageManager.

After installing mise, from a fresh clone:

mise install # install the pinned tools (and write mise.lock)
mise run setup # install workspace dependencies from the lockfile

Available mise tasks:

  • mise run installpnpm install --frozen-lockfile.
  • mise run setup — prepare the repo after a fresh clone (runs install).
  • mise run relock — refresh mise.lock to match .nvmrc and package.json#packageManager.
  • mise run doctor — verify the active tools match the committed pins.

To bump a pinned version, edit .nvmrc, package.json#packageManager, or mise.toml and run mise run relock. The package scripts (pnpm test, pnpm build, pnpm vibe-check, etc.) are unchanged and run as usual once dependencies are installed.

About

Kubernetes in the browser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DecOperations/webernetes: Kubernetes in the browser. · GitHub
Skip to content

Repository files navigation

Webernetes

Kubernetes that runs in your browser.

To see it in action, check out the demo!

Wait, what?

This project is a port of a subset of the Kubernetes project to make it such that clusters can be booted up in the browser, without any backend server components.

But why?

At ngrok, we want to make visual and interactive content about Kubernetes. We didn't want to create and maintain infrastructure for spinning up real clusters, so we decided to create a browser-based simulator instead. The hope and dream is that this will make it possible for us (and you!) to create interactive Kubernetes content that lives for a long time, because the maintenance burden is much smaller.

Please note: This is very experimental. The API is subject to change, the level of support for different resources is subject to change. I'm kinda figuring this out as I go.

How does it work?

First, install webernetes as a dependency:

npm install @ngrok/webernetes

Then define an image to run in your cluster. Webernetes does not run real images from Docker Hub, nor is it a goal to do so.

import{BaseImage,typeProcessContext}from"@ngrok/webernetes";classMyImageextendsBaseImage{// The imageName and imageVersion variables are what make up the image label// you'll use in your container definition. Here we have my-image:1.0 but// webernetes also knows what to do if you specify just my-image or// my-image:lateststaticreadonlyimageName="my-image";staticreadonlyimageVersion="1.0";// If no other command is specified in your container manifest, this is the// command that will be passed in as argv below.readonlydefaultCommand=["server"];// exec is the main entrypoint for your image. It will be called with the// command-line arguments passed in from your container definition.overrideasyncexec(ctx: ProcessContext,argv: readonlystring[]): Promise<number>{if(argv[0]!=="server"){// The base image defines a bunch of core utils (cat, false, printenv,// etc.) so if we don't recognize the command, fall back to the base// image.returnawaitsuper.exec(ctx,argv);}// Binds to port 8080 on this container.ctx.listenHttp(8080,async(request)=>{return{statusCode: 200,body: "hello, world\n",};});// Required for long-running processes to be cancellable when clusters shut// down. If we returned an exit code of 0 here, the listener above would be// unregistered because this container will have exited.returnawaitctx.waitUntilKilled();}}

Then we create a cluster and register our image with it.

import{Cluster}from"@ngrok/webernetes";constcluster=newCluster();cluster.registerImage(MyImage);

And then we can run the cluster and spawn a pod using our image in it.

// By default this spins up a 3-node cluster. This can't currently be changed.awaitcluster.init();awaitcluster.apply([{apiVersion: "v1",kind: "Pod",metadata: {name: "my-pod",labels: {app: "my-pod"},},spec: {containers: [{name: "my-container",image: "my-image:1.0",},],},},]);

To send a request to your pod, you'll need to create a Service to talk to it. In this case, a NodePort service gives us the easiest route.

awaitcluster.apply([{apiVersion: "v1",kind: "Service",metadata: {name: "my-service"},spec: {type: "NodePort",ports: [{port: 80,targetPort: 8080,nodePort: 31000,protocol: "TCP",},],selector: {app: "my-pod",},},},]);constresp=awaitcluster.fetch("http://node-1:31000");consttext=awaitresp.text();// hello, world

Pods are also able to talk to each other over HTTP. To see how this works in a few runnable examples, check out the code under the examples/ directory. For the full visual demo, check out the code under the demo/ directory.

What's implemented and what isn't

I've scoped this so far to the bits I need to make the first piece of content I want to make, which is about probing.

I'll also preface this by saying I am by no means an exhaustive expert on every detail of Kubernetes, so it is likely that I'm missing some things or I haven't fully implemented the things I believe I have.

Nodes

  • Cluster spins up a 3-node cluster (node-1, node-2, node-3) and that isn't configurable yet. I would like to suppose arbitrarily adding and removing nodes in the future.

Namespaces

Supported, including special handling for deleting the resources within a namespace via a namespace controller (separate to the garbage collector controller that handles deleting everything else).

Pods

Basics are supported: Pods can have Containers and those containers can listen for HTTP traffic on ports. They get a pod name, an IP address, they can speak to other pods by their DNS name or IP address. They can accept environment variables. They get probed.

What's not supported yet:

  • Init or ephemeral containers.
  • gRPC probing.
  • Volume mounts.
  • Any sort of affinity rules.
  • Resources.
  • Probably a lot of other things, but those are the big ones that come to mine.

Services

Support for ClusterIP and NodePort services is in, LoadBalancer and ExternalName services are not yet supported. Pods can talk to service DNS names and the requests will be load balanced across the Pods in the service using round robin.

UDP isn't supported. TCP kinda sorta isn't either if you think about it, I'm not emulating that far down the network stack. Stuff can talk HTTP and DNS to each other and that's it. I don't anticipate ever wanting or needing to change this. As a result, the distinction between IP families also isn't really modeled.

EndpointSlices

A fun implementation detail of Services I had no idea existed until starting this project. These are created to track sets of Pods that are part of a Service. They're usually sharded into 100 Pods each but I haven't done that, purely for simplicity. They exist, they work how they should, but the sharding isn't there for now.

Events

Supported for the most part, and I've tried to make sure we fire the same events as Kubernetes does. I'm not doing any event aggregating, and it's possible not all fields are present and correct, but events with messages do get fired and can be inspected.

ReplicaSets

Supported and usually created by Deployments. ReplicaSet controller is also in place and largely at parity with the upstream Kubernetes ReplicaSet controller.

Deployments

Supported, including RollingUpdate and Recreate strategies. Deployment controller is in place and largely at parity with the upstream Kubernetes Deployment controller.

Development

This repo uses mise to pin the toolchain (Node, pnpm, ast-grep, ripgrep) so it's reproducible across machines. Node is read from .nvmrc and pnpm is single-sourced from package.json#packageManager.

After installing mise, from a fresh clone:

mise install # install the pinned tools (and write mise.lock)
mise run setup # install workspace dependencies from the lockfile

Available mise tasks:

  • mise run installpnpm install --frozen-lockfile.
  • mise run setup — prepare the repo after a fresh clone (runs install).
  • mise run relock — refresh mise.lock to match .nvmrc and package.json#packageManager.
  • mise run doctor — verify the active tools match the committed pins.

To bump a pinned version, edit .nvmrc, package.json#packageManager, or mise.toml and run mise run relock. The package scripts (pnpm test, pnpm build, pnpm vibe-check, etc.) are unchanged and run as usual once dependencies are installed.

About

Kubernetes in the browser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DecOperations/webernetes: Kubernetes in the browser. · GitHub
Skip to content

Repository files navigation

Webernetes

Kubernetes that runs in your browser.

To see it in action, check out the demo!

Wait, what?

This project is a port of a subset of the Kubernetes project to make it such that clusters can be booted up in the browser, without any backend server components.

But why?

At ngrok, we want to make visual and interactive content about Kubernetes. We didn't want to create and maintain infrastructure for spinning up real clusters, so we decided to create a browser-based simulator instead. The hope and dream is that this will make it possible for us (and you!) to create interactive Kubernetes content that lives for a long time, because the maintenance burden is much smaller.

Please note: This is very experimental. The API is subject to change, the level of support for different resources is subject to change. I'm kinda figuring this out as I go.

How does it work?

First, install webernetes as a dependency:

npm install @ngrok/webernetes

Then define an image to run in your cluster. Webernetes does not run real images from Docker Hub, nor is it a goal to do so.

import{BaseImage,typeProcessContext}from"@ngrok/webernetes";classMyImageextendsBaseImage{// The imageName and imageVersion variables are what make up the image label// you'll use in your container definition. Here we have my-image:1.0 but// webernetes also knows what to do if you specify just my-image or// my-image:lateststaticreadonlyimageName="my-image";staticreadonlyimageVersion="1.0";// If no other command is specified in your container manifest, this is the// command that will be passed in as argv below.readonlydefaultCommand=["server"];// exec is the main entrypoint for your image. It will be called with the// command-line arguments passed in from your container definition.overrideasyncexec(ctx: ProcessContext,argv: readonlystring[]): Promise<number>{if(argv[0]!=="server"){// The base image defines a bunch of core utils (cat, false, printenv,// etc.) so if we don't recognize the command, fall back to the base// image.returnawaitsuper.exec(ctx,argv);}// Binds to port 8080 on this container.ctx.listenHttp(8080,async(request)=>{return{statusCode: 200,body: "hello, world\n",};});// Required for long-running processes to be cancellable when clusters shut// down. If we returned an exit code of 0 here, the listener above would be// unregistered because this container will have exited.returnawaitctx.waitUntilKilled();}}

Then we create a cluster and register our image with it.

import{Cluster}from"@ngrok/webernetes";constcluster=newCluster();cluster.registerImage(MyImage);

And then we can run the cluster and spawn a pod using our image in it.

// By default this spins up a 3-node cluster. This can't currently be changed.awaitcluster.init();awaitcluster.apply([{apiVersion: "v1",kind: "Pod",metadata: {name: "my-pod",labels: {app: "my-pod"},},spec: {containers: [{name: "my-container",image: "my-image:1.0",},],},},]);

To send a request to your pod, you'll need to create a Service to talk to it. In this case, a NodePort service gives us the easiest route.

awaitcluster.apply([{apiVersion: "v1",kind: "Service",metadata: {name: "my-service"},spec: {type: "NodePort",ports: [{port: 80,targetPort: 8080,nodePort: 31000,protocol: "TCP",},],selector: {app: "my-pod",},},},]);constresp=awaitcluster.fetch("http://node-1:31000");consttext=awaitresp.text();// hello, world

Pods are also able to talk to each other over HTTP. To see how this works in a few runnable examples, check out the code under the examples/ directory. For the full visual demo, check out the code under the demo/ directory.

What's implemented and what isn't

I've scoped this so far to the bits I need to make the first piece of content I want to make, which is about probing.

I'll also preface this by saying I am by no means an exhaustive expert on every detail of Kubernetes, so it is likely that I'm missing some things or I haven't fully implemented the things I believe I have.

Nodes

  • Cluster spins up a 3-node cluster (node-1, node-2, node-3) and that isn't configurable yet. I would like to suppose arbitrarily adding and removing nodes in the future.

Namespaces

Supported, including special handling for deleting the resources within a namespace via a namespace controller (separate to the garbage collector controller that handles deleting everything else).

Pods

Basics are supported: Pods can have Containers and those containers can listen for HTTP traffic on ports. They get a pod name, an IP address, they can speak to other pods by their DNS name or IP address. They can accept environment variables. They get probed.

What's not supported yet:

  • Init or ephemeral containers.
  • gRPC probing.
  • Volume mounts.
  • Any sort of affinity rules.
  • Resources.
  • Probably a lot of other things, but those are the big ones that come to mine.

Services

Support for ClusterIP and NodePort services is in, LoadBalancer and ExternalName services are not yet supported. Pods can talk to service DNS names and the requests will be load balanced across the Pods in the service using round robin.

UDP isn't supported. TCP kinda sorta isn't either if you think about it, I'm not emulating that far down the network stack. Stuff can talk HTTP and DNS to each other and that's it. I don't anticipate ever wanting or needing to change this. As a result, the distinction between IP families also isn't really modeled.

EndpointSlices

A fun implementation detail of Services I had no idea existed until starting this project. These are created to track sets of Pods that are part of a Service. They're usually sharded into 100 Pods each but I haven't done that, purely for simplicity. They exist, they work how they should, but the sharding isn't there for now.

Events

Supported for the most part, and I've tried to make sure we fire the same events as Kubernetes does. I'm not doing any event aggregating, and it's possible not all fields are present and correct, but events with messages do get fired and can be inspected.

ReplicaSets

Supported and usually created by Deployments. ReplicaSet controller is also in place and largely at parity with the upstream Kubernetes ReplicaSet controller.

Deployments

Supported, including RollingUpdate and Recreate strategies. Deployment controller is in place and largely at parity with the upstream Kubernetes Deployment controller.

Development

This repo uses mise to pin the toolchain (Node, pnpm, ast-grep, ripgrep) so it's reproducible across machines. Node is read from .nvmrc and pnpm is single-sourced from package.json#packageManager.

After installing mise, from a fresh clone:

mise install # install the pinned tools (and write mise.lock)
mise run setup # install workspace dependencies from the lockfile

Available mise tasks:

  • mise run installpnpm install --frozen-lockfile.
  • mise run setup — prepare the repo after a fresh clone (runs install).
  • mise run relock — refresh mise.lock to match .nvmrc and package.json#packageManager.
  • mise run doctor — verify the active tools match the committed pins.

To bump a pinned version, edit .nvmrc, package.json#packageManager, or mise.toml and run mise run relock. The package scripts (pnpm test, pnpm build, pnpm vibe-check, etc.) are unchanged and run as usual once dependencies are installed.

About

Kubernetes in the browser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - DecOperations/webernetes: Kubernetes in the browser. · GitHub
Skip to content

Repository files navigation

Webernetes

Kubernetes that runs in your browser.

To see it in action, check out the demo!

Wait, what?

This project is a port of a subset of the Kubernetes project to make it such that clusters can be booted up in the browser, without any backend server components.

But why?

At ngrok, we want to make visual and interactive content about Kubernetes. We didn't want to create and maintain infrastructure for spinning up real clusters, so we decided to create a browser-based simulator instead. The hope and dream is that this will make it possible for us (and you!) to create interactive Kubernetes content that lives for a long time, because the maintenance burden is much smaller.

Please note: This is very experimental. The API is subject to change, the level of support for different resources is subject to change. I'm kinda figuring this out as I go.

How does it work?

First, install webernetes as a dependency:

npm install @ngrok/webernetes

Then define an image to run in your cluster. Webernetes does not run real images from Docker Hub, nor is it a goal to do so.

import{BaseImage,typeProcessContext}from"@ngrok/webernetes";classMyImageextendsBaseImage{// The imageName and imageVersion variables are what make up the image label// you'll use in your container definition. Here we have my-image:1.0 but// webernetes also knows what to do if you specify just my-image or// my-image:lateststaticreadonlyimageName="my-image";staticreadonlyimageVersion="1.0";// If no other command is specified in your container manifest, this is the// command that will be passed in as argv below.readonlydefaultCommand=["server"];// exec is the main entrypoint for your image. It will be called with the// command-line arguments passed in from your container definition.overrideasyncexec(ctx: ProcessContext,argv: readonlystring[]): Promise<number>{if(argv[0]!=="server"){// The base image defines a bunch of core utils (cat, false, printenv,// etc.) so if we don't recognize the command, fall back to the base// image.returnawaitsuper.exec(ctx,argv);}// Binds to port 8080 on this container.ctx.listenHttp(8080,async(request)=>{return{statusCode: 200,body: "hello, world\n",};});// Required for long-running processes to be cancellable when clusters shut// down. If we returned an exit code of 0 here, the listener above would be// unregistered because this container will have exited.returnawaitctx.waitUntilKilled();}}

Then we create a cluster and register our image with it.

import{Cluster}from"@ngrok/webernetes";constcluster=newCluster();cluster.registerImage(MyImage);

And then we can run the cluster and spawn a pod using our image in it.

// By default this spins up a 3-node cluster. This can't currently be changed.awaitcluster.init();awaitcluster.apply([{apiVersion: "v1",kind: "Pod",metadata: {name: "my-pod",labels: {app: "my-pod"},},spec: {containers: [{name: "my-container",image: "my-image:1.0",},],},},]);

To send a request to your pod, you'll need to create a Service to talk to it. In this case, a NodePort service gives us the easiest route.

awaitcluster.apply([{apiVersion: "v1",kind: "Service",metadata: {name: "my-service"},spec: {type: "NodePort",ports: [{port: 80,targetPort: 8080,nodePort: 31000,protocol: "TCP",},],selector: {app: "my-pod",},},},]);constresp=awaitcluster.fetch("http://node-1:31000");consttext=awaitresp.text();// hello, world

Pods are also able to talk to each other over HTTP. To see how this works in a few runnable examples, check out the code under the examples/ directory. For the full visual demo, check out the code under the demo/ directory.

What's implemented and what isn't

I've scoped this so far to the bits I need to make the first piece of content I want to make, which is about probing.

I'll also preface this by saying I am by no means an exhaustive expert on every detail of Kubernetes, so it is likely that I'm missing some things or I haven't fully implemented the things I believe I have.

Nodes

  • Cluster spins up a 3-node cluster (node-1, node-2, node-3) and that isn't configurable yet. I would like to suppose arbitrarily adding and removing nodes in the future.

Namespaces

Supported, including special handling for deleting the resources within a namespace via a namespace controller (separate to the garbage collector controller that handles deleting everything else).

Pods

Basics are supported: Pods can have Containers and those containers can listen for HTTP traffic on ports. They get a pod name, an IP address, they can speak to other pods by their DNS name or IP address. They can accept environment variables. They get probed.

What's not supported yet:

  • Init or ephemeral containers.
  • gRPC probing.
  • Volume mounts.
  • Any sort of affinity rules.
  • Resources.
  • Probably a lot of other things, but those are the big ones that come to mine.

Services

Support for ClusterIP and NodePort services is in, LoadBalancer and ExternalName services are not yet supported. Pods can talk to service DNS names and the requests will be load balanced across the Pods in the service using round robin.

UDP isn't supported. TCP kinda sorta isn't either if you think about it, I'm not emulating that far down the network stack. Stuff can talk HTTP and DNS to each other and that's it. I don't anticipate ever wanting or needing to change this. As a result, the distinction between IP families also isn't really modeled.

EndpointSlices

A fun implementation detail of Services I had no idea existed until starting this project. These are created to track sets of Pods that are part of a Service. They're usually sharded into 100 Pods each but I haven't done that, purely for simplicity. They exist, they work how they should, but the sharding isn't there for now.

Events

Supported for the most part, and I've tried to make sure we fire the same events as Kubernetes does. I'm not doing any event aggregating, and it's possible not all fields are present and correct, but events with messages do get fired and can be inspected.

ReplicaSets

Supported and usually created by Deployments. ReplicaSet controller is also in place and largely at parity with the upstream Kubernetes ReplicaSet controller.

Deployments

Supported, including RollingUpdate and Recreate strategies. Deployment controller is in place and largely at parity with the upstream Kubernetes Deployment controller.

Development

This repo uses mise to pin the toolchain (Node, pnpm, ast-grep, ripgrep) so it's reproducible across machines. Node is read from .nvmrc and pnpm is single-sourced from package.json#packageManager.

After installing mise, from a fresh clone:

mise install # install the pinned tools (and write mise.lock)
mise run setup # install workspace dependencies from the lockfile

Available mise tasks:

  • mise run installpnpm install --frozen-lockfile.
  • mise run setup — prepare the repo after a fresh clone (runs install).
  • mise run relock — refresh mise.lock to match .nvmrc and package.json#packageManager.
  • mise run doctor — verify the active tools match the committed pins.

To bump a pinned version, edit .nvmrc, package.json#packageManager, or mise.toml and run mise run relock. The package scripts (pnpm test, pnpm build, pnpm vibe-check, etc.) are unchanged and run as usual once dependencies are installed.

About

Kubernetes in the browser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DecOperations/webernetes: Kubernetes in the browser. · GitHub
Skip to content

Repository files navigation

Webernetes

Kubernetes that runs in your browser.

To see it in action, check out the demo!

Wait, what?

This project is a port of a subset of the Kubernetes project to make it such that clusters can be booted up in the browser, without any backend server components.

But why?

At ngrok, we want to make visual and interactive content about Kubernetes. We didn't want to create and maintain infrastructure for spinning up real clusters, so we decided to create a browser-based simulator instead. The hope and dream is that this will make it possible for us (and you!) to create interactive Kubernetes content that lives for a long time, because the maintenance burden is much smaller.

Please note: This is very experimental. The API is subject to change, the level of support for different resources is subject to change. I'm kinda figuring this out as I go.

How does it work?

First, install webernetes as a dependency:

npm install @ngrok/webernetes

Then define an image to run in your cluster. Webernetes does not run real images from Docker Hub, nor is it a goal to do so.

import{BaseImage,typeProcessContext}from"@ngrok/webernetes";classMyImageextendsBaseImage{// The imageName and imageVersion variables are what make up the image label// you'll use in your container definition. Here we have my-image:1.0 but// webernetes also knows what to do if you specify just my-image or// my-image:lateststaticreadonlyimageName="my-image";staticreadonlyimageVersion="1.0";// If no other command is specified in your container manifest, this is the// command that will be passed in as argv below.readonlydefaultCommand=["server"];// exec is the main entrypoint for your image. It will be called with the// command-line arguments passed in from your container definition.overrideasyncexec(ctx: ProcessContext,argv: readonlystring[]): Promise<number>{if(argv[0]!=="server"){// The base image defines a bunch of core utils (cat, false, printenv,// etc.) so if we don't recognize the command, fall back to the base// image.returnawaitsuper.exec(ctx,argv);}// Binds to port 8080 on this container.ctx.listenHttp(8080,async(request)=>{return{statusCode: 200,body: "hello, world\n",};});// Required for long-running processes to be cancellable when clusters shut// down. If we returned an exit code of 0 here, the listener above would be// unregistered because this container will have exited.returnawaitctx.waitUntilKilled();}}

Then we create a cluster and register our image with it.

import{Cluster}from"@ngrok/webernetes";constcluster=newCluster();cluster.registerImage(MyImage);

And then we can run the cluster and spawn a pod using our image in it.

// By default this spins up a 3-node cluster. This can't currently be changed.awaitcluster.init();awaitcluster.apply([{apiVersion: "v1",kind: "Pod",metadata: {name: "my-pod",labels: {app: "my-pod"},},spec: {containers: [{name: "my-container",image: "my-image:1.0",},],},},]);

To send a request to your pod, you'll need to create a Service to talk to it. In this case, a NodePort service gives us the easiest route.

awaitcluster.apply([{apiVersion: "v1",kind: "Service",metadata: {name: "my-service"},spec: {type: "NodePort",ports: [{port: 80,targetPort: 8080,nodePort: 31000,protocol: "TCP",},],selector: {app: "my-pod",},},},]);constresp=awaitcluster.fetch("http://node-1:31000");consttext=awaitresp.text();// hello, world

Pods are also able to talk to each other over HTTP. To see how this works in a few runnable examples, check out the code under the examples/ directory. For the full visual demo, check out the code under the demo/ directory.

What's implemented and what isn't

I've scoped this so far to the bits I need to make the first piece of content I want to make, which is about probing.

I'll also preface this by saying I am by no means an exhaustive expert on every detail of Kubernetes, so it is likely that I'm missing some things or I haven't fully implemented the things I believe I have.

Nodes

  • Cluster spins up a 3-node cluster (node-1, node-2, node-3) and that isn't configurable yet. I would like to suppose arbitrarily adding and removing nodes in the future.

Namespaces

Supported, including special handling for deleting the resources within a namespace via a namespace controller (separate to the garbage collector controller that handles deleting everything else).

Pods

Basics are supported: Pods can have Containers and those containers can listen for HTTP traffic on ports. They get a pod name, an IP address, they can speak to other pods by their DNS name or IP address. They can accept environment variables. They get probed.

What's not supported yet:

  • Init or ephemeral containers.
  • gRPC probing.
  • Volume mounts.
  • Any sort of affinity rules.
  • Resources.
  • Probably a lot of other things, but those are the big ones that come to mine.

Services

Support for ClusterIP and NodePort services is in, LoadBalancer and ExternalName services are not yet supported. Pods can talk to service DNS names and the requests will be load balanced across the Pods in the service using round robin.

UDP isn't supported. TCP kinda sorta isn't either if you think about it, I'm not emulating that far down the network stack. Stuff can talk HTTP and DNS to each other and that's it. I don't anticipate ever wanting or needing to change this. As a result, the distinction between IP families also isn't really modeled.

EndpointSlices

A fun implementation detail of Services I had no idea existed until starting this project. These are created to track sets of Pods that are part of a Service. They're usually sharded into 100 Pods each but I haven't done that, purely for simplicity. They exist, they work how they should, but the sharding isn't there for now.

Events

Supported for the most part, and I've tried to make sure we fire the same events as Kubernetes does. I'm not doing any event aggregating, and it's possible not all fields are present and correct, but events with messages do get fired and can be inspected.

ReplicaSets

Supported and usually created by Deployments. ReplicaSet controller is also in place and largely at parity with the upstream Kubernetes ReplicaSet controller.

Deployments

Supported, including RollingUpdate and Recreate strategies. Deployment controller is in place and largely at parity with the upstream Kubernetes Deployment controller.

Development

This repo uses mise to pin the toolchain (Node, pnpm, ast-grep, ripgrep) so it's reproducible across machines. Node is read from .nvmrc and pnpm is single-sourced from package.json#packageManager.

After installing mise, from a fresh clone:

mise install # install the pinned tools (and write mise.lock)
mise run setup # install workspace dependencies from the lockfile

Available mise tasks:

  • mise run installpnpm install --frozen-lockfile.
  • mise run setup — prepare the repo after a fresh clone (runs install).
  • mise run relock — refresh mise.lock to match .nvmrc and package.json#packageManager.
  • mise run doctor — verify the active tools match the committed pins.

To bump a pinned version, edit .nvmrc, package.json#packageManager, or mise.toml and run mise run relock. The package scripts (pnpm test, pnpm build, pnpm vibe-check, etc.) are unchanged and run as usual once dependencies are installed.

About

Kubernetes in the browser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DecOperations/webernetes: Kubernetes in the browser. · GitHub
Skip to content

Repository files navigation

Webernetes

Kubernetes that runs in your browser.

To see it in action, check out the demo!

Wait, what?

This project is a port of a subset of the Kubernetes project to make it such that clusters can be booted up in the browser, without any backend server components.

But why?

At ngrok, we want to make visual and interactive content about Kubernetes. We didn't want to create and maintain infrastructure for spinning up real clusters, so we decided to create a browser-based simulator instead. The hope and dream is that this will make it possible for us (and you!) to create interactive Kubernetes content that lives for a long time, because the maintenance burden is much smaller.

Please note: This is very experimental. The API is subject to change, the level of support for different resources is subject to change. I'm kinda figuring this out as I go.

How does it work?

First, install webernetes as a dependency:

npm install @ngrok/webernetes

Then define an image to run in your cluster. Webernetes does not run real images from Docker Hub, nor is it a goal to do so.

import{BaseImage,typeProcessContext}from"@ngrok/webernetes";classMyImageextendsBaseImage{// The imageName and imageVersion variables are what make up the image label// you'll use in your container definition. Here we have my-image:1.0 but// webernetes also knows what to do if you specify just my-image or// my-image:lateststaticreadonlyimageName="my-image";staticreadonlyimageVersion="1.0";// If no other command is specified in your container manifest, this is the// command that will be passed in as argv below.readonlydefaultCommand=["server"];// exec is the main entrypoint for your image. It will be called with the// command-line arguments passed in from your container definition.overrideasyncexec(ctx: ProcessContext,argv: readonlystring[]): Promise<number>{if(argv[0]!=="server"){// The base image defines a bunch of core utils (cat, false, printenv,// etc.) so if we don't recognize the command, fall back to the base// image.returnawaitsuper.exec(ctx,argv);}// Binds to port 8080 on this container.ctx.listenHttp(8080,async(request)=>{return{statusCode: 200,body: "hello, world\n",};});// Required for long-running processes to be cancellable when clusters shut// down. If we returned an exit code of 0 here, the listener above would be// unregistered because this container will have exited.returnawaitctx.waitUntilKilled();}}

Then we create a cluster and register our image with it.

import{Cluster}from"@ngrok/webernetes";constcluster=newCluster();cluster.registerImage(MyImage);

And then we can run the cluster and spawn a pod using our image in it.

// By default this spins up a 3-node cluster. This can't currently be changed.awaitcluster.init();awaitcluster.apply([{apiVersion: "v1",kind: "Pod",metadata: {name: "my-pod",labels: {app: "my-pod"},},spec: {containers: [{name: "my-container",image: "my-image:1.0",},],},},]);

To send a request to your pod, you'll need to create a Service to talk to it. In this case, a NodePort service gives us the easiest route.

awaitcluster.apply([{apiVersion: "v1",kind: "Service",metadata: {name: "my-service"},spec: {type: "NodePort",ports: [{port: 80,targetPort: 8080,nodePort: 31000,protocol: "TCP",},],selector: {app: "my-pod",},},},]);constresp=awaitcluster.fetch("http://node-1:31000");consttext=awaitresp.text();// hello, world

Pods are also able to talk to each other over HTTP. To see how this works in a few runnable examples, check out the code under the examples/ directory. For the full visual demo, check out the code under the demo/ directory.

What's implemented and what isn't

I've scoped this so far to the bits I need to make the first piece of content I want to make, which is about probing.

I'll also preface this by saying I am by no means an exhaustive expert on every detail of Kubernetes, so it is likely that I'm missing some things or I haven't fully implemented the things I believe I have.

Nodes

  • Cluster spins up a 3-node cluster (node-1, node-2, node-3) and that isn't configurable yet. I would like to suppose arbitrarily adding and removing nodes in the future.

Namespaces

Supported, including special handling for deleting the resources within a namespace via a namespace controller (separate to the garbage collector controller that handles deleting everything else).

Pods

Basics are supported: Pods can have Containers and those containers can listen for HTTP traffic on ports. They get a pod name, an IP address, they can speak to other pods by their DNS name or IP address. They can accept environment variables. They get probed.

What's not supported yet:

  • Init or ephemeral containers.
  • gRPC probing.
  • Volume mounts.
  • Any sort of affinity rules.
  • Resources.
  • Probably a lot of other things, but those are the big ones that come to mine.

Services

Support for ClusterIP and NodePort services is in, LoadBalancer and ExternalName services are not yet supported. Pods can talk to service DNS names and the requests will be load balanced across the Pods in the service using round robin.

UDP isn't supported. TCP kinda sorta isn't either if you think about it, I'm not emulating that far down the network stack. Stuff can talk HTTP and DNS to each other and that's it. I don't anticipate ever wanting or needing to change this. As a result, the distinction between IP families also isn't really modeled.

EndpointSlices

A fun implementation detail of Services I had no idea existed until starting this project. These are created to track sets of Pods that are part of a Service. They're usually sharded into 100 Pods each but I haven't done that, purely for simplicity. They exist, they work how they should, but the sharding isn't there for now.

Events

Supported for the most part, and I've tried to make sure we fire the same events as Kubernetes does. I'm not doing any event aggregating, and it's possible not all fields are present and correct, but events with messages do get fired and can be inspected.

ReplicaSets

Supported and usually created by Deployments. ReplicaSet controller is also in place and largely at parity with the upstream Kubernetes ReplicaSet controller.

Deployments

Supported, including RollingUpdate and Recreate strategies. Deployment controller is in place and largely at parity with the upstream Kubernetes Deployment controller.

Development

This repo uses mise to pin the toolchain (Node, pnpm, ast-grep, ripgrep) so it's reproducible across machines. Node is read from .nvmrc and pnpm is single-sourced from package.json#packageManager.

After installing mise, from a fresh clone:

mise install # install the pinned tools (and write mise.lock)
mise run setup # install workspace dependencies from the lockfile

Available mise tasks:

  • mise run installpnpm install --frozen-lockfile.
  • mise run setup — prepare the repo after a fresh clone (runs install).
  • mise run relock — refresh mise.lock to match .nvmrc and package.json#packageManager.
  • mise run doctor — verify the active tools match the committed pins.

To bump a pinned version, edit .nvmrc, package.json#packageManager, or mise.toml and run mise run relock. The package scripts (pnpm test, pnpm build, pnpm vibe-check, etc.) are unchanged and run as usual once dependencies are installed.

About

Kubernetes in the browser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - DecOperations/webernetes: Kubernetes in the browser. · GitHub
Skip to content

Repository files navigation

Webernetes

Kubernetes that runs in your browser.

To see it in action, check out the demo!

Wait, what?

This project is a port of a subset of the Kubernetes project to make it such that clusters can be booted up in the browser, without any backend server components.

But why?

At ngrok, we want to make visual and interactive content about Kubernetes. We didn't want to create and maintain infrastructure for spinning up real clusters, so we decided to create a browser-based simulator instead. The hope and dream is that this will make it possible for us (and you!) to create interactive Kubernetes content that lives for a long time, because the maintenance burden is much smaller.

Please note: This is very experimental. The API is subject to change, the level of support for different resources is subject to change. I'm kinda figuring this out as I go.

How does it work?

First, install webernetes as a dependency:

npm install @ngrok/webernetes

Then define an image to run in your cluster. Webernetes does not run real images from Docker Hub, nor is it a goal to do so.

import{BaseImage,typeProcessContext}from"@ngrok/webernetes";classMyImageextendsBaseImage{// The imageName and imageVersion variables are what make up the image label// you'll use in your container definition. Here we have my-image:1.0 but// webernetes also knows what to do if you specify just my-image or// my-image:lateststaticreadonlyimageName="my-image";staticreadonlyimageVersion="1.0";// If no other command is specified in your container manifest, this is the// command that will be passed in as argv below.readonlydefaultCommand=["server"];// exec is the main entrypoint for your image. It will be called with the// command-line arguments passed in from your container definition.overrideasyncexec(ctx: ProcessContext,argv: readonlystring[]): Promise<number>{if(argv[0]!=="server"){// The base image defines a bunch of core utils (cat, false, printenv,// etc.) so if we don't recognize the command, fall back to the base// image.returnawaitsuper.exec(ctx,argv);}// Binds to port 8080 on this container.ctx.listenHttp(8080,async(request)=>{return{statusCode: 200,body: "hello, world\n",};});// Required for long-running processes to be cancellable when clusters shut// down. If we returned an exit code of 0 here, the listener above would be// unregistered because this container will have exited.returnawaitctx.waitUntilKilled();}}

Then we create a cluster and register our image with it.

import{Cluster}from"@ngrok/webernetes";constcluster=newCluster();cluster.registerImage(MyImage);

And then we can run the cluster and spawn a pod using our image in it.

// By default this spins up a 3-node cluster. This can't currently be changed.awaitcluster.init();awaitcluster.apply([{apiVersion: "v1",kind: "Pod",metadata: {name: "my-pod",labels: {app: "my-pod"},},spec: {containers: [{name: "my-container",image: "my-image:1.0",},],},},]);

To send a request to your pod, you'll need to create a Service to talk to it. In this case, a NodePort service gives us the easiest route.

awaitcluster.apply([{apiVersion: "v1",kind: "Service",metadata: {name: "my-service"},spec: {type: "NodePort",ports: [{port: 80,targetPort: 8080,nodePort: 31000,protocol: "TCP",},],selector: {app: "my-pod",},},},]);constresp=awaitcluster.fetch("http://node-1:31000");consttext=awaitresp.text();// hello, world

Pods are also able to talk to each other over HTTP. To see how this works in a few runnable examples, check out the code under the examples/ directory. For the full visual demo, check out the code under the demo/ directory.

What's implemented and what isn't

I've scoped this so far to the bits I need to make the first piece of content I want to make, which is about probing.

I'll also preface this by saying I am by no means an exhaustive expert on every detail of Kubernetes, so it is likely that I'm missing some things or I haven't fully implemented the things I believe I have.

Nodes

  • Cluster spins up a 3-node cluster (node-1, node-2, node-3) and that isn't configurable yet. I would like to suppose arbitrarily adding and removing nodes in the future.

Namespaces

Supported, including special handling for deleting the resources within a namespace via a namespace controller (separate to the garbage collector controller that handles deleting everything else).

Pods

Basics are supported: Pods can have Containers and those containers can listen for HTTP traffic on ports. They get a pod name, an IP address, they can speak to other pods by their DNS name or IP address. They can accept environment variables. They get probed.

What's not supported yet:

  • Init or ephemeral containers.
  • gRPC probing.
  • Volume mounts.
  • Any sort of affinity rules.
  • Resources.
  • Probably a lot of other things, but those are the big ones that come to mine.

Services

Support for ClusterIP and NodePort services is in, LoadBalancer and ExternalName services are not yet supported. Pods can talk to service DNS names and the requests will be load balanced across the Pods in the service using round robin.

UDP isn't supported. TCP kinda sorta isn't either if you think about it, I'm not emulating that far down the network stack. Stuff can talk HTTP and DNS to each other and that's it. I don't anticipate ever wanting or needing to change this. As a result, the distinction between IP families also isn't really modeled.

EndpointSlices

A fun implementation detail of Services I had no idea existed until starting this project. These are created to track sets of Pods that are part of a Service. They're usually sharded into 100 Pods each but I haven't done that, purely for simplicity. They exist, they work how they should, but the sharding isn't there for now.

Events

Supported for the most part, and I've tried to make sure we fire the same events as Kubernetes does. I'm not doing any event aggregating, and it's possible not all fields are present and correct, but events with messages do get fired and can be inspected.

ReplicaSets

Supported and usually created by Deployments. ReplicaSet controller is also in place and largely at parity with the upstream Kubernetes ReplicaSet controller.

Deployments

Supported, including RollingUpdate and Recreate strategies. Deployment controller is in place and largely at parity with the upstream Kubernetes Deployment controller.

Development

This repo uses mise to pin the toolchain (Node, pnpm, ast-grep, ripgrep) so it's reproducible across machines. Node is read from .nvmrc and pnpm is single-sourced from package.json#packageManager.

After installing mise, from a fresh clone:

mise install # install the pinned tools (and write mise.lock)
mise run setup # install workspace dependencies from the lockfile

Available mise tasks:

  • mise run installpnpm install --frozen-lockfile.
  • mise run setup — prepare the repo after a fresh clone (runs install).
  • mise run relock — refresh mise.lock to match .nvmrc and package.json#packageManager.
  • mise run doctor — verify the active tools match the committed pins.

To bump a pinned version, edit .nvmrc, package.json#packageManager, or mise.toml and run mise run relock. The package scripts (pnpm test, pnpm build, pnpm vibe-check, etc.) are unchanged and run as usual once dependencies are installed.

About

Kubernetes in the browser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages