浅谈分布式操作系统 KubeWharf 的第二批开源项目|社区征文

2023总结

书接上文,我们已经在文章一文速览字节最新分布式操作系统中介绍了去年 7 月 KubeWharf 的首批开源的项目,分别是 KubeBrain,KubeZoo,KubeGateway,以及 KubeWharf 的构建基础 Kubernetes(K8s)。

本文我们将剖析 KubeWharf 在 2023 年,开源的第二批项目分别为:

  • Katalyst:在离线混部、资源管理与成本优化项目
  • KubeAdmiral:多云多集群调度管理项目
  • Kelemetry:面向 Kubernetes 控制面的全局追踪系统

Katalyst

Katalyst 引申自英文单词 catalyst,本意为催化剂,首字母修改为 K,寓意该系统能够为所有运行在 Kubernetes 体系中的负载提供更加强劲的自动化资源管理能力。

项目地址 | github.com/kubewharf/katalyst-core

解决的问题

随着字节跳动各业务云原生化的推进,根据不同阶段业务需求和技术特点,选择合适的混合部署方案,并在此过程中不断迭代混部系统。

由于在线部分早先已经基于 Kubernetes 进行了原生化改造,但大多数离线作业仍然基于 YARN 进行运行。为推进混合部署,我们在单机上引入第三方组件负责确定协调给在线和离线的资源量,并与 Kubelet 或 Node Manager 等单机组件打通;同时当在线和离线工作负载调度到节点上后,也由该协调组件异步更新这两种工作负载的资源分配。

该方案使得我们完成混部能力的储备积累,并验证可行性,但仍然存在一些问题:

  • 两套系统异步执行,使得在离线容器只能旁路管控,存在 race;且中间环节资源损耗过多;

  • 对在离线负载的抽象简单,使得我们无法描述复杂 QoS 要求;

  • 在离线元数据割裂,使得极致的优化困难,无法实现全局调度优化。

为解决上面问题,彻底实现在离线统一的混合部署,KubeWharf 团队使用 Katalyst 作为其中核心的资源管控层,负责实现单机侧实时的资源分配和预估,下面具体介绍 Katalyst。

Katalyst 系统介绍

如下图所示,Katalyst 系统大致分为四层,从上到下依次包括:

  • 最上层的标准 API,为用户抽象不同的 QoS 级别,提供丰富的资源表达能力;
  • 中心层则负责统一调度、资源推荐以及构建服务画像等基础能力;
  • 单机层包括自研的数据监控体系,以及负责资源实时分配和动态调整的资源分配器;
  • 最底层是字节定制的内核,通过增强内核的 patch 和底层隔离机制解决在离线跑时单机性能问题。

picture.image Katalyst QoS 可以从宏观和微观两个视角进行解读。

宏观上,Katalyst 以 CPU 为主维度定义了标准的 QoS 级别;具体来说我们将 QoS 分为四类:独占型、共享型、回收型和为系统关键组件预留的系统型;

微观上,Katalyst 最终期望状态无论什么样的 workload,都能实现在相同节点上的并池运行,不需要通过硬切集群来隔离,实现更好的资源流量效率和资源利用效率。

在 QoS 的基础上,Katalyst 同时也提供了丰富的扩展 Enhancement 来表达除 CPU 核心外其他的资源需求:

  • QoS Enhancement:扩展表达业务对于 NUMA / 网卡绑定、网卡带宽分配、IO Weight 等多维度的资源诉求;
  • Pod Enhancement:扩展表达业务对于各类系统指标的敏感程度,比如 CPU 调度延迟对业务性能的影响;
  • Node Enhancement:通过扩展原生的 TopologyPolicy 表示多个资源维度间微拓扑的组合诉求。

KubeAdmiral

KubeAdmiral 命名引申自 Admiral(读音[ˈædm(ə)rəl]),本意为舰队司令,加上 Kube(rnetes)前缀,寓意该工具具有强大的 Kubernetes 多集群编排调度能力。

项目地址 | github.com/kubewharf/kubeadmiral

解决的问题

随着业务飞速发展,内部 Kubernetes 集群的数量也不断壮大。早期出于隔离和安全的考虑,字节的各个业务线独占集群,但随着业务壮大,这些独占的集群形成资源孤岛,开始影响资源的弹性效率:1)各个业务线需要维护独立的 buffer;2)业务和集群深度绑定,业务感知大量的集群,并在集群之间为应用人肉分配资源,SRE 在运营资源上也需要深度感知业务和集群,最终导致资源在各个业务线之间的周转慢、自动化效率低以及部署率不够理想。

如何解耦应用和集群的绑定关系,将各个业务线的资源并池,成为了提升云原生底座的资源利用率以及交付效率的关键。

随着多云、混合云愈发成为业内主流形态,Kubernetes 成为了云原生的操作系统,实现了对基础设施的进一步抽象和规范,为应用提供更加统一的标准接口。在此基础上,引入 Kubernetes 集群联邦**作为分布式云场景下的云原生系统底座,面向应用提供统一的平台入口,提升应用跨集群分发的能力,做好应用跨集群的分发调度,管理好多个云云原生场景下的基础设施。

KubeAdmiral 架构介绍

KubeAdmiral 支持 Kubernetes 原生 API,提供丰富的、可扩展的调度框架,并对调度算法、分发过程进行了细致的打磨。下文对一些显著特性进行详细介绍:

picture.image

  • 丰富的多集群调度能力:KubeAdmiral 引入了更丰富的调度语义,支持通过标签、污点等更灵活的方式选择集群,提供有状态、作业类资源调度能力,同时引入依赖跟随调度等优化。

下图展示了 PropagationPolicy 对象配置文件.yaml 来操控调度的语意:

apiVersion: core.kubeadmiral.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: mypolicy
  namespace: default
spec:
  # 提供多种集群选择方式,最终结果取交集
  placement: # 手动指定集群与权重
    - cluster: Cluster-01
      preferences:
        weight: 40
    - cluster: Cluster-02
      preferences:
        weight: 30
    - cluster: Cluster-03
      preferences:
        weight: 40
  clusterSelector: # 类似Pod.Spec.NodeSelector,通过label过滤集群
    IPv6: "true"
  clusterAffinity: # 类似Pod.Spec.NodeAffinity,通过label过滤集群,语法比clusterSelector更加灵活
    - matchExpressions:
        - key: region
          operator: In
          values:
            - beijing
  tolerations: # 通过污点过滤集群
    - key: "key1"
      operator: "Equal"
      value: "value1"
      effect: "NoSchedule"
  schedulingMode: Divide # 是否为副本数调度
  stickyCluster: false # 仅在首次调度,适合有状态服务或作业类服务
  maxClusters: 1 # 最多可分发到多少个子集群,适合有状态服务或作业类服务
  disableFollowerScheduling: false # 是否开启依赖调度
  • 调度能力可拓展 KubeAdmiral:参考 kube-scheduler 的设计,提供了可拓展的调度框架,将调度逻辑抽象成 Filter、Score、Select 和 Replica 四个步骤,并由多个相对独立的插件各自实现其在每个步骤的逻辑。
  • 应用调度失败自动迁移: 对于副本调度的资源,KubeAdmiral 会计算出每个成员集群应得几个副本,并将副本数字段覆盖后下发到各成员集群,这一过程称为联邦调度;资源下发后,各成员集群的 kube-scheduler 又会把资源对应的 pod 分配给相应的 node,这一过程成为单集群调度。
  • 根据集群水位动态调度资源:在多集群环境中,各集群的资源水位因机器上下线而动态变化,仅依靠 KubeFed RSP 提供的静态权重调度副本容易造成集群水位不均的情况,部署率过高的集群在服务升级过程中容易出现 pod 长时间 pending,而部署率过低的集群资源无法完全利用。对此,KubeAdmiral 引入了基于集群水位的动态权重调度,通过收集每个集群的资源总量与使用量计算出可用量,并将可用资源量作为副本调度的权重,最终达到各个 member 集群负载均衡。
  • 副本分配算法改进:KubeFed 的副本算法首先在集群中预分配当前存在的实例数,然后再将剩余的实例按照各个集群的权重分配,如果当前集群中存在的副本数过多,就会导致实例分布与权重严重偏离。KubeAdmiral 对 KubeFed 的副本算法进行了优化,在保证扩缩容时不产生非预期迁移的情况下,使最终分发尽量趋近于权重分布。
  • 支持原生资源:为了解决这个问题,无缝支持原生资源,KubeAdmiral 提供了 status 汇聚的能力,Status Aggregator 将多个成员集群中资源的 status 进行合并与融合,并写回原生资源,让用户无需感知多集群拓扑,就可以一目了然地观测到资源在整个联邦中的状态。

kelemetry

Kelemetry 是字节跳动开发的用于 Kubernetes 控制平面的追踪系统,它从全局视角串联起多个 Kubernetes 组件的行为,追踪单个 Kubernetes 对象的完整生命周期以及不同对象之间的相互影响。

通过可视化 K8s 系统内的事件链路,它使得 Kubernetes 系统更容易观测、更容易理解、更容易 Debug

项目地址 | github.com/kubewharf/kelemetry

解决的问题

在传统的分布式追踪中,“追踪”通常对应于用户请求期间的内部调用。与传统的 RPC 系统相反,Kubernetes API 是异步和声明式的。为了执行操作,组件会更新 apiserver 上对象的规范(期望状态),然后其他组件会不断尝试自我纠正以达到期望的状态。

例如,当我们将 ReplicaSet 从 3 个副本扩展到 5 个副本时,我们会将 spec.replicas 字段更新为 5,rs controller 会观察到此更改,并不断创建新的 pod 对象,直到总数达到 5 个。当 kubelet 观察到其管理的节点创建了一个 pod 时,它会在其节点上生成与 pod 中的规范匹配的容器。

在此过程中,我们从未直接调用过 rs controller,rs controller 也从未直接调用过 kubelet。这意味着我们无法观察到组件之间的直接因果关系。如果在过程中删除了原始的 3 个 pod 中的一个,副本集控制器将与两个新的 pod 一起创建一个不同的 pod,我们无法将此创建与 ReplicaSet 的扩展或 pod 的删除关联起来。

因此,由于“追踪”或“跨度”的定义模糊不清,传统的基于跨度的分布式追踪模型在 Kubernetes 中几乎不适用。为解决可观察性数据孤岛的问题,Kelemetry 以组件无关、非侵入性的方式,收集并连接来自不同组件的信号,并以追踪的形式展示相关数据。

kelemetry 的特点介绍

将对象作为跨度

为了连接不同组件的可观察性数据,Kelemetry 采用了一种不同的方法,受 kspan 项目的启发,与将单个操作作为根跨度的尝试不同,这里为对象本身创建一个跨度,而每个在对象上发生的事件都是一个子跨度。此外,各个对象通过它们的拥有关系连接在一起,使得子对象的跨度成为父对象的子跨度。

基于此,我们得到了两个维度:树形层次结构表示对象层次结构和事件范围,而时间线表示事件顺序,通常与因果关系一致。

审计日志收集

Kelemetry 的主要数据源之一是 apiserver 的审计日志。审计日志提供了关于每个控制器操作的丰富信息,包括发起操作的客户端、涉及的对象、从接收请求到完成的准确持续时间等。在 Kubernetes 架构中,每个对象的更改会触发其相关的控制器进行协调,并导致后续对象的更改,因此观察与对象更改相关的审计日志有助于理解一系列事件中控制器之间的交互。

Kubernetes apiserver 的审计日志以两种不同的方式暴露:日志文件webhook。一些云提供商实现了自己的审计日志收集方式,而在社区中配置审计日志收集的与厂商无关的方法进展甚微。为了简化自助提供的集群的部署过程,Kelemetry 提供了一个审计 webhook,用于接收原生的审计信息,也暴露了插件 API 以实现从特定厂商的消息队列中消费审计日志。

Event 收集

为了避免重复事件,Kelemetry 使用了几种启发式方法来“猜测”是否应将 event 报告为一个跨度:

  • 持久化处理的最后一个 event 的时间戳,并在重启后忽略该时间戳之前的事件。虽然事件的接收顺序不一定有保证(由于客户端时钟偏差、控制器 — apiserver — etcd 往返的不一致延迟等原因),但这种延迟相对较小,可以消除由于控制器重启导致的大多数重复。
  • 验证 event 的 resourceVersion 是否发生了变化,避免由于重列导致的重复 event。

将对象状态与审计日志关联

在研究审计日志进行故障排除时,我们最想知道的是“此请求改变了什么”,而不是“谁发起了此请求”,尤其是当各个组件的语义不清楚时。Kelemetry 运行一个控制器来监视对象的创建、更新和删除事件,并在接收到审计事件时将其与审计跨度关联起来。当 Kubernetes 对象被更新时,它的 resourceVersion 字段会更新为一个新的唯一值。这个值可以用来关联更新对应的审计日志。Kelemetry 把对象每个 resourceVersion 的 diff 和快照缓存在分布式 KV 存储中,以便稍后从审计消费者中链接,从而使每个审计日志跨度包含控制器更改的字段。

前端追踪转换

为了提高用户体验,Kelemetry 拦截在 Jaeger 查询前端和存储后端之间,将存储后端结果返回给查询前端之前,对存储后端结果执行自定义转换流水线。

Kelemetry 目前支持 4 种转换流水线:

  • tree:服务名/操作名等字段名简化后的原始 trace 树

  • timeline:修剪所有嵌套的伪跨度,将所有事件跨度放在根跨度下,有效地提供审计日志

  • tracing:非对象跨度被展平为相关对象的跨度日志

  • 分组:在追踪管道输出之上,为每个数据源(审计/事件)创建一个新的伪跨度。当多个组件将它们的跨度发送到 Kelemetry 时,组件所有者可以专注于自己组件的日志并轻松地交叉检查其他组件的日志。

用户可以在追踪搜索时通过设置“service name“来选择转换流水线。中间存储插件为每个追踪搜索结果生成一个新的“CacheID”,并将其与实际 TraceID 和转换管道一起存储到缓存 KV 中。当用户查看时,他们传递 CacheID,CacheID 由中间存储插件转换为实际 TraceID,并执行与 CacheID 关联的转换管道。

picture.image

简单使用

在线预览kubewharf.io/kelemetry/trace-deployment/

picture.image

从追踪可见几个关键点:

  • Replicaset-controller 发出 SuccessfulCreate 事件,表示 Pod 创建请求成功返回,并在 replicaset reconcile 中得到了 replicaset controller 的确认。

  • 没有 replicaset 状态更新事件,这意味着 replicaset controller 中的 Pod reconcile 未能更新 replicaset 状态或未观察到这些 Pod。

  • 查看其中一个 Pod 的追踪后 Replicaset controller 在 Pod 创建后再也没有与该 Pod 进行交互,甚至没有失败的更新请求。

因此,可以得出结论,replicaset controller 中的 Pod 缓存很可能与 apiserver 上的实际 Pod 存储不一致,我们应该考虑 pod informer 的性能或一致性问题。如果没有 Kelemetry,定位此问题将涉及查看多个 apiserver 实例的各个 Pod 的审计日志。

总结

本文对 KubeWharf 的第二批开源项目做了介绍,云原生不仅仅是某一项或某几项技术,更是一种理念和引导,促进企业在上云的过程中使用相关技术来更好的发挥云计算的优势。

在云原生技术发展的过程中,Docker 技术的出现、CNCF 的成立、Kubernetes 成为容器编排的事实标准等事件都具有极其重要的意义,最终使得云原生向着更加标准化的方向发展。正是社区和开源的力量把全球开发者凝聚在一起共同推进云原生技术的进步,使其欣欣向荣的向前发展。

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论