Kubernetesをブラウザへ移植
(ngrok.com)- webernetes は Kubernetes の一部を TypeScript に移し、ブラウザ内でクラスターを実行できるようにしたプロジェクトで、2か月のあいだに 552 コミット・629 ファイル・ほぼ 10 万行規模で作られた
- WebAssembly で Kubernetes をそのままコンパイルした方式ではなく、kubelet の一部、複数のコントローラー、ブラウザベースの CNI とコンテナランタイム、クラスター操作 API を新たに実装した
- 実際のイメージレジストリからイメージを取得するのではなく、TypeScript API でイメージを定義し、目標は本番向けディストリビューションではなく インタラクティブな Kubernetes コンテンツ の制作にある
- コードの大半は LLM が書いたが、すべての行を人間がレビューし、k3s と同じテストを実行する 204 件の統合テスト と、Kubernetes の Go コードベースから移植した 1,855 件の単体テストで検証した
- LLM は移植中に省略、勝手なヘルパー生成、テスト漏れを繰り返したため、高速なコード生成の利点を得るには レビューとテスト を併用する必要がある
webernetes がブラウザで実行するもの
- webernetes は Kubernetes を TypeScript に部分移植し、ブラウザでクラスターを実行できるようにしたプロジェクト
- デモクラスターはすべてブラウザ内で動作し、実際の Kubernetes クラスターが行う作業のかなりの部分をこなす
-
Pod ライフサイクル
- クラスター DNS とネットワーキング
- コンテナのガベージコレクション
- IP 割り当て
DeploymentとReplicaSetの追跡- デモの青い点は、Pod どうしがリクエストを送り合う様子を表している
-
WebAssembly コンパイルではなく部分移植を選んだ理由
- Kubernetes を WebAssembly にコンパイルしたものではない
- 単純な Go の
hello, world!プログラムを WebAssembly にコンパイルしても gzip ベースで約 540KiB あり、webernetes は gzip ベースで約 140KiB - Kubernetes 全体を WebAssembly にコンパイルすると、メガバイト単位の転送が必要になる可能性があり、ブラウザで使えないシステムレベル API 呼び出しのためにコンパイル時エラーも発生する
- webernetes は次の要素で構成される
- Pod 実行とプローブに必要な Kubernetes kubelet バイナリの部分移植
- Pod スケジューラー、namespace controller、kube-proxy、deployment controller など複数の Kubernetes コントローラーの移植
- Pod 間通信のためのブラウザベースのコンテナネットワークインターフェース (CNI)
- kubelet がコンテナランタイムインターフェース (CRI) を通じてコンテナを実行できるようにするブラウザベースのコンテナランタイム
- manifest の適用やリソースの watch などの操作のための webernetes クラスター API
イメージ定義と API の使い方
- webernetes はサイズを小さく保つため、Docker Hub のようなレジストリから実際のイメージを取得しない
- その代わり、ブラウザベースのレジストリを持ち、イメージは TypeScript API で定義する
- 例のイメージ
HelloWorldはw8s.BaseImageを継承し、exec内で 8080 ポートへの HTTP リクエストに"Hello, world!"を返す - クラスター利用の流れは次の通り
new w8s.Cluster()でクラスターを作成するcluster.registerImage(HelloWorld)でイメージを登録するcluster.apply()でapps/v1Deploymentmanifest を適用するcluster.api.corev1.listNamespacedPod()で Pod 一覧を取得するcluster.informer("pods", ...)で Pod の変更を監視するcluster.on("request")、cluster.on("response")で Pod 間のリクエストとレスポンスのイベントを観察するcluster.fetch()でクラスターのネットワーク経由で Pod IP に HTTP リクエストを送れる
- さらに多くの例は webernetes repository examples にある
用途と現在の制約
- webernetes の目的は インタラクティブな Kubernetes コンテンツ を作ること
- 本番運用に対応した Kubernetes ディストリビューションではなく、実際のイメージを実行する必要もない
- コンテンツ制作者が説明したい Kubernetes の概念を示すために、特定のワークロードを設定できれば十分
- 現時点で未対応の機能には次のものが含まれる
- ConfigMaps
- Secrets
- Pod resources
- persistent volumes
- まだ必要になっていないさまざまな Kubernetes 機能
- 今後はコンテンツ制作の過程で必要になった Kubernetes 機能をさらに実装していく予定
LLM で作ったが、そのまま任せなかった理由
- webernetes のコードのほぼすべては LLM が書いた
- プロジェクトの信頼性を確保するために、次の 2 つを並行して行った
- すべてのコード行を直接レビューした
- webernetes が実際のクラスターと同じ挙動をするか確認するために数百件のテストを作成した
- 手動レビューを通じて、コードの大半が Kubernetes の Go コードベースと行単位で一致しているという確信を得た
- テストは、その語彙的な類似性が実際の挙動の同一性につながっているかを確認する役割を持つ
- レビュー後に残っているミスはプロジェクト作成者の責任であり、問題を見つけたら issue を立ててほしいとしている
移植コードにレビューが必要だった理由
- LLM で C コンパイラを書いたり、Bun を Zig から Rust に移植した事例では、自動的に正しさを検証する方法があった
- Anthropic には比較対象となる既存の C コンパイラがあった
- Bun には、手動レビューなしで 100 万行以上の新しい Rust コードをマージできるほど信頼された大規模テストスイートがあった
- webernetes にはそうした基盤がなかった
- テストスイートが必要なら自分で書かなければならなかった
- 実際の Kubernetes と比較するには、その比較方法も自分で用意する必要があった
- webernetes のコードの大半は Kubernetes の Go コードベースから移植されており、手入力より速いと見込んで LLM を使った
- 移植の過程で LLM は繰り返しミスをした
- 省略: Kubernetes の LRU cache、expiring cache、FIFO cache、transforming cache などを正しく実装せず、
Mapに置き換えて誤動作を招くことがあった - 過度な整理: 元の Go コードにないヘルパー関数を作ってレビューを難しくしたり、微妙な差異を生んだりすることがあった
- 欠落: Go の table test を移植する際に、テストケースを勝手に落とすことが頻繁にあった
- 省略: Kubernetes の LRU cache、expiring cache、FIFO cache、transforming cache などを正しく実装せず、
- LLM の移植結果を信頼するには出力物のレビューが必要であり、プロジェクト作成者は、自分が使える近道を LLM は知らないと考えている
実クラスターと比較したテスト
- コードが原文と並べて似ていても、Go と JavaScript のランタイムは異なるため挙動が変わる可能性がある
- webernetes には JavaScript 版の channels、mutexes、Go の
select文、そのほか Go 的な動作も必要だった - 同じテストコードを webernetes と k3s クラスターの両方で実行し、挙動を比較した
- API 互換対象としては、TypeScript 型を備えた公式 JavaScript Kubernetes クライアント kubernetes-client/javascript を選んだ
- テストハーネスは
kubernetes.describe(..)を通じて実行環境を切り替えるpnpm test:nodeは Node 環境で k3s を対象にテストするpnpm test:browserは headless browser で webernetes を対象にテストする
- 統合テストは移植コードだけでなく、カスタムのブラウザベース container runtime と cluster network が実クラスターに合った形で動作するかも確認する
- バグを見つけたら、まず k3s では通るが webernetes では失敗するテストを作り、そのフィードバックループで LLM の助けを借りながら原因を理解して修正した
- 執筆時点で webernetes には 204 件の統合テスト と 1,855 件の単体テスト がある
レビューとテストを併用すべき理由
- LLM が生成したコードにも、人間が書いた PR と同じように良いテストと良いコードが必要
- 2026 年の違いは、人間の同僚にはある程度良い仕事を期待できても、LLM には良い仕事をしない前提で考えるほうが安全だという点
- テストコードさえレビューしなければ、LLM がどんな成功基準を目標に作業しているのか把握しにくい
- すべてのコードをレビューしても、テストがなければ人間があらゆる可能性を推論できるとは考えにくい
- LLM は疲れず素早く入力できるため、人間が思いつかなかった edge case を提案させ、妥当ならテストを書かせる使い方が有効
- 人間の好みと理解、LLM の高速な記述能力を組み合わせる方法は、2012 年にキャリアを始めて以来最大の変化だと感じたとしている
プロジェクト規模とトークン使用量
- 最初のコミットは 4 月 21 日に現在の webernetes repository に入った
- 初期作業の一部はこのブログサイトの背後にあるリポジトリブランチで進められたため、グラフは全体の実態を完全には表していない
- コード行数グラフは 6 月 15 日の週の時点で 126,642 行 を示している
- 冒頭で述べた約 10 万行という数字は、TypeScript 以外のコード、コメント、demo app を除いたもの
- 週ごとの総行数は次のように増加した
- 4 月 20 日の週: 11,640 行
- 4 月 27 日の週: 20,660 行
- 5 月 4 日の週: 25,048 行
- 5 月 11 日の週: 30,417 行
- 5 月 18 日の週: 42,301 行
- 5 月 25 日の週: 54,155 行
- 6 月 1 日の週: 79,704 行
- 6 月 8 日の週: 98,532 行
- 6 月 15 日の週: 126,642 行
- Codex と Claude のセッションでは cached input token が他のトークンよりはるかに大きく、長いコンテキストウィンドウを頻繁に埋めるほど特に顕著だった
- 6 月 15 日の週には uncached input token 104,155,857 個、cached input token 2,196,467,968 個、output token 6,420,826 個 が使われた
最終週の急増とコスト
- 最終週は demo app に Deployments 対応を入れようとして、想定以上に作業が膨らんだ
- LLM の最初の移植試行では、必要な構成要素の多くの機能が欠けていた
- その後、複数のエージェントを動員して依存チェーンを特定し、各構成要素をさらに多くのサブエージェントで移植し、別のサブエージェント群でレビューも行った
- この方法は手作業より速く完了したが、トークン効率は非常に悪く、最後にはやはり手動レビューが必要だった
- API 換算の LLM トークンコストは週ごとに増加し、6 月 15 日の週には $1,811.64 だった
- プロジェクト全体を通して、作成者の時間は最後まで最も高価なコスト項目だった
1件のコメント
Hacker Newsの意見
例えばこれを作ったのだけれど、https://kubernetes-made-simple.vercel.app/ 今ならここに追加できそう
それでもサイトは素晴らしい
Gatewayをもっと広げて、可能ならCRDにも触れるとよさそう
ほとんどの人がk8sについて誤解していて、学習を不必要に難しくしている点を1つ挙げるなら何だろう?
記憶では最初はKatacodaを使い、その後は似た別のプラットフォームを使ったが、各ユーザーに特定の設定を施した新しいインスタンスをその場で立ち上げてくれて、とても便利だった
ただ、今は概念やアーキテクチャの教育により向いているように見える。本当に面白くなるのはkubectlを自在に扱い始めてから
似た他のプラットフォームも、費用を出す人がいなくなって消えたようで、残念
これが代替になってくれることを願う。現実とずれて古くなるリスクはあるが、それでも核心部分はほとんど常に有効なはず
これはLLM支援エンジニアリングを見る正しい方法のように感じる。AIは驚くほど多くのコードを生成できるが、実際の価値はレビューの規律と、その周辺のテストにある
ブラウザでKubernetesを扱うという切り口も良いが、より興味深いのはワークフロー、特に「それらしく見える」を信じるのではなく、k8sについて挙動をテストする部分だ。AIが書いたコードに対して、すでにどれだけ多くのチームがこの程度の検証をしているのか気になるし、今後数年で皆が向かう方向なのかもしれない
残念ながら、すべてのコーディング作業にそうした機会があるわけではない
https://youtu.be/t7L2iROVaRg?is=xoV4aiCXcYMVvVDL
追加の複雑さや性能低下にはある程度の正当化が必要だろうが、一部のユースケースでは十分に元が取れそう
Fred Brooksの本質的複雑性と偶有的複雑性の区別に似ている
もちろん、もっと単純にできることにkubeを使うと、kubeはすぐに偶有的複雑性になる
例えるなら、誰かがDOOMをブラウザにポーティングしたと言えば、今やブラウザでプレイできるという意味だ。しかし、タブに表示されているデータベースを実際にブラウザで実行できるわけではないのでは?
ruby2dを立ち上げたからといって、突然クライアント側Ruby対応が生まれるとは言えない。ブラウザタブで実際に動くようにするには、あらゆるカスタム作業が必要になる
一方で一般的なバックエンドのコンテナサービスは、本当にあちこちへ移してどこでも実行できる
なので要点がよく分からないし、自分が間違っているなら訂正してほしい。主張していること自体とも合っていないように見える
実際のコンテナイメージを実行しているわけではない。「シミュレートされたKubernetes」と呼ぶ方がよいかもしれない
ポーティングされたのはコントロールプレーン。スケジューラー、kube-proxy、deployment controllerなどが実際のGoソースから移植されており、同じクライアントAPIを使ってk3sとの挙動一致性をテストしている。「レンダリング」は、Pod間のリクエストを動く点として可視化するデモアプリ
楽しかった