你打开浏览器,随手点开一个 GitHub Pages 链接。没有 kubectl,没有 minikube start,没有 Docker Desktop 在后台烧 CPU。标签页加载完,一个 K8s 集群已经在跑了——Pod 在三个节点之间调度,Deployment 控制器盯着 ReplicaSet 的数量,kube-proxy 维护着虚拟网络,蓝色小光点在屏幕上来回穿梭,那是 Pod 之间在互相发 HTTP 请求。
这不是概念 Demo。ngrok 工程师 Sam Rose 花了两个月时间,把 Kubernetes 的控制平面搬进了浏览器。项目叫 webernetes——一个由近 10 万行 TypeScript 组成的部分移植版 Kubernetes,gzip 后不到 140KiB,不需要任何后端服务。
它在你的浏览器里做的事情和真实集群几乎一样:Pod 生命周期管理、集群 DNS 和网络、容器垃圾回收、IP 地址分配、Deployment 和 ReplicaSet 状态追踪。而这一切运行的「容器」压根不是真正的容器——它们是一段段 TypeScript 类,在浏览器沙箱里模拟了 HTTP 服务的行为。
来源:ngrok 博客中 webernetes 的在线演示。蓝色圆点代表正在收发请求的 Pod。
逐行重写,而非编译 WASM
大多数人的第一反应是:「你们是不是把 Kubernetes 编译到了 WebAssembly?」答案是「不」。
一个最简单的 Go「Hello, World」编译为 WASM 后大约是 540KiB(gzip)。这已经比 webernetes 整体还大。真正把整个 Kubernetes 编译为 WASM,下载体积会直奔几十 MB,而且——作者确实试过——编译不过。Kubernetes 的 Go 代码大量调用系统级 API(文件系统、网络命名空间、cgroups),这些在浏览器的 WASM 沙箱中根本不存在。
所以 webernetes 的做法是:从 Kubernetes 的 Go 源码出发,逐行翻译为 TypeScript——用 JavaScript 的语义重新实现各组件的内部逻辑。
具体来说,webernetes 包含以下几个浏览器内实现的组件:
- kubelet 的部分端口——足够运行 Pod 并执行健康探测
- 多个控制器:Pod 调度器、Namespace 控制器、kube-proxy、Deployment 控制器等
- 浏览器版容器网络接口(CNI)——让 Pod 在模拟网络上互相通信
- 浏览器版容器运行时——通过 CRI 接口与 kubelet 通信来「运行」容器
- 集群 API——可以用标准的 Kubernetes API 语义来
apply清单、watch 资源
容器镜像也不走 Docker Hub。webernetes 有自己的浏览器内镜像注册表,镜像直接用 TypeScript 定义:
class HelloWorld extends w8s.BaseImage {
static readonly imageName = "hello-world";
static readonly imageVersion = "1.0";
async exec(ctx: w8s.ProcessContext, argv: string[]): Promise<number> {
ctx.listenHttp(8080, async (_ctx, request) => {
return { status: 200, body: "Hello, world!" };
});
return await ctx.waitUntilKilled();
}
}
然后像标准 K8s 一样部署:
const cluster = new w8s.Cluster();
await cluster.registerImage(HelloWorld);
await cluster.apply([{
apiVersion: "apps/v1",
kind: "Deployment",
metadata: { name: "hello-world-deployment" },
spec: {
replicas: 3,
selector: { matchLabels: { app: "hello-world-pod" } },
template: {
metadata: { labels: { app: "hello-world-pod" } },
spec: { containers: [{ name: "hello", image: "hello-world:1.0" }] },
},
},
}]);
使用相同的 kubernetes-client/javascript API 接口来操作集群——这意味着你可以用同一套测试代码,分别指向真实 k3s 集群和浏览器里的 webernetes。
10 万行代码,552 次提交,几乎全由 LLM 生成
这个项目引人注目的地方在于技术实现和构建方式两个方面。Sam Rose 几乎没有手写几行代码——几乎全部由 LLM 生成。他用了两个月,592 次提交,629 个文件,累计近 10 万行 TypeScript(剔除注释、非代码文件和 Demo 应用后)。
但他坚决否认这是「AI slop」。
他的论证核心就两点:每一行代码都经过了人工审查,以及编写了数百个集成测试,确保 webernetes 的行为与真实集群一致。
为什么必须逐行审查?
LLM 在移植代码时犯的错误有一个固定的模式:
抄近路。 Kubernetes 源码里有多种缓存实现——LRU 缓存、过期缓存、FIFO 缓存、转换缓存。LLM 经常偷偷用一个 Map 替代它们,省了几十行代码但改变了行为语义。
过度热心。 LLM 喜欢发明原始 Go 代码中不存在的辅助函数,声称是为了「代码整洁」。大部分无害,但有时会有细微行为差异。关键是这让逐行对比审查变得困难,Sam 只好要求 LLM 把它们删掉。
无故遗漏。 这是移植 Go 的表驱动测试时最常见的问题。LLM 会任意跳过某些测试用例,问它原因,有时说「不适用」——偶尔确实如此,但更多时候它承认就是漏了。
HN 评论区有人直言「skill issue」——认为是 prompt 不过关。Sam 的态度是:我乐意被打脸,谁给我一段能完美一键移植那个表驱动测试的 prompt,我马上请他喝咖啡。
但在他能信任 LLM 的输出之前,他必须亲眼看过每一行。
双轨测试:同一套代码,跑在两个集群上
审查代码只能保证「看上去一样」。JavaScript 和 Go 的运行环境完全不同,webernetes 还不得不用 JavaScript 自己实现 channels、mutexes、Go 的 select 等多线程原语——这些在非并发场景是否真的行为一致,光看代码看不出来。
所以他设计了一套双轨测试架构:
- 所有集成测试使用
kubernetes-client/javascript的 API pnpm test:node时,测试指向一个真实的 k3s 集群pnpm test:browser时,同样的测试代码指向浏览器里的 webernetes
同一段测试代码:「创建一个 Pod → 确认它存在 → 删除它 → 确认它消失」,分别跑在两个完全不同的后端上。
截止文章发布时,webernetes 有 204 个集成测试和 1,855 个单元测试,其中绝大多数单元测试直接从 Kubernetes Go 代码库移植而来。Sam 的工作流程也很有纪律性:每当发现一个 bug,他先写一个在 k3s 上通过、在 webernetes 上失败的测试,然后用这个反馈循环驱动 LLM 去理解和修复问题。
如果把 token 消耗换算成钱
来源:ngrok 博客。左侧为代码增量(最终约 12.7 万行含注释和 Demo),右侧为 token 消耗。注意两个 Y 轴的量级差异。
在文章最后,Sam 公开了全程的 token 消耗数据。看得让人血压飙升:
最后一星期(6 月 15 日),他一口气烧掉了 1.04 亿 uncached input tokens 和 21.96 亿 cached input tokens,API 等价成本 $1,811。这是什么概念?缓存令牌占了输入量的 95.5%——说明他的 LLM 会话经常用满上下文窗口,每次调用都需要把之前所有代码和对话重新喂进去。
整两个月的 API 等价成本约 $4,334。最后一星期的暴增来自一个意外:他在做 Demo 应用时想加上 Deployment 支持,本以为很快就能搞定,结果 LLM 的第一版缺少大量功能。他启动了一组 agent 团队——一个 agent 识别依赖链,一堆 sub-agent 分别移植每个组件,再一组 sub-agent 做审查。虽然「不可否认比手动快得多」,但 token 效率的低下让他自己都觉得不太对劲。
不过他也承认,即使到最后一周,他的时间仍然是整个项目中最贵的成本项。
社区的碰撞:称赞、嘲讽和那则必出现的 xkcd
HN 上的反应相当热烈。最高赞评论简洁直白:
Investing early in this HN post before it’s a banger. Instant classic.
有人马上捕捉到了更深层的意义——这是 LLM 辅助工程的一个案例研究,测试和审查纪律比代码生成本身更重要:
The browser Kubernetes angle is cool, but what I find more interesting is the workflow, and especially testing behaviour against k3s instead of just trusting “looks right.”
当然,Kubernetes 项目的任何新闻下面都少不了这个经典梗。有人贴出了 xkcd #763(Workflow):
Please port Kubernetes to common house flies so that they drop dead out of all the unnecessary overhead.
也有人抓住核心争议不放——这不是真正的「运行 Kubernetes」,因为没有真正的容器运行时和镜像生态:
What are you using to replace etcd here? Where is state stored?
It’s not really running containers in the browser, right? It seems every service would need a custom connector.
确实,webernetes 目前的状态是:不支持 ConfigMap、Secret、PersistentVolume、Ingress、RBAC……Sam 坦然承认「随着我做更多内容,我再实现我需要的部分」。定位非常明确:教育工具,不是生产级发行版。
另一个被反复提起的 meta-trend:用 LLM 把现有系统移植到新语言的项目正在井喷——Bun 迁移到 Rust、Flow 迁移到 Rust、React Compiler 迁移到 Rust、甚至有人用 Rust 重写了 PostgreSQL 并通过了 100% 的回归测试。
这是教育的未来,不只是炫技
把 Kubernetes 塞进浏览器这件事,最直接的受益者是 Kubernetes 教育。在此之前,任何想学习 K8s 内部机制的人都面临一个巨大的入门障碍:你至少需要启动一个本地集群(minikube、kind、k3s),这本身就要消耗数 GB 内存和相当的环境配置时间。
Katacoda 这样的平台试图解决这个问题,为每个用户按需启动一个真正的集群实例,但背后仍然需要维护服务器集群。webernetes 把这一切压缩到一个 140KiB 的 npm 包里——打开网页就能用。
更深一层看,webernetes 证明了浏览器正在成为一个合法的通用运行时。不光是《半条命2》这样的老游戏可以通过 WASM 在浏览器里复活,就连 Kubernetes 这种重度依赖操作系统原语的分布式系统,也可以通过精心设计的抽象层在浏览器沙箱中运行一个足够逼真的版本。
这里的关键词是「足够逼真」。webernetes 是 Kubernetes 的一个行为精确的模型副本。但这个模型副本的仿真度足够高,以至于你能真正理解 Pod 是如何被调度的、Deployment 控制器在副本数不一致时做了什么、kube-proxy 如何更新 iptables 规则(好吧,浏览器里没有 iptables,这些都是模拟的)。
两个月的疯狂实验留下了什么
Sam Rose 在文章结尾写了一句很有分寸的话:
Combining my unique strengths of taste and understanding with the LLM’s ability to write fast, without fatigue or wrist pain, has been the biggest step change in what feels possible since I started my career in 2012.
webernetes 的存在,本身就回答了一个问题:在 2026 年,一个有经验的工程师加上 LLM,两个月能造出什么?答案是一个浏览器里的 Kubernetes,附带 2,000 多个测试。
但它也制造了一个新的问题:当模型的输出需要你逐行审查才能信任,而审查成本比亲自写还高时,LLM 到底有没有提高你的效率?Sam 的回答切入了一个微妙的视角:关键不在于「LLM 写得更快」,在于「LLM 能写你不愿意写的东西」——那些枯燥、重复、需要照搬上游代码结构的移植工作。人的精力集中在判断哪里对、哪里错,而模型负责生成选项。
这可能才是 LLM 辅助工程的真实面貌:不是魔法,是汗水换了个容器。
本文的素材来自公开信息和社区讨论。如果你对这个话题有更深入的一手经验,欢迎指出文中的不足。
参考链接
- I ported Kubernetes to the browser — ngrok blog — Sam Rose 的原文
- HN 讨论帖 — 社区反响和讨论
- webernetes GitHub 仓库 — 开源代码
- @ngrok/webernetes — npm 包
- kubernetes-client/javascript — 官方 Kubernetes JavaScript 客户端