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