字节跳动开源 Kelemetry:面向 Kubernetes 控制面的全局追踪系统( 二 )


Event 收集当Kubernetes控制器处理对象时,它们会发出与对象关联的“event” 。当用户运行kubectl describe命令时,这些event会显示出来,通常提供了控制器处理过程的更友好的描述 。例如,当调度器无法调度一个pod时,它会发出一个FAIlToSchedulePod事件,其中包含详细的消息:

0/4022 nodes are available to run pod xxxxx: 1072 Insufficient memory, 1819 Insufficient cpu, 1930 node(s) didn't match node selector, 71 node(s) had taint {xxxxx}, that the pod didn't tolerate.
由于event针对用于kubectl describe命令优化,它们并不保留每个原始事件,而是存储了最后一次记录事件的时间戳和次数 。另一方面,Kelemetry使用Kubernetes中的对象列表观察API检索事件,而该API仅公开event对象的最新版本 。为了避免重复事件,Kelemetry使用了几种启发式方法来“猜测”是否应将event报告为一个跨度:
  • 持久化处理的最后一个event的时间戳,并在重启后忽略该时间戳之前的事件 。虽然事件的接收顺序不一定有保证(由于客户端时钟偏差、控制器 — apiserver — etcd往返的不一致延迟等原因),但这种延迟相对较小,可以消除由于控制器重启导致的大多数重复 。
  • 验证event的resourceVersion是否发生了变化,避免由于重列导致的重复event 。
将对象状态与审计日志关联在研究审计日志进行故障排除时,我们最想知道的是“此请求改变了什么”,而不是“谁发起了此请求”,尤其是当各个组件的语义不清楚时 。Kelemetry运行一个控制器来监视对象的创建、更新和删除事件,并在接收到审计事件时将其与审计跨度关联起来 。当Kubernetes对象被更新时,它的resourceVersion字段会更新为一个新的唯一值 。这个值可以用来关联更新对应的审计日志 。Kelemetry把对象每个resourceVersion的diff和快照缓存在分布式KV存储中,以便稍后从审计消费者中链接,从而使每个审计日志跨度包含控制器更改的字段 。
追踪resourceVersion还有助于识别控制器之间的409冲突 。当客户端传递UPDATE请求的resourceVersion过旧,且其他请求是将resourceVersion更改时,就会发生冲突请求 。Kelemetry能够将具有相同旧资源版本的多个审计日志组合在一起,以显示与其后续冲突相关的审计请求作为相关的子跨度 。
为了确保无缝可用性,该控制器使用多主选举机制,允许控制器的多个副本同时监视同一集群,以确保在控制器重新启动时不会丢失任何事件 。
字节跳动开源 Kelemetry:面向 Kubernetes 控制面的全局追踪系统

文章插图
前端追踪转换在传统的追踪中,跨度总是在同一个进程(通常是同一个函数)中开始和结束 。因此,OTLP 等追踪协议不支持在跨度完成后对其进行修改 。不幸的是,Kelemetry 不是这种情况,因为对象不是运行中的函数,并且没有专门用于启动或停止其跨度的进程 。相反,Kelemetry 在创建后立即确定对象跨度,并将其他数据写入子跨度,是以每个审计日志和事件都是一个子跨度而不是对象跨度上的日志 。
然而,由于审计日志的结束时间/持续时间通常没有什么价值,因此追踪视图非常丑陋且空间效率低下:
字节跳动开源 Kelemetry:面向 Kubernetes 控制面的全局追踪系统

文章插图
为了提高用户体验,Kelemetry 拦截在 Jaeger 查询前端和存储后端之间,将存储后端结果返回给查询前端之前,对存储后端结果执行自定义转换流水线 。
Kelemetry 目前支持 4 种转换流水线:
  • tree:服务名/操作名等字段名简化后的原始trace树
  • timeline:修剪所有嵌套的伪跨度,将所有事件跨度放在根跨度下,有效地提供审计日志
  • tracing:非对象跨度被展平为相关对象的跨度日志

字节跳动开源 Kelemetry:面向 Kubernetes 控制面的全局追踪系统

文章插图
  • 分组:在追踪管道输出之上,为每个数据源(审计/事件)创建一个新的伪跨度 。当多个组件将它们的跨度发送到 Kelemetry 时,组件所有者可以专注于自己组件的日志并轻松地交叉检查其他组件的日志 。
用户可以在追踪搜索时通过设置“service name”来选择转换流水线 。中间存储插件为每个追踪搜索结果生成一个新的“CacheID”,并将其与实际 TraceID 和转换管道一起存储到缓存 KV 中 。当用户查看时,他们传递CacheID,CacheID 由中间存储插件转换为实际TraceID,并执行与 CacheID 关联的转换管道 。


推荐阅读