kruntimes PRD

背景: 基于Kubernetes的Serverless平台的挑战

在 Kubernetes 上构建 Serverless 平台是一个具有挑战性的任务,主要原因是 Kubernetes 的调度和资源管理机制并不完全适合 Serverless 工作负载的特点。Serverless 工作负载通常具有短暂的生命周期、快速的启动时间和动态的资源需求,这些特点与 Kubernetes 的设计理念存在一定的冲突。例如,Kubernetes 的调度器通常需要几秒钟的时间来调度一个 Pod,而 Serverless 工作负载可能需要在毫秒级别内启动和执行。此外,Kubernetes 的资源管理机制也可能导致资源分配不均衡,影响 Serverless 工作负载的性能和效率

  • 性能与开销的矛盾 (Pod 冷启动):

    这是最核心的痛点。Serverless 要求毫秒级响应,但原生 Pod 的启动是分钟级流程,瓶颈在于镜像拉取(几个GB的镜像拉取是大头)和调度与容器初始化(调度、分配IP、挂载卷等)。虽然 Firecracker 这类安全沙箱能在百毫秒内启动微虚拟机,但把它和需要懒加载技术的容器镜像结合,与标准 K8s 接口不兼容,需要大量非标改造,一般平台团队不愿碰。

  • 调度效率的矛盾

    K8s 调度器是为长期运行的服务设计的,采用“尽力而为”的模型。用它调度海量、短命、高并发的 Serverless 任务,会导致调度吞吐量不足、决策慢。批处理调度器(如 Volcano)适合大数据任务,但对函数这类更细粒度的任务,缺乏感知和优化能力。

  • 弹性速度的矛盾

    原生 HPA 通过监控指标扩缩容,反应滞后,跟不上流量的瞬间尖峰。驱动层弹性(如 KEDA)更敏捷,但底层 Pod 启动慢,导致“决策快、执行慢”的脱节。最终需要靠“冷启动优化”或“预留热池”来兜底,这都是应用层对平台缺陷的妥协。

  • 组织与康威定律的矛盾

    这是最现实的难点, Serverless 团队往往独立于K8s基础设施团队, 无法有效地推动 K8s 团队做定制优化。组织架构的墙,让技术上本可以解决的难题(如懒加载镜像、定制调度器)变得不可行,只能另辟蹊径。

方案: 基于 K8s 构建两层调度的 Runtime 方案

  • 绕过组织障碍,实现自主可控

    K8s 团队只提供最基础的 Pod 管理能力,我们把它当成新的IaaS层。所有的优化逻辑,全部上移到由 Serverless 团队完全掌控的应用层。我们的调度器、CRD、Daemon 都部署在自己的 Namespace 里,完全不强依赖 K8s 团队的配合。

  • 复用代替“创建”,解决冷启动

    这是方案的精髓。用预先创建好的热 Pod池,代替按需创建新 Pod。 任务到达时,调度器直接将其分发给热 Pod 里早已就绪的 Daemon 进程,用户感知的启动时间从分钟级降到秒级甚至毫秒级。这完美规避了 Pod 冷启动中最慢的调度和镜像拉取过程。

  • 两级调度实现关注点分离

    第一层是 K8s 的宏观调度,负责维持热 Pod 池的存在,性能要求不高。第二层是我们精细的应用调度,在热 Pod 内部复用计算资源,处理高并发的短任务。这让我们可以在应用层自由实现排队、优先级、资源分配等逻辑,完全匹配 CI/CD 任务的场景。

  • Runtime 抽象解决环境一致性

    我们把不同技术栈的执行环境(Go、Node.js、BuildKit)封装为不同的“Runtime”,每种 Runtime 就是一个独立的 Deployment 池。这有三大好处:天然隔离环境依赖,避免“依赖地狱”;保证环境一致性,所有任务跑在相同的预构建镜像里;具有极好的可扩展性,只需部署新池并注册标签即可支持新语言。

  • 声明式 CRD解决分布式系统复杂性

    我们没有采用复杂的 P2P 通信,而是引入了 Run CRD。这是整个方案的基石。它让任务用什么环境、做什么、谁来做、结果如何都变成了集群里的标准资源对象。

    • Scheduler 变简单了:它不再需要维护 Daemon 地址和发指令,只需 Watch 任务,给它打上 Scheduled 标签和分配信息。
    • Daemon 变简单了:它只需 Watch 分配给自己的任务,拉取后执行,再更新状态。
    • Failover 变简单了:Daemon 挂了,挂在它名下的任务因超时被重新标记为 Pending,由 Scheduler 重新分配。所有状态都持久化在 etcd 中,健壮性极高。

总而言之,这个方案是我们面对不完美的 K8s 平台,以一个开发者的姿态,利用 K8s 原生能力构建的一个应用层 Serverless 框架。它专门为短任务优化,自主可控,复杂度可控,为未来向环境即函数等理想架构演进铺平了道路。

用户体验

kruntimes 是一个运行在Kubernetes之上的双层调度系统,通过保持预热运行时(Runtime pod) 在毫秒内准备好执行代码,消除冷启动延迟。我们的目标是构建一个简单但功能强大的serverless基础设施, 管理员无需深度优化/改造kubernetes集群就能够容易的部署和运行kruntimes, 用户也能够通过简单的接口在kruntimes上运行他们的代码, 同时kruntimes也提供了丰富的功能来满足不同用户的需求, 例如支持多种编程语言的运行时环境, 支持自定义Runtime Server来扩展更多的执行环境和调度策略, 支持丰富的资源管理功能等.

核心的用户体验

  1. 用户接口
    • 通过Kubernetes API进行交互,用户可以使用kubectl命令行工具创建和管理Run CRD对象来执行代码。
    • 通过SDK(python, golang等)在kruntimes上运行代码。
  2. 运行时环境
    • 内建的运行时环境支持多种编程语言,包括Bash、Python、Go、Node.js、WASM等, 对于这些Runtime, Run的调度和执行默认由kruntimes的双层调度系统管理.
    • 同时支持通过自定义Runtime Server扩展支持更多语言和执行环境, 并且能够支持自定义调度, 也就是说调度交给Runtime Server来管理, 这对于一些特殊的执行环境或者需要特殊调度策略的场景非常有用, 例如Slurm, Ray, Spark等.(对于这种case, kruntimes提供Slurm和Ray两种Runtime Server的实现, 用户也可以自己实现其他的Runtime Server来支持更多的执行环境)
  3. 资源管理
    • Runtime 默认的资源包括CPU、内存、存储, 用户也可以在 Runtime CRD spec 中增加更多的资源类型, 例如GPU, FPGA等, 以及一些特殊的资源例如网络带宽, 存储IOPS等.
    • 通过在Run CRD spec中指定资源需求, 用户可以确保他们的代码在满足资源需求的环境中运行, 从而提高性能和效率.
    • kruntimes的调度系统会根据资源需求和可用资源来调度Run到合适的Runtime pod上执行, 从而实现高效的资源利用和性能优化.
    • 对于每个Run的资源隔离由Runtime server的本地调度器来管理, 例如在Runtime内部使用Cgroup对Run进行资源隔离
  4. 应用层
    • 我们在未来会提供一些应用层的功能, 例如定时任务(CronRun), 工作流编排(Pipeline)等, 这些功能将建立在核心的用户接口和运行时环境之上, 为用户提供更丰富的功能和更好的用户体验.