リリース:新しい Rust 製クラスターエージェント
tl;dr クラスターエージェントを Go から Rust に移行し、より小さく、より少ないメモリで動作するようになりました。新しいクラスターエージェントを使用するには、最新リリース(cli/v0.8.2、helm/v0.15.2)にアップグレードしてください。こちらでライブ体験もできます。
最近、クラスターエージェントを Go から Rust に移行することを決定しました。書き直しが完了したことをお伝えできて嬉しいです。結果は、CPU 使用率を最小限(約 0.1%)に保ちながら、イメージサイズが 57% 削減(10MB)、メモリ使用量が 70% 削減(約 3MB)されたクラスターエージェントです。
最初に Go を選んだ理由
Section titled “最初に Go を選んだ理由”Kubetail の最初のバージョンはクラスター内で動作し、Web ブラウザを通じてユーザーにログを提供するよう設計されていました。そのバージョンでは、バックエンドの主な役割は Kubernetes API にリクエストを送り、レスポンスをリアルタイムでフロントエンドに中継することでした。Python や JavaScript などのオプションを検討した後、Go を選びました。Kubernetes API との互換性が優れており、マルチスレッドのサポートも充実していて、高速な実行ファイルと小さな Docker イメージを生成できるからです。
次のバージョンの Kubetail では、Web ダッシュボードをローカルで実行できる kubetail CLI ツールが追加されました。CLI ツールの実装にも再び Go を選びました。優れた CLI インタラクションライブラリがあり(spf13 に感謝!)、クロスプラットフォームサポートが優れており、何より、インクラスターダッシュボードで使用していた Go ベースの Web アプリを再利用できたからです。
それまで、Kubetail は Kubernetes API を通じてのみログを取得していました。しかし、ログファイルサイズや最後のイベントタイムスタンプなど、Kubernetes API が公開していないデータを活用した新機能を追加したくなったとき、各ノードの生のログファイルに直接アクセスできるエージェントが必要だと気づきました。別の言語を使うこともできましたが、最もよく知っていてこれまで良い結果をもたらしてくれた Go を再び選びました。幸い、Go にはエージェントのインターフェースとして自然な選択である gRPC の優れたサポートもありました。
当時のアプリの機能セットを考えると、デスクトップでもクラスター内でも Go がうまく機能していたことから、最初に Go を選んだことに非常に満足していました。そして、最も要望の多かった機能であるログ検索の実装方法を検討し始めました。
次に Rust を選んだ理由
Section titled “次に Rust を選んだ理由”ログ検索を考え始めたとき、フルテキストインデックスではなく grep を使いたいと思っていました。ほとんどのユースケースには十分であり、ユーザーにフルテキストインデックスのメンテナンスコストを負わせたくなかったからです。同時に、個人的にログの grep に rg を使っていてその速度に感銘を受けており、grep ソリューションを探す中でそれを活用できないかと考えました。そこで、ライブラリとして利用可能だが、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 の経験豊富な 2 人の開発者に出会いました。Christopher Valerio(freexploit)と Giannis Karagiannis(gikaragia)です。最初は 2 人が Rust コードの改善を始め、コードベースに慣れるにつれて、クラスターエージェントにおける Go と Rust のインピーダンスミスマッチをどう解消するかについて話し合うようになりました。検索機能とは別に、クラスターエージェントはクラスターのすべてのノードで動作するため、できるだけ高性能で軽量であることが重要です。これはまさに Rust が輝くユースケースです。こうしたアイデアが浮かぶ中で、エージェント全体を Rust に移行するアイデアを話し合うコミュニティミーティングを開きました。2 人とも喜んで取り組むと言ってくれたので、やってみることにしました!
どのように実施したか
Section titled “どのように実施したか”決定が下されると、Christopher と Giannis はすぐに作業に取り掛かりました。Christopher はプロジェクトの初期高レベルアーキテクチャを定義し、GitHub にいくつかの初期 issue を作成しました。その後 Giannis が加わり、機能セットの実装、テストの作成、他のコントリビューターの協力を得るためのさらなる issue 作成を始めました。Giannis は数週間で Go ベースのクラスターエージェントと機能的に同等になり、さらに約 1 週間のテストを経て、コードを main にマージする準備ができたと判断しました。
私自身は最近 Rust を学び始めたばかりだったので、Giannis のプルリクエストをレビューするのに Claude Code と Codex CLI が非常に役立ちました。彼も自分の側でチャットボットを使っていたので、GitHub プルリクエストを介した真の人間とボットの共同作業でした。私たちが持っていた重要な利点の一つは、エージェントが明確に定義された gRPC インターフェースを使用しているため、protocol buffers スキーマを再利用でき、Rust ベースのエージェントが Go ベースのバージョンと機能的に同等になった時点でスイッチを切り替えるだけでよかったことです。Rust ベースの gRPC サーバーの構築には tonic を使いました。これは直感的で、Go ベースの gRPC サーバーとの違いはわずかな差異のみでした。
最終結果は、CPU 使用率を最小限(約 0.1%)に保ちながら、イメージサイズが 57% 削減(10MB)、メモリ使用量が 70% 削減(約 3MB)されたクラスターエージェントです。さらに、コードがすべて同じ言語で書かれているため、作業が格段に容易になりました。
これからの方向性
Section titled “これからの方向性”私たちのミッションは、シンプルで軽量なパッケージで強力なロギングツールをユーザーに提供することです。しかし Kubernetes API のロギング機能には限界があり、より高度な機能を解放するには各ノードの生のログファイルへの直接アクセスが必要です。それがクラスターエージェントの役割です——これは次に構築したいすべてのものの基盤となります。
もちろん、ユーザーがクラスターにエージェントをインストールすることに慎重になるのは理解できます。役立つことに加えて、エージェントは小さく、高速で、安全でなければなりません。Rust への移行はこれらの要件に対する私たちの回答です。イメージサイズを半分以上削減し、メモリ使用量を 70% 削減することで、Kubetail エージェントはリソースが最も制限された環境でもデプロイできるほど小さくなりました。
しかし、これは始まりに過ぎません。Rust を活用することで、CPU とメモリをできるだけ少なく使いながら、クラスター内でディスク上のファイルをリアルタイムに直接処理する可能性の限界を押し広げていきます。現在はログに焦点を当てていますが、同じアプローチはメトリクス、通知、その他の種類のオブザーバビリティデータにも適用できます。
次のステップにワクワクしており、ぜひ皆さんにもご参加いただきたいです。私たちの取り組みに共感し、コードのコントリビューションやユーザーとしてのフィードバックを共有したい方は、Discord でご参加ください。