Kubernetes를 브라우저로 포팅함
3 hours ago
2
- 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/v1 Deployment manifest를 적용함
- 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이 작성함
- 프로젝트 신뢰성을 위해 두 가지를 병행함
- 모든 코드 줄을 직접 리뷰함
- 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를 포팅할 때 테스트 케이스를 임의로 빼먹는 일이 자주 있었음
- 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였음
- 프로젝트 전체에서 작성자의 시간은 마지막까지도 가장 비싼 항목이었음
마무리 자료와 참여 방법
- 제작 과정을 기록한 영상 시리즈도 있음
- 영상에서는 초기의 잘못된 낙관과, 음성 제어와 눈 추적으로 대부분 hands-free로 작업하는 방식도 볼 수 있음
- 프로젝트를 사용해 보고 issues를 열거나, 무언가 만들었거나 막혔을 때 s.rose@ngrok.com으로 연락해 달라고 요청함
-
Homepage
-
Tech blog
- Kubernetes를 브라우저로 포팅함