内容
# Qubit
**基于eBPF的Kubernetes可观测性,用于AI代理**
Qubit通过在内核级别捕获实时的HTTP服务依赖关系,并通过 [模型上下文协议(MCP)](https://modelcontextprotocol.io) 公开它们 - 这样您就可以在不接触应用程序代码的情况下,向Claude询问您的服务拓扑结构、最近的Kubernetes事件和原始网络流量。
```
> 哪些服务依赖于payment-service?
> 我的服务图在最近一小时内发生了什么变化?
> 显示checkout和inventory之间的所有流量
```
无需 sidecar、无需仪器SDK、无需代码更改。Qubit将一个eBPF过滤器附加到每个节点的网络接口,拦截OS内核级别的HTTP数据包,并将它们与Kubernetes元数据相关联,以构建一个实时的依赖关系图 - 包括通过Envoy等透明代理路由的流量。
## 状态
**早期alpha版本 - 在单节点Kind集群上测试。尚未为生产环境强化。**
当前功能:
- 完整的eBPF捕获 → 丰富 → 拓扑管道端到端
- Kubernetes元数据收集(11种资源类型)
- Envoy透明代理解析
- 用于AI助手集成的MCP工具
- 用于拓扑可视化的Web UI
已知缺陷(生产环境前):无高可用性,无Prometheus指标,无gRPC的TLS/auth,无优雅的关闭协调,无硬编码的K8s清单中的开发地址(参见 [本地开发架构](#本地开发架构))。
## 架构
```
Kubernetes集群
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ eBPF守护进程 集群代理 │
│ (每个节点) (单个副本) │
│ │
│ 内核 ──► ebpf-loader 监视K8s API: │
│ • 捕获HTTP数据包 • Pod(IP ↔ 服务映射) │
│ • 提取方法,路径,主机 • 服务,配置映射 │
│ • 通过gRPC发送事件 • 部署,入口等 │
│ • 解析envoy.yaml配置映射 │
│ → 域 → 服务路由 │
└──────────┬───────────────────────────────────────┬───────────────────┘
│ gRPC:50051 │ gRPC:50051
│ 发送Ebpf网络事件 │ 发送Pod事件
│ │ 发送服务Pod映射
│ │ 发送Envoy路由
│ │ 发送K8s资源事件
▼ ▼
┌─────────────────────────────────────────────────────┐
│ 核心 │
│ │
│ 事件摄取(写路径) │
│ • Ebpf聚合器 ─── 批量缓冲区 ──► ClickHouse │
│ └── 目的地解析(三层): │
│ 1. EnvoyDomainCache(主机 → 服务) │
│ 2. Pod缓存(dst IP → 服务) │
│ 3. 解析k8s主机(DNS名称启发式) │
│ • K8s聚合器 ──── pod缓存 + 拓扑 │
│ │
│ Qubit查询(读路径) │
│ • 获取拓扑 ──► 节点,上游,下游 │
│ │
│ HTTP:9000 /ping │
└──────────────────────────┬──────────────────────────┘
│ gRPC + ClickHouse查询
▼
┌─────────────────────────────────────────────────────┐
│ MCP服务器 │
│ │
│ stdio JSON-RPC(模型上下文协议) │
│ • 获取拓扑 完整的服务图 │
│ • 获取服务依赖关系 每服务视图 │
│ • 获取K8s事件 最近的K8s资源事件 │
│ • 获取网络事件 原始eBPF HTTP流量 │
└─────────────────────────────────────────────────────┘
▲
│ Claude代码 / AI客户端
```
**数据流:**
1. **捕获** — 一个eBPF套接字过滤器附加到每个节点的网络接口,捕获出站HTTP数据包。用户空间加载器提取源/目标IP,端口,HTTP方法,路径和主机头,然后通过gRPC将事件转发到核心。
2. **丰富(pod元数据)** — 集群代理监视Kubernetes API的pod和服务事件。它通过标签选择器将pod与服务匹配,并将IP → 服务映射发送到核心。在启动时和每30秒重新发送完整的映射,以便核心在重启后正确恢复。
3. **丰富(Envoy路由)** — 当集群代理看到一个包含`envoy.yaml`键的ConfigMap时,它解析Envoy静态配置 - 提取集群端点FQDN和虚拟主机域 - 并将域 → 服务映射通过`SendEnvoyRoutes`推送到核心。这意味着通过Envoy代理路由的服务(其中主机头是虚拟主机名,而不是K8s FQDN)可以正确解析,无需手动设置。
4. **关联** — 当到达eBPF事件时,核心通过pod缓存解析源IP(如果IP尚未已知,则丢弃事件,防止原始IP泄漏到拓扑中)。目标按优先级顺序解析:Envoy缓存 → pod缓存 → K8s DNS名称启发式。
5. **存储** — 事件被缓冲并批量写入ClickHouse。内存拓扑图跟踪节点(服务),上游流(谁调用此服务?)和下游流(此服务调用什么?)。每个服务对都被去重 - 每条边仅保留第一个事件并添加到图中。
6. **查询** — `QubitQuery` gRPC服务公开完整的拓扑图。
## 项目结构
```
qubit/
├── core/ # 聚合服务器(在主机或作为部署运行)
│ ├── src/
│ │ ├── main.rs # 入口点 - 连接服务器,DB,拓扑
│ │ ├── lib.rs # 库根(用于负载测试)
│ │ ├── aggregator/
│ │ │ ├── ebpf_aggregator # 处理eBPF事件,目的地解析
│ │ │ └── k8s_aggregator # Pod /服务缓存,拓扑修复
│ │ ├── server/
│ │ │ ├── grpc.rs # 事件摄取 + QubitQuery gRPC处理程序
│ │ │ ├── http.rs # 健康检查端点(/ping)
│ │ │ ├── factory.rs # 服务器构造
│ │ │ └── query.rs # 读取路径查询处理程序
│ │ ├── envoy/ # EnvoyDomainCache(由集群代理填充)
│ │ ├── topology/ # 内存服务依赖关系图
│ │ ├── dao/ # ClickHouse持久性
│ │ ├── config/ # YAML配置
│ │ └── model/ # 事件类型,错误
│ ├── proto/qubit.proto # gRPC服务定义
│ └── hack/ # Makefile,ClickHouse docker-compose
│
├── cluster-agent/ # K8s元数据收集器(在集群内运行)
│ ├── src/
│ │ ├── kubernetes/
│ │ │ ├── informer.rs # 通用K8s资源监视器(EventHandler特质)
│ │ │ ├── informer_factory # 类型化的informers(Pod,服务,配置映射,
│ │ │ │ # 部署,副本集,入口,HPA,节点,
│ │ │ │ # 滚动,外部秘密,HTTP代理,虚拟服务)
│ │ │ ├── configmap_handler# 检测envoy.yaml → 触发路由推送
│ │ │ ├── envoy_parser # 解析Envoy静态YAML → 域→服务映射
│ │ │ ├── service_registry # 服务选择器的内存缓存
│ │ │ ├── pod_handler # 通过标签匹配将pod映射到服务
│ │ │ └── service_handler # 跟踪服务选择器更改
│ │ └── service/
│ │ └── cluster_aggregator # 到核心的gRPC客户端
│ └── proto/qubit.proto
│
├── ebpf/ # eBPF内核程序(TC套接字过滤器)
│ └── src/main.rs
│
├── ebpf-loader/ # 用户空间eBPF加载器守护进程
│ └── src/
│ ├── loader/ # 加载eBPF字节码,读取性能数组
│ ├── service/ # 到核心的gRPC客户端
│ └── config/
│
├── ebpf-common/ # eBPF内核和加载器之间的共享类型
│
├── load-tests/ # 流量生成器
│ └── src/main.rs # 两条流:K8s事件 + HTTP流量
│ # 运行方式:make -C ebpf/hack vm-load-gen
│
├── mcp-server/ # MCP服务器(AI助手接口)
│ └── src/
│ ├── main.rs # stdio JSON-RPC传输
│ ├── tools.rs # MCP工具定义
│ ├── grpc_client.rs # 封装核心的QubitQuery gRPC服务
│ ├── ch_client.rs # 查询ClickHouse原始事件
│ └── config.rs
│
├── helm/qubit/ # Kubernetes部署的Helm图表
│ ├── Chart.yaml
│ ├── values.yaml # 所有可调默认设置
│ └── templates/
│ ├── core/ # 部署 + 服务 + 配置映射
│ ├── cluster-agent/ # 部署 + RBAC + 服务帐户
│ └── ebpf-loader/ # DaemonSet + 配置映射
│
└── ebpf/hack/ # K8s清单和开发工具
└── k8s/
├── ebpf-daemonset.yaml # eBPF加载器DaemonSet(仅限开发 - 硬编码IP)
├── cluster-agent.yaml # 集群代理部署 + RBAC(仅限开发)
├── envoy-proxy.yaml # Envoy代理 + 配置映射(静态配置)
└── test-pods.yaml # 测试服务(service-a → service-b通过Envoy)
```
## 先决条件
- **Rust** (稳定工具链)
- **protoc** - `brew install protobuf`(macOS)或`apt install protobuf-compiler`(Linux)
- **ClickHouse** - 用于事件持久性
- **Lima** - 用于macOS上的本地K8s开发(`brew install lima`)
- **Kind** - Docker中的Kubernetes(安装在Lima VM中)
- **Docker** - 用于ClickHouse和eBPF字节码编译
- **Helm** - 用于Kubernetes部署(`brew install helm`)
- **grpcurl** - 用于测试gRPC服务(`brew install grpcurl`)
## 快速入门
### 1. 启动核心(macOS)
```bash
make -C core/hack core-up
```
启动ClickHouse并运行核心。监听:
- `localhost:50051` - gRPC(事件摄取 + QubitQuery)
- `localhost:9000` - HTTP健康检查
### 2. 部署到Kind集群(Lima VM)
```bash
# 一次性设置
make -C ebpf/hack lima-create # 创建带有Docker + Kind的Ubuntu VM
make -C ebpf/hack vm-setup # 在VM中安装Rust工具链
make -C ebpf/hack build-ebpf # 编译eBPF字节码(通过Docker,macOS)
make -C ebpf/hack vm-cluster-create # 创建Kind集群
make -C ebpf/hack vm-envoy-pull # 将Envoy镜像预加载到Kind
# 完整的首次部署
make -C ebpf/hack vm-test
```
部署到Kind:
- **eBPF DaemonSet** - 在每个节点捕获HTTP流量
- **集群代理** - 监视pod/服务/配置映射,发送元数据和Envoy路由到核心
- **Envoy代理** - 在测试服务之间路由流量
- **测试pod** - `service-a`(curl)调用`service-b`(nginx)通过Envoy每5秒
### 3. 迭代
```bash
# 重新构建和重新部署代码更改后
make -C ebpf/hack vm-redeploy
# 检查pod状态
make -C ebpf/hack vm-status
# 跟随eBPF加载器日志
make -C ebpf/hack vm-logs
# 生成负载(K8s事件 + HTTP流量)
make -C ebpf/hack vm-load-gen
make -C ebpf/hack vm-load-gen K8S_RPS=50 HTTP_RPS=100 DURATION=120
```
### 4. 打开UI
```bash
cd ui && npm install && npm run dev
```
在`http://localhost:5173`打开。三个选项卡:**拓扑**(交互式服务图),**K8s事件**(最近的Kubernetes资源事件),**网络事件**(原始eBPF HTTP流量)。
### 5. 通过gRPC查询拓扑
```bash
grpcurl -plaintext localhost:50051 qubit.QubitQuery/GetTopology
```
预期:`service-a → service-b`(不是`service-a → envoy-proxy`)。Envoy路由解析将`envoy-proxy.default.svc`主机头映射回`service-b`。
## 使用Helm部署
Helm图表将所有Qubit组件完全部署到集群中。ClickHouse单独安装,以便它可以独立于Qubit升级进行管理。
### 1. 安装ClickHouse
```bash
helm install clickhouse oci://registry-1.docker.io/bitnamicharts/clickhouse \
--set auth.password=yourpassword \
--set shards=1 \
--set replicaCount=1 \
--set zookeeper.enabled=false
```
或使用任何现有的ClickHouse实例 - 云托管或自行管理。
### 2. 安装Qubit
```bash
helm install qubit ./helm/qubit \
--set clickhouse.host=clickhouse.default.svc.cluster.local \
--set clickhouse.password=yourpassword
```
### 3. 验证
```bash
kubectl get pods
# qubit-core, qubit-cluster-agent, qubit-ebpf-loader 都应该达到运行状态
kubectl port-forward svc/qubit-core 50051:50051
grpcurl -plaintext localhost:50051 qubit.QubitQuery/GetTopology
```
拓扑结构在第一个HTTP流量流经服务之间的大约30秒内填充。
### 关键值
| 值 | 默认值 | 描述 |
|-------|---------|-------------|
| `clickhouse.host` | `""` (必填)| ClickHouse服务主机名 |
| `clickhouse.port` | `8123` | ClickHouse HTTP端口 |
| `clickhouse.username` | `default` | ClickHouse用户名 |
| `clickhouse.password` | `""` (必填)| ClickHouse密码 |
| `ebpfLoader.config.interface` | `eth0` | 要附加eBPF过滤器的网络接口 |
| `core.config.ebpfFlushIntervalSecs` | `10` | 多久将事件刷新到ClickHouse |
### 升级
```bash
helm upgrade qubit ./helm/qubit \
--set clickhouse.host=clickhouse.default.svc.cluster.local \
--set clickhouse.password=yourpassword
```
> **注意:** eBPF DaemonSet使用`hostNetwork: true`(需要捕获所有节点流量)与`dnsPolicy: ClusterFirstWithHostNet`,因此集群内部服务名称正确解析。如果您在eBPF加载器日志中看到DNS错误,请验证此dnsPolicy是否生效。## 本地开发架构
```
macOS主机 Lima VM(Ubuntu)
┌─────────────────┐ ┌─────────────────────────────┐
│ │ │ Kind集群(qubit-test) │
│ 核心 │◄── gRPC ──────│ ├── eBPF DaemonSet │
│ (cargo run) │ :50051 │ ├── 集群代理 │
│ │ │ ├── Envoy代理 │
│ │ │ ├── service-a(curl) │
│ │ │ └── service-b(nginx) │
└─────────────────┘ └─────────────────────────────┘
192.168.5.2 192.168.5.15
```
核心在macOS上原生运行以进行快速迭代。Kind集群内的Lima VM主机eBPF DaemonSet(需要Linux内核访问),集群代理,Envoy和测试工作负载。所有集群内组件到达核心时地址为`192.168.5.2:50051`(Mac主机网关)。
> **注意:** 地址`192.168.5.2`是Lima VM的默认Mac主机网关。集群代理和ebpf-loader的K8s ConfigMaps硬编码此地址 - 它们是仅限开发的清单,需要为任何其他环境进行更新。## 配置
### 核心 (`core/config.yaml`)
```yaml
app:
http_port: 9000
grpc_port: 50051
ebpf_bulk_insertion_max_size: 100 # 10秒内将事件刷新到ClickHouse
ebpf_flush_interval_secs: 5 # 定期刷新间隔
db:
host: localhost
port: 8123
user: default
password: "qubit"
table:
ebpf_network_events: ebpf_network_events
k8s_resource_events: k8s_resource_events
```
ClickHouse表在第一次运行时自动创建。eBPF事件保留 **7 天**;K8s资源事件保留 **1 天**。超过这些TTL的数据由ClickHouse的后台合并过程自动删除。
### 集群代理 (`cluster-agent/config.yaml`)
```yaml
qubit_core:
host: "192.168.5.2" # 核心地址(从Lima VM的Mac主机网关)
grpc_port: 50051
kubernetes:
namespace: "" # 空 = 监视所有命名空间
```
### eBPF 加载器 (`ebpf/hack/k8s/config.yaml`)
```yaml
qubit_core:
host: "192.168.5.2"
grpc_port: 50051
perf_array_name: "NETWORK_EVENTS"
ebpf_path: "/app/ebpf-bytecode"
```
## gRPC API
### 写路径 — `EventIngestion`
| RPC | 发送者 | 描述 |
|-----|--------|-------------|
| `SendEbpfNetworkEvent` | eBPF 加载器 | HTTP数据包事件(源/目标IP,方法,路径,主机) |
| `SendPodEvent` | 集群代理 | Pod创建/删除 |
| `SendServiceEvent` | 集群代理 | 服务创建/删除 |
| `SendConfigMapEvent` | 集群代理 | ConfigMap创建/删除 |
| `SendServicePodMap` | 集群代理 | 批量pod→服务映射(启动 + 30秒重新同步) |
| `SendK8sResourceEvent` | 集群代理 | 通用K8s资源事件(部署,入口等) |
| `SendEnvoyRoutes` | 集群代理 | 从Envoy ConfigMap解析的域→服务映射 |
### 读路径 — `QubitQuery`
| RPC | 描述 |
|-----|-------------|
| `GetTopology` | 完整的服务依赖关系图 — 节点,上游,下游 |
## MCP 服务器
通过stdio JSON-RPC将Qubit的数据暴露给AI助手 [模型上下文协议](https://modelcontextprotocol.io)。
### 配置 (`mcp-server/config.yaml`)
```yaml
qubit_core:
grpc_address: "http://localhost:50051"
clickhouse:
host: localhost
port: 8123
user: default
password: "qubit"
database: default
ebpf_table: ebpf_network_events
k8s_table: k8s_events
```
Claude 代码集成
在仓库根目录下创建 `.mcp.json` 文件,指向 Claude Code 编译后的二进制文件:
```bash
cargo build -p qubit-mcp
cat > .mcp.json << 'EOF'
{
"mcpServers": {
"qubit": {
"command": "/absolute/path/to/target/debug/qubit-mcp",
"args": []
}
}
}
EOF
# Claude Code 将在下次启动时自动检测到它。
```
`.mcp.json` 文件被 gitignore(它包含机器本地的二进制文件路径)。
### 可用的 MCP 工具
| 工具 | 描述 |
|------|-------------|
| `get_topology` | 完整的服务依赖图 |
| `get_service_dependencies` | 单个服务的上游/下游 |
| `get_k8s_events` | 最近的 Kubernetes 资源事件 |
| `get_network_events` | 原始 eBPF 捕获的 HTTP 流量 |
## 负载生成器
`load-tests` 是一个流量生成器,具有两个可配置的流:
| 流 | 功能 | 测试内容 | 运行位置 |
|--------|-------------|-------------------|-----------|
| K8s (`--k8s-rps`) | 创建/更新/删除 ConfigMaps | cluster-agent informers → Core | Lima VM 主机 |
| HTTP (`--http-rps`) | 向 service-b 发送 GET 请求 | eBPF 捕获 → Core | 仅限集群内 |
K8s 流需要一个 `load-gen` 命名空间:
```bash
# 一次性:创建命名空间
limactl shell qubit -- kubectl create namespace load-gen
# 运行 K8s 流(10 rps,60s)
make -C ebpf/hack vm-load-gen
# 自定义速率
make -C ebpf/hack vm-load-gen K8S_RPS=50 DURATION=120
# 或者直接从 Lima VM shell 运行
limactl shell qubit -- bash -c "cd load-tests && cargo run --release --bin load-gen -- --k8s-rps 10"
```
> **注意:** HTTP 流使用 `http://service-b.default.svc.cluster.local/`,只有在集群内的 Pod 中才能解析。从 Lima VM 主机运行 `HTTP_RPS > 0` 将产生错误。HTTP 流的 K8s Job 部署路径尚未实现。
## 测试
```bash
# 单元测试 — 在 macOS 上运行,不需要 Linux 或集群(16 个测试)
cargo test -p Qubit -p qubit-mcp
# 端到端测试:部署到 Kind,生成负载,观察拓扑
make -C ebpf/hack vm-test
make -C ebpf/hack vm-load-gen # 仅限 K8s 流;参见负载生成器部分
grpcurl -plaintext localhost:50051 qubit.QubitQuery/GetTopology
```
`cluster-agent`、`ebpf-loader` 和 `load-tests` 依赖于 Aya(仅限 Linux 的 eBPF 框架),无法在 macOS 上编译。需要在 Lima VM 中构建和测试它们:
```bash
limactl shell qubit -- bash -c "cd /path/to/qubit && cargo test -p cluster-agent -p ebpf-loader"
```
`ebpf-common` 测试需要 `user` 功能标志,也需要 Linux:
```bash
# 仅限在 Lima VM 中运行
cargo test -p ebpf-common --features user
```
## 技术栈
| 组件 | 技术 |
|-----------|-----------|
| 语言 | Rust |
| 异步运行时 | Tokio |
| eBPF 框架 | Aya |
| gRPC | Tonic + Prost |
| HTTP 服务器 | Axum |
| K8s 客户端 | kube-rs + k8s-openapi |
| 数据库 | ClickHouse |
| 缓存 | Moka |
| 错误处理 | thiserror + anyhow |
| MCP 框架 | rmcp |
| 开发环境 | Lima + Kind |
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器