跳转到内容

Blog

发布:全新基于 Rust 的集群代理


tl;dr 我们将集群代理从 Go 迁移至 Rust,现在它更小、占用内存更少。要使用新的集群代理,只需升级到最新版本(cli/v0.8.2,helm/v0.15.2)即可。也可以在这里在线体验。


近期,我们决定将集群代理从 Go 迁移至 Rust。现在重写工作已经完成,结果是一个镜像体积缩小 57%(10MB)、内存使用减少 70%(约 3MB)、CPU 占用依然极低(约 0.1%)的集群代理。

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 非常满意,它在桌面端和集群端都表现良好。然后我开始思考如何实现呼声最高的功能:日志搜索。

开始思考日志搜索时,我就知道想用 grep 而不是全文索引,因为 grep 对大多数使用场景已经足够,也不想让用户承担维护全文索引的资源开销。与此同时,我个人用 rg grep 日志已有一段时间,对其速度印象深刻,在寻找 grep 解决方案时,我好奇是否能加以利用。就在这时,我发现它可以作为库使用——但有一个条件:它是用 Rust 编写的。

在编写自定义代码之前,我探索了通过 exec.Commandrg 作为外部可执行文件、通过 stdin/stdout 交互的方案。这在基本用例下运行良好,但随着我添加时间过滤、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 上创建了一些初始 issue。随后 Giannis 加入,开始实现功能集、编写测试,并创建更多 issue 以获得其他贡献者的帮助。Giannis 仅用几周时间就实现了与基于 Go 的集群代理的功能对等,经过约一周的测试后,我们认为代码已准备好合并到 main 分支。

我自己最近才开始学习 Rust,因此 Claude Code 和 Codex CLI 在帮我审查 Giannis 的 pull request 方面发挥了重要作用。他在自己那边也在使用这些聊天机器人,这是一次真正的人机协作,通过 GitHub pull request 来完成。我们拥有的一个关键优势是,代理使用定义良好的 gRPC 接口,因此我们能够复用 protocol buffers 模式,并在基于 Rust 的代理达到与基于 Go 的版本功能对等时直接切换。构建基于 Rust 的 gRPC 服务器时,我们使用了 tonic,使用起来很直观,与基于 Go 的 gRPC 服务器相比只有少量差异

最终结果是一个镜像体积缩小 57%(10MB)、内存使用减少 70%(约 3MB)、CPU 占用依然极低(约 0.1%)的集群代理。而且由于现在代码全部使用同一种语言编写,维护起来也方便了很多。

我们的使命是在简单轻量的包装中为用户提供强大的日志工具,但 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 之前,我是 Octopart 的联合创始人,该公司是 Y Combinator W07 批次的成员。作为创业公司,我们表现还不错,因此在开始做 Kubetail 时,我沿用了类似的方式:专注于构建 MVP,一旦准备好就发布到 Hacker News(HN)。幸运的是,帖子在几小时内登上了首页,我们获得了数百个 GitHub star 和少量真实用户(约 10 人)。

随后,Kubetail 进入了悲伤之谷。这是创业曲线中初次发布后的阶段,最初的热潮消退,只剩下少数用户,没有外部验证,全靠自己内心的乐观支撑。我对这个谷底并不陌生,于是像以前一样,埋头继续编码。

在这段时间里,我专注于让 MVP(Kubetail 仪表板)尽可能易用。根据少数早期用户的反馈,我修改了架构,使其既能在集群内运行,也能在用户桌面上运行。我还致力于让用户更方便地通过 Homebrew 和其他包仓库找到并下载应用。与此同时,我在后台专注于实现最受期待的功能:搜索。

一年多来,我独自工作,项目增长停滞不前。然后我收到了一封来自 OCV 的意外邮件,最终促成了我们加入 Catalyst 赞助项目,改变了一切。

作为 Catalyst 的参与者,我得到了 Alex 和 OCV 团队的亲身指导,这对有技术能力但缺乏社区建设或开源项目管理经验的我来说弥足珍贵。在 Catalyst 的帮助下,我将日常工作从纯编码转变为在开发、社区参与和贡献者支持之间取得平衡。

参加 Catalyst 之前,Kubetail 没有任何社区。我们有一个 Discord 服务器,但只有我自己,每天独自工作。之后,Alex 每周指导我,建议我应该关注什么、尝试什么新事物。在他的帮助下,Kubetail 在 12 周内从约 300 个 star 增长到超过 1,300 个。更重要的是,社区活跃起来了。Catalyst 之前,我们有 3 名贡献者,Discord 中没有用户。现在,我们有 35 名贡献者和一个拥有 61 名成员的活跃 Discord 社区。

Catalyst 期间,一切都水到渠成,我们终于准备好推出日志搜索功能——这次有了社区的支持和 OCV 的导师指导,帮助我们向新用户推广这项功能。这次宣布功能时,Kubetail 在 HN 首页停留了超过一天,并在 Reddit 和 Twitter 上被数万用户看到。这使每月下载量从不足 100 次增长到超过 400 次,将 Kubetail 从一个小的热情项目转变为一个充满雄心的社区驱动项目。对我来说,Catalyst 的高光时刻就在这前后——能够与新的 Kubetail 维护者(rxinui)和社区其他成员一起庆祝 GitHub 1,000 star 里程碑。

Discord 庆祝

我对前路的艰难没有幻想。我们面对的是一个困难的技术问题,在一个拥有 Datadog、Grafana、New Relic、ClickHouse 等众多资金雄厚企业的市场中运营,这些企业已经吸引了大多数潜在用户的注意。此外,用户对可观测性工具已有诸多功能期待,我们需要大量优秀工程师来实现目标,为此还需要尚未解决如何获取的资源。

然而,我从未像现在这样对成功充满信心。每次从经验丰富的贡献者那里学到新知识,或者看到年轻贡献者的 pull request 被合并时他们的兴奋,都让我充满能量。每次审阅用户为解决自己问题提交的 pull request,或与人讨论新功能,都让我更加确信,以开源社区的一员、共同构建产品的方式是最佳选择。

对我来说,开源项目就像一口锅,能够产出用户热爱使用、对他们也有益的高质量产品。当然,每个产品背后的神奇成分是社区,谈到 Kubetail 的社区,我必须在此向 Alex 和 OCV 团队全体表达诚挚的感谢。

发布:Kubernetes 实时日志搜索

自去年 Kubetail 发布以来,日志搜索一直是呼声最高的功能需求。现在,我很高兴地宣布,日志搜索功能已在最新官方版本(cli/v0.4.3,helm/v0.10.1)中正式推出。您可以在这里查看实际效果:

https://www.kubetail.com/demo

实现搜索功能花费了较长时间,原因在于 Kubernetes API 原生不支持搜索,我们需要从零开始构建。我们曾考虑通过客户端 grep 快速实现,但这种方式用户体验不佳——每次搜索可能需要下载大量日志文件,既慢又耗费带宽。虽然有变通方案,但需要用户额外操作,同样影响体验。

因此,我们通过封装 ripgrep 的自定义 Rust 可执行文件来实现搜索。为什么选择 Rust?因为它极快。Kubetail 后端大部分用 Go 编写,但对于这个直接读取磁盘日志文件的底层组件,我们希望它尽可能快。结果:扫描一个 1GB 的文件只需约 250ms。每次查询时,该可执行文件仅扫描每个节点上相关的容器日志文件,并将匹配的行流式传输到浏览器。大多数查询可以提前终止,甚至在完整扫描前就能返回结果。您可以将 Kubetail 搜索理解为 Kubernetes 日志的”远程 grep”——不再需要下载整个日志文件再本地 grep。

要启用搜索功能,需要在集群中安装 Kubetail”集群资源”。可以通过 GUI 点击”Install”或在 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 命令,您可以直接在终端中实时 grep Kubernetes 工作负载的日志,还可以按时间范围以及节点、可用区等来源属性进行过滤。

安装 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 命令,并告诉我们您的使用体验!