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이 작성함
- 프로젝트 신뢰성을 위해 두 가지를 병행함
- 모든 코드 줄을 직접 리뷰함
- 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 포팅 결과를 신뢰하려면 출력물을 리뷰해야 하며, 프로젝트 작성자는 자신이 쓸 수 있는 지름길을 알지 못한다고 봄
실제 클러스터와 비교한 테스트
- 코드가 원본과 나란히 비슷해도 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였음
- 프로젝트 전체에서 작성자의 시간은 마지막까지도 가장 비싼 항목이었음
마무리 자료와 참여 방법
댓글과 토론
Hacker News 의견들
- 가르치는 것과 만드는 것을 좋아하는 입장에서, 이런 건 꽤 유용할 수 있음
예를 들어 이걸 만들었는데 https://kubernetes-made-simple.vercel.app/ 이제 여기에 추가할 수 있겠음- iPhone에서 기대 Pod 수를 바꾸면 reconciliation 시뮬레이터가 이상하게 폭주함
그래도 사이트는 멋짐 - 전체를 다 읽었고 내용이 좋음
Gateway를 더 확장하고, 가능하면 CRD도 언급하면 좋겠음 - 멋진 사이트임
대부분의 사람들이 k8s에 대해 잘못 이해해서 학습을 불필요하게 어렵게 만드는 것 하나를 꼽자면 무엇일까?
- iPhone에서 기대 Pod 수를 바꾸면 reconciliation 시뮬레이터가 이상하게 폭주함
- 멋짐. 예전에 Kubernetes 교육 콘텐츠를 만들었던 입장에서, 이런 걸 만들고 싶은 매력이 분명히 보임
기억하기로는 처음엔 Katacoda를 썼고 이후 비슷한 다른 플랫폼을 썼는데, 각 사용자에게 특정 설정이 된 새 인스턴스를 즉석에서 띄워줘서 매우 유용했음
다만 지금은 개념이나 아키텍처 교육에 더 잘 맞아 보임. 진짜 재미는 kubectl을 능숙하게 다루기 시작할 때부터임- 예전 직장에서라면 컨트롤 플레인이 어떻게 동작하는지 설명하는 다이어그램, 성능 저하와 장애 모드, k8s에 배포하는 여러 아키텍처나 방식 비교에 정말 좋았을 것 같음
- 아쉽게도 Katacoda는 유료 장벽 뒤로 들어갔음. 왜 그렇게 했는지는 완전히 이해함. 이런 것들은 비용이 드니까
비슷한 다른 플랫폼들도 비용을 대줄 사람이 없어져 사라진 것 같고, 안타까움
이게 대안이 되길 바람. 현실과 달라져 낡아질 위험은 있지만, 그래도 핵심은 거의 항상 유효할 것임
- 우선 정말 멋짐
이게 LLM 보조 엔지니어링을 바라보는 올바른 방식처럼 느껴짐. AI는 놀랄 만큼 많은 코드를 생성할 수 있지만, 실제 가치는 리뷰 규율과 그 주변의 테스트에 있음
브라우저에서 Kubernetes를 다루는 각도도 멋지지만, 더 흥미로운 건 워크플로, 특히 “그럴듯해 보인다”를 믿는 대신 k8s에 대해 동작을 테스트하는 부분임. 이미 얼마나 많은 팀이 AI가 작성한 코드에 대해 이 정도 검증을 하고 있을지 궁금하고, 앞으로 몇 년간 모두가 가게 될 방향일 수도 있음- 이건 말 그대로 맞춰 구현할 명세가 있는 특수한 경우임
안타깝게도 모든 코딩 작업이 그런 기회를 갖는 건 아님
- 이건 말 그대로 맞춰 구현할 명세가 있는 특수한 경우임
- Nate Abele가 작년 Carolina Code Conference에서 이 주제로 발표했음
https://youtu.be/t7L2iROVaRg?is=xoV4aiCXcYMVvVDL - 진짜로 꽤 멋짐
추가 복잡도나 성능 손실에는 어느 정도 정당화가 필요하겠지만, 일부 사용 사례에서는 충분히 보상받을 수 있어 보임 - kube 복잡도에 대한 농담들이 나오기 전에 말하자면, kube 같은 것은 kube가 의도한 작업들을 수행하기 위해 필요한 수준의 복잡도라는 흥미로운 논리가 가능해 보임
Fred Brooks의 본질적 복잡도와 우발적 복잡도 구분과 비슷함
물론 더 단순하게 할 수 있는 일에 kube를 쓰면, kube는 빠르게 우발적 복잡도가 됨 -
- 실제로 브라우저에서 컨테이너를 실행하는 건 아닌 것 같음. 모든 서비스에 커스텀 커넥터가 필요해 보이고, 더 중요하게는…
- …렌더러도 필요하지 않나? 그렇지 않으면 “브라우저로 포팅됐다”는 게 무슨 뜻인지 모르겠음
비유하자면 누군가 DOOM을 브라우저로 포팅했다면 이제 브라우저에서 플레이할 수 있다는 뜻임. 하지만 탭에 보이는 데이터베이스들을 실제로 브라우저에서 실행할 수 있는 건 아니지 않나?
ruby2d를 띄우면 갑자기 클라이언트 쪽 Ruby 지원이 생긴다고 말할 수는 없음. 브라우저 탭에서 실제로 돌아가게 하려면 온갖 커스텀 작업이 필요함
반면 일반적인 백엔드 컨테이너 서비스는 정말로 여기저기로 옮겨 어디서든 실행할 수 있음
그래서 요점을 잘 모르겠고, 내가 틀렸다면 바로잡아 달라. 주장하는 바 자체와도 맞지 않아 보임
- 그 지점들은 제목에는 없지만 본문에서 다뤄짐
실제 컨테이너 이미지를 실행하는 건 아님. “시뮬레이션된 Kubernetes”라고 부르는 편이 더 나을 수도 있음
포팅된 것은 컨트롤 플레인임. 스케줄러, kube-proxy, deployment controller 등이 실제 Go 소스에서 옮겨졌고, 같은 클라이언트 API를 써서 k3s와의 동작 일치성을 테스트함. “렌더링”은 Pod 간 요청을 움직이는 점으로 시각화하는 데모 앱임 - “Webernetes는 대화형 Kubernetes 콘텐츠를 만들기 위한 용도임”
- 데모는 멋짐. 이걸 어디에 쓰면 될까?
- 관련해서 며칠 전에 재미로 이걸 vibe coding으로 만들었음: https://imjasonh.github.io/kubescheduler-the-game/
재미있었음- 이 게임은 딱 내 취향임