콘텐츠로 이동

Blog

출시 안내: 새로운 Rust 기반 클러스터 에이전트


tl;dr 클러스터 에이전트를 Go에서 Rust로 마이그레이션했으며, 이제 더 작고 더 적은 메모리를 사용합니다. 새 클러스터 에이전트를 사용하려면 최신 릴리스(cli/v0.8.2, helm/v0.15.2)로 업그레이드하세요. 여기에서 직접 체험해볼 수도 있습니다.


최근 클러스터 에이전트를 Go에서 Rust로 마이그레이션하기로 결정했습니다. 이제 재작성이 완료되어 기쁘게 발표할 수 있습니다. 결과물은 CPU 사용량을 최소로 유지(~0.1%)하면서 이미지 크기가 57% 감소(10MB)하고 메모리 사용량이 70% 감소(~3MB)한 클러스터 에이전트입니다.

Kubetail의 첫 번째 버전은 클러스터 내에서 실행되어 웹 브라우저를 통해 사용자에게 로그를 제공하도록 설계되었습니다. 이 버전에서 백엔드의 주된 역할은 Kubernetes API에 요청을 보내고 응답을 실시간으로 프론트엔드에 전달하는 것이었습니다. Python, JavaScript 등 몇 가지 옵션을 검토한 끝에 Go를 선택했습니다. Kubernetes API를 잘 지원하고, 멀티스레딩 지원이 뛰어나며, 빠른 실행 파일과 작은 Docker 이미지를 생성하기 때문입니다.

다음 버전의 Kubetail에서는 웹 대시보드를 로컬에서 실행할 수 있는 kubetail CLI 도구가 추가되었습니다. CLI 도구 구현에도 Go를 다시 선택했습니다. 훌륭한 CLI 인터랙션 라이브러리(spf13 감사합니다!)가 있고, 크로스 플랫폼 지원이 뛰어나며, 무엇보다 클러스터 내 대시보드에서 사용하던 Go 기반 웹 앱을 재사용할 수 있었기 때문입니다.

그때까지 Kubetail은 Kubernetes API를 통해서만 로그를 가져왔습니다. 하지만 로그 파일 크기, 마지막 이벤트 타임스탬프 등 Kubernetes API에서 노출되지 않는 데이터를 활용한 새 기능을 추가하고 싶어지면서, 각 노드의 원시 로그 파일에 직접 접근할 수 있는 에이전트가 필요하다는 것을 깨달았습니다. 다른 언어를 사용할 수도 있었지만, 가장 잘 알고 있고 지금까지 잘 활용해 온 Go를 다시 선택했습니다. 다행히 Go는 에이전트 인터페이스로 자연스러운 선택인 gRPC도 잘 지원했습니다.

당시 앱의 기능 범위를 고려했을 때, 데스크톱과 클러스터 모두에서 잘 작동한 Go를 처음 선택한 것에 매우 만족했습니다. 그러다 가장 많이 요청된 기능인 로그 검색을 어떻게 구현할지 고민하기 시작했습니다.

로그 검색을 고민하면서, 전문 검색 인덱스 대신 grep을 사용하고 싶었습니다. 대부분의 사용 사례에 충분하고, 사용자에게 전문 검색 인덱스 유지에 따른 리소스 부담을 주고 싶지 않았기 때문입니다. 동시에 개인적으로 rg를 사용해 로그를 grep해온 터라 그 속도에 깊은 인상을 받았고, grep 솔루션을 찾으면서 이것을 활용할 수 있을지 궁금해졌습니다. 그때 rg가 라이브러리 형태로도 사용 가능하다는 것을 알게 되었는데, 한 가지 조건이 있었습니다. Rust로 작성되었다는 것이었습니다.

커스텀 코드를 작성하기 전에 exec.Command를 사용해 stdin/stdout으로 rg를 외부 실행 파일로 활용하는 방법을 먼저 검토했습니다. 기본적인 사용 사례에서는 잘 동작했지만, 시간 필터, ANSI 이스케이프 시퀀스 처리, JSON 형식 줄 지원 등 커스텀 기능을 추가하면서 관리하기 어려워졌습니다. 결국 커스텀 로그 파일 grepper를 직접 작성하기로 결심했습니다. Go로 작성하는 것도 잠시 검토했지만, 성능과 견고성을 위해 rg 뒤에 있는 라이브러리인 ripgrep을 사용하고 싶었고, 그러려면 Rust로 작성해야 했습니다.

당시에는 클러스터 에이전트 전체를 Rust로 재작성하고 싶지 않았기 때문에, Go에서 Rust를 호출하는 방법(예: rustgo)을 찾아보다가 커스텀 Rust 코드를 별도의 실행 파일로 유지하고 exec.Command를 통해 Go에서 호출하는 방식을 택했습니다. 코드를 최대한 단순하게 유지하기 위해 공유 protocol buffers 스키마를 사용하고 stdin/stdout 인터페이스에서 직렬화/역직렬화를 구현했습니다.

검색 기능을 출시한 후 커뮤니티가 성장하면서 저보다 훨씬 많은 Rust 경험을 가진 두 명의 개발자를 만났습니다. Christopher Valerio(freexploit)와 Giannis Karagiannis(gikaragia)입니다. 처음에는 두 사람이 Rust 코드를 개선하기 시작했고, 코드베이스에 익숙해지면서 클러스터 에이전트에서 Go와 Rust 사이의 임피던스 불일치를 없애는 방법에 대해 논의하게 되었습니다. 검색 기능과는 별개로, 클러스터 에이전트는 클러스터의 모든 노드에서 실행되기 때문에 가능한 한 성능이 좋고 가벼워야 했는데, 이는 정확히 Rust가 빛을 발하는 사용 사례였습니다. 이러한 아이디어들이 떠오르는 가운데, 커뮤니티 회의에서 전체 에이전트를 Rust로 마이그레이션하는 아이디어를 논의했습니다. 두 사람 모두 열정적으로 참여하겠다고 해서 바로 진행하기로 했습니다!

결정을 내린 후 Christopher와 Giannis가 작업에 착수했습니다. Christopher는 프로젝트의 초기 고수준 아키텍처를 정의하고 GitHub에 초기 이슈들을 만들었습니다. 그 다음 Giannis가 참여하여 기능 구현, 테스트 작성, 다른 기여자들의 도움을 받을 수 있도록 추가 이슈들을 만들었습니다. Giannis는 단 몇 주 만에 Go 기반 클러스터 에이전트와 동등한 기능을 구현했고, 약 일주일간의 추가 테스트 후 코드를 main에 머지할 준비가 되었다고 판단했습니다.

저는 최근에야 Rust를 배우기 시작했기 때문에 Giannis의 풀 리퀘스트를 검토하는 데 Claude Code와 Codex CLI가 큰 도움이 되었습니다. Giannis도 자신의 작업에 챗봇을 활용하고 있었기에, 이는 GitHub 풀 리퀘스트를 통해 이루어진 진정한 인간-봇 협업이었습니다. 핵심적인 이점 중 하나는 에이전트가 잘 정의된 gRPC 인터페이스를 사용하기 때문에 protocol buffers 스키마를 재사용하고, Rust 기반 에이전트가 Go 기반 버전과 기능적으로 동등해지는 시점에 스위치를 전환하는 방식을 취할 수 있었다는 점입니다. Rust 기반 gRPC 서버를 구축하는 데는 tonic을 사용했는데, 직관적이었으며 Go 기반 gRPC 서버와 비교해 사소한 차이점만 있었습니다.

최종 결과물은 CPU 사용량을 최소로 유지(~0.1%)하면서 이미지 크기가 57% 감소(10MB)하고 메모리 사용량이 70% 감소(~3MB)한 클러스터 에이전트입니다. 게다가 이제 코드 전체가 같은 언어로 작성되어 훨씬 다루기 쉬워졌습니다.

저희의 미션은 단순하고 가벼운 패키지로 강력한 로깅 도구를 사용자에게 제공하는 것입니다. 하지만 Kubernetes API는 로깅 기능이 제한적이어서 더 고급 기능을 활성화하려면 각 노드의 원시 로그 파일에 직접 접근이 필요합니다. 바로 그 역할을 클러스터 에이전트가 담당합니다. 에이전트는 앞으로 구현할 모든 기능의 토대입니다.

물론 사용자들이 클러스터에 에이전트를 설치하는 것에 신중한 것은 충분히 이해합니다. 유용성 외에도 에이전트는 작고, 빠르고, 안전해야 합니다. Rust 마이그레이션은 이러한 요구 사항에 대한 저희의 답변입니다. 이미지 크기를 절반 이상 줄이고 메모리 사용량을 70% 감소시킴으로써 Kubetail 에이전트는 리소스가 가장 제한된 환경에서도 배포할 수 있을 만큼 작아졌습니다.

하지만 이것은 시작에 불과합니다. Rust를 통해 CPU와 메모리를 최소한으로 사용하면서 클러스터 내에서 디스크의 파일을 직접 실시간으로 처리하는 가능성의 한계를 계속 넓혀나갈 것입니다. 지금은 로그에 집중하고 있지만, 같은 접근 방식은 메트릭, 알림, 그 외 다양한 유형의 옵저버빌리티 데이터에도 적용됩니다.

앞으로의 계획에 기대가 크며, 여러분도 함께해 주시길 바랍니다. 저희가 하는 일에 공감하고 코드 기여나 사용자로서 피드백을 공유하고 싶다면 Discord에서 함께해 주세요.

OCV에게 감사드립니다

모든 클러스터에서 실행되는 Kubernetes의 새로운 로깅 레이어를 구축한다는 저희의 미션이 성공한다면, 그것은 Alex Smith가 이끄는 Open Core Ventures(OCV)와 그들의 Catalyst 프로그램 덕분이라고 해도 과언이 아닐 것입니다.

OCV는 Sid Sijbandij(GitLab 공동 창업자)가 설립한 벤처 회사로, 오픈 코어 원칙에 기반한 초기 단계의 오픈소스 기업에 투자합니다. 오픈소스 생태계 지원 활동의 일환으로 Catalyst라는 프로그램을 만들었는데, 이 프로그램은 프로젝트를 성장시키고자 하는 오픈소스 프로젝트 메인테이너에게 소정의 지원금과 많은 양의 멘토십을 제공합니다. 12주에 걸쳐 오픈소스 커뮤니티를 구축하는 방법과 제품을 효과적으로 마케팅하여 성장과 트랙션을 달성하는 방법을 가르쳐 줍니다. Kubetail은 최근 이 프로그램에 참가했으며, 저희에게는 게임 체인저였습니다.

Kubetail을 시작하기 전에 저는 Y Combinator W07 배치에 속한 Octopart라는 스타트업의 공동 창업자였습니다. 스타트업치고는 나쁘지 않은 결과를 얻었기에, Kubetail을 시작할 때도 비슷한 접근 방식을 따랐습니다. MVP 구축에 집중하고 준비가 되는 즉시 Hacker News(HN)에 포스팅했습니다. 다행히 몇 시간 동안 프런트 페이지에 오르며 수백 개의 GitHub 스타와 소수의 실제 사용자(~10명)를 확보했습니다.

그 후 Kubetail은 슬픔의 골짜기에 접어들었습니다. 이것은 초기 출시 후 화제가 사그라들고 소수의 사용자와 외부 검증 없이 내면의 낙관론만으로 버텨야 하는 스타트업 곡선의 구간입니다. 저는 이 골짜기가 낯설지 않았기에 이전처럼 묵묵히 코딩에 집중했습니다.

이 시기에 저는 MVP(Kubetail 대시보드)를 최대한 사용하기 쉽게 만드는 데 집중했습니다. 초기 사용자 몇 명의 피드백에 따라 클러스터 내부뿐 아니라 사용자 데스크톱에서도 실행할 수 있도록 아키텍처를 변경했습니다. 또한 Homebrew 및 다른 패키지 저장소를 통해 앱을 더 쉽게 찾고 다운로드할 수 있도록 하는 데도 주력했습니다. 그리고 백그라운드에서는 가장 많이 요청된 기능인 검색을 구현하는 데 집중했습니다.

1년 넘게 혼자 작업하는 동안 프로젝트의 성장은 정체되었습니다. 그러다 OCV로부터 예상치 못한 아웃리치 이메일을 받았고, 이것이 모든 것을 바꾸어 놓은 Catalyst 스폰서십 프로그램 합격으로 이어졌습니다.

Catalyst 참가의 일환으로 Alex와 OCV 팀으로부터 실질적인 멘토십을 받았으며, 기술 역량은 있지만 커뮤니티 구축이나 오픈소스 프로젝트 관리 경험이 없는 저에게 이는 매우 값진 것이었습니다. Catalyst의 도움으로 저는 순수한 코딩 중심의 루틴에서 개발과 커뮤니티 참여 및 기여자 지원을 균형 있게 병행하는 방식으로 전환했습니다.

Catalyst 참가 전 Kubetail에는 커뮤니티가 전혀 없었습니다. Discord 서버는 있었지만 저 혼자만 있었고, 매일 혼자 작업했습니다. 그 후 Alex가 주별로 집중할 사항과 새로 시도할 것들을 제안해 주었습니다. 그의 도움으로 Kubetail은 12주 만에 약 300개에서 1,300개 이상의 GitHub 스타로 성장했습니다. 그리고 더 중요한 것은 커뮤니티가 활성화되었다는 점입니다. Catalyst 이전에는 기여자가 3명이었고 Discord에 사용자가 없었습니다. 지금은 기여자가 35명이고 61명의 멤버가 있는 활발한 Discord 커뮤니티를 갖게 되었습니다.

Catalyst 기간에 모든 것이 맞아떨어졌고, 마침내 로그 검색 기능을 출시할 준비가 되었습니다. 이번에는 커뮤니티와 OCV의 멘토십을 등에 업고 새로운 사용자들에게 기능을 알릴 수 있었습니다. 이번 기능 발표로 Kubetail은 하루 이상 HN 프런트 페이지에 오르며 Reddit과 Twitter에서 수만 명의 사용자에게 노출되었습니다. 이를 통해 월 다운로드 수가 100건 미만에서 400건 이상으로 증가했고, Kubetail은 작은 열정 프로젝트에서 야심 찬 커뮤니티 주도 프로젝트로 탈바꿈했습니다. Catalyst의 하이라이트는 바로 이 무렵 새로운 Kubetail 메인테이너(rxinui)와 커뮤니티 전체와 함께 GitHub 1,000 스타 마일스톤을 기념할 수 있었던 것입니다.

Discord celebration

앞길이 얼마나 험난한지 잘 알고 있습니다. 어려운 기술적 문제를 다루고 있으며, 이미 대부분의 잠재 사용자의 관심을 사로잡고 있는 Datadog, Grafana, New Relic, ClickHouse 등 자금력이 풍부한 기업들과 경쟁해야 하는 시장에서 운영하고 있습니다. 게다가 사용자들은 이미 옵저버빌리티 도구에 많은 기능을 기대하고 있어, 많은 재능 있는 엔지니어가 필요하고, 이를 위해서는 아직 확보하지 못한 리소스도 필요합니다.

그러나 지금만큼 성공 가능성에 낙관적인 적이 없었습니다. 경험 많은 기여자로부터 새로운 것을 배울 때마다, 또는 젊은 기여자의 풀 리퀘스트가 머지될 때 그들이 보이는 흥분을 볼 때마다 에너지가 솟구칩니다. 사용자가 자신의 문제를 해결하기 위한 풀 리퀘스트를 검토하거나 새 기능에 대해 대화할 때마다 제품을 만드는 최선의 방법을 선택했다는 확신이 더욱 커집니다. 바로 오픈소스 커뮤니티의 일원으로 함께 만들어가는 것입니다.

저에게 오픈소스 프로젝트는 사용자들이 사랑하고 신뢰할 수 있는 고품질 제품을 만들어내는 마법 솥과 같습니다. 물론 모든 제품 뒤에 있는 핵심 재료는 커뮤니티이며, Kubetail 커뮤니티를 이야기할 때는 Alex와 OCV 팀 전체에 진심 어린 감사를 전하지 않을 수 없습니다.

출시 안내: Kubernetes 실시간 로그 검색

Kubetail이 작년에 출시된 이후, 가장 많이 요청된 기능은 단연 로그 검색이었습니다. 이제 드디어 최신 공식 릴리스(cli/v0.4.3, helm/v0.10.1)에서 로그 검색 기능을 사용할 수 있게 되어 기쁩니다. 실제 동작은 아래에서 확인하실 수 있습니다:

https://www.kubetail.com/demo

검색 기능 구현에 시간이 걸린 이유는 Kubernetes API가 검색을 기본적으로 지원하지 않아 처음부터 직접 구현해야 했기 때문입니다. 클라이언트 측 grepping으로 빠르게 구현하는 방안도 검토했지만, 검색마다 수많은 로그 파일을 전체 다운로드해야 할 수 있어 속도가 느리고 대역폭 소모가 크다는 점에서 좋은 사용자 경험을 제공하기 어려웠습니다. 이를 우회하는 방법도 있지만 사용자에게 추가 입력을 요구해야 하므로 역시 좋은 사용자 경험이 아니었습니다.

대신, ripgrep을 래핑한 커스텀 Rust 기반 실행 파일을 만들어 검색을 구현했습니다. 왜 Rust냐고요? 빠르기 때문입니다. Kubetail 백엔드의 대부분은 Go로 작성되어 있지만, 디스크에서 로그 파일을 읽는 이 저수준 컴포넌트는 가능한 한 빠르게 동작하길 원했습니다. 결과적으로 1GB 파일을 전체 스캔하는 데 약 250ms가 걸립니다. 모든 쿼리에서 이 실행 파일은 각 노드의 관련 컨테이너 로그 파일만 스캔하고, 매칭되는 줄만 브라우저로 스트리밍합니다. 대부분의 쿼리는 조기 종료가 가능하므로 전체 스캔 전에 결과를 반환할 수도 있습니다. Kubetail 검색을 Kubernetes 로그용 “원격 grep”이라고 생각하면 됩니다. 이제 로컬에서 grep하기 위해 전체 로그 파일을 다운로드할 필요가 없습니다.

검색 기능을 활성화하려면 클러스터에 Kubetail “클러스터 리소스”를 설치해야 합니다. GUI에서 “설치”를 클릭하거나 CLI에서 kubetail cluster install을 실행하면 간단히 설치할 수 있습니다. 이 작업을 수행하면 각 노드에 Kubetail 클러스터 에이전트와 Kubetail 클러스터 API 인스턴스가 배포됩니다. 클러스터 API가 사용 가능한 경우 대시보드는 이를 통해 노드에서 검색과 같은 Kubetail 커스텀 기능에 접근합니다. 사용 불가능한 경우에는 해당 기능을 GUI에서 비활성화하고 Kubernetes API를 사용하는 방식으로 전환합니다.

로그 검색은 이제 막 시작이며, 더욱 발전시킬 여지가 많이 남아 있습니다. Rust, Go, React 개발자이거나 로그를 사랑하는 UI 디자이너라면 오픈소스 생태계에서 가장 사용자 친화적인 Kubernetes 로깅 플랫폼을 함께 만들어 가요. Discord에서 커뮤니티에 합류하세요!

Andres

Kubetail CLI "logs" 명령어 출시 안내

Kubernetes에서 멀티 컨테이너 워크로드를 보다 쉽게 모니터링하고 디버깅할 수 있도록, Kubetail CLI 도구에 새로운 logs 명령어를 추가했습니다. 새로운 logs 명령어를 사용하면 터미널에서 실시간으로 Kubernetes 워크로드 로그를 grep할 수 있습니다. 또한 시간 및 노드, 가용 영역과 같은 소스 속성으로 필터링할 수도 있습니다.

Kubetail CLI 도구를 설치하려면 릴리스 페이지에서 다운로드하거나 Homebrew를 사용하세요:

Terminal window
brew install kubetail

새로운 logs 명령어로 할 수 있는 몇 가지 예시를 소개합니다:

Terminal window
# Tail 'web' deployment in the 'default' namespace
kubetail logs deployments/web
# Tail 'web' deployment in the 'frontend' namespace
kubetail logs frontend:deployments/web
# Return last 100 records
kubetail logs deployments/web --tail=100
# Return first 100 records
kubetail logs deployments/web --head=100
# Stream new records
kubetail logs deployments/web --follow
# Return all records
kubetail logs deployments/web --all
# Return first 10 records starting from 30 minutes ago
kubetail logs deployments/web --since PT30M
# Return last 10 records leading up to 30 minutes ago
kubetail logs deployments/web --until PT30M
# Return first 10 records between two exact timestamps
kubetail logs deployments/web --since 2006-01-02T15:04:05Z07:00 --until 2007-01-02T15:04:05Z07:00
# Return last 10 records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --force
# Return first 10 records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --head --force
# Return last 10 records that match "GET /about" or "GET /contact"
kubetail logs deployments/web --grep "GET /(about|contact)" --force
# Stream new records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --follow --force

logs 명령어는 로컬 kube config 파일을 사용하여 클러스터에 인증하므로, 클러스터를 전환하려면 kube config 컨텍스트를 변경하면 됩니다. --kube-context 플래그를 사용할 수도 있습니다:

Terminal window
kubetail logs --kube-context minikube deployments/web

한 가지 주의할 점은, --grep을 사용하려면 --force도 함께 사용해야 한다는 것입니다. 이는 필터링이 클라이언트 측에서 이루어지기 때문인데, 즉 도구가 원하는 수의 매칭 결과를 찾을 때까지 클러스터에서 지속적으로 로그를 다운로드하게 됩니다. 이로 인해 예상치 못한 대용량 다운로드가 발생할 수 있어 보조 플래그 확인 절차를 추가했습니다. 현재 이 문제를 해결하기 위한 새로운 기능을 개발 중입니다.

새로운 logs 명령어를 사용해 보시고 의견을 알려주세요!