关于云原生:深入探索云原生流水线的架构设计

3次阅读

共计 4305 个字符,预计需要花费 11 分钟才能阅读完成。

目前,市面上的流水线 / 工作流产品层出不穷,有没有一款工作流引擎,可能同时满足:

  • 反对各种工作运行时,包含 K8s Job、K8s Flink、K8s Spark、DC/OS Job、Docker、InMemory 等?
  • 反对疾速对接其余工作运行时?
  • 反对工作逻辑形象,并且疾速地开发本人的 Action?
  • 反对嵌套流水线,在流水线层面进行逻辑复用?
  • 反对灵便的上下文参数传递,有好用的 UI 以及简略明确的工作流定义?
  • ······

那么,无妨试试 Erda Pipeline 吧~

Erda Pipeline 是一款自研、用 Go 编写的工作流引擎。作为根底服务,它在 Erda 外部撑持了许多产品:

  • CI/CD
  • 快数据平台
  • 自动化测试平台
  • SRE 运维链路
  • ……

Erda Pipeline 之所以抉择自研,其中最重要的三点是:

  1. 自研能更快地响应业务需要,进行定制化开发;
  2. 时至今日,开源社区还没有一个本质上的流水线规范,各种产品百花齐放;
  3. K8s、DC/OS 等的 Job 实现都偏弱、上下文传递缺失,无奈满足咱们的需要,更不用说灵便好用的 Flow 了,例如:嵌套流水线。

本文咱们将次要从架构层面对 Pipeline 进行分析,和大家一起来深刻摸索 Pipeline 的架构设计。次要内容包含以下几个局部:

  • 整体架构
  • 外部架构
  • 程度扩大
  • 分布式架构
  • 性能个性
  • 实现细节

整体架构

Pipeline 反对灵便的应用形式,目前反对 UI 可视化操作、OPENAPI 凋谢接口、CLI 命令行工具几种形式。

协定层面,在 Erda-Infra 微服务框架的加持下,以 HTTP 和 gRPC 模式对外提供服务:在晚期的时候,咱们只提供了 HTTP 服务,因为 Erda 平台自身外部是微服务架构,服务间调用就须要手动编写 HTTP 客户端,不好主动生成,麻烦且容易出错。起初咱们改为应用 Protobuf 作为 IDL(Interface Define Language),在 Erda-Infra 中主动生成 gRPC 的客户端调用代码和服务端框架代码,外部服务间的调用都改为应用 gRPC 调用。

在中间件依赖层面,咱们应用 ETCD 做分布式协调,用 MySQL 做数据长久化。ETCD 咱们也有打算把它替换掉,应用 MySQL 来做分布式协调。

最要害的工作运行时(Task Runtime)层面,咱们反对工作能够运行在 K8s、DC/OS(分布式云 OS,在 2017-2019 年十分火)、用户本地 Docker 环境等。

咱们还有凋谢的工作扩大市场,在平台级别内置了十分多的流水线工作扩大,开箱即用。同时,用户也能够开发企业 / 我的项目 / 利用 / 集体级别的工作扩大,这部分性能在代码层面曾经齐全反对,产品层面正在开发中,在后续迭代中很快就能够和大家见面。

外部架构

应用 Erda-Infra 微服务框架开发,功能模块划分清晰:

  • Server 层包含业务逻辑,对 Pipeline 来说业务就是创立、执行、重试、重试失败节点等。
  • Modules 层提供不含业务逻辑的公共模块,其余两层均可调用,包含预校验机制、定时守护过程、YAML 解析器、配置管理、事件治理等。

Erda Pipeline 始终在践行:“把简单留给本人,把简略留给他人”。

在 Pipeline 中,咱们对一个工作执行的形象是 ActionExecutor。

  • Engine 层负责流水线的推动,包含:

    • Queue Manager 队列管理器,反对队列内工作流的优先级动静调整、资源查看、依赖查看等
    • Dispatcher 工作散发器,用于将满足出队条件的流水线分发给适合的 Worker 进行推动
    • Reconciler 协调器,负责将一条残缺的流水线解析为 DAG 构造后进行推动,直至终态。
  • 模块外部应用插件机制,对接各种工作运行时。
  • AOP 扩大点机制(借鉴 Spring),把代码要害节点进行裸露,不便开发同学在不批改外围代码的前提下定制流水线行为。

    • 目前曾经有的一些扩大点插件,譬如自动化测试报告嵌套生成、队列弹出前查看、接口测试主动登录放弃等。
    • 这个能力后续咱们还会凋谢给调用方,包含用户,去做一些有意思的事件。
  • 对立应用 Event,封装了 WebHook / WebSocket / Metrics。

程度扩大

随着 Pipeline 须要撑持的业务场景和数据量一劳永逸,单实例的 Pipeline 稳定性和推动性能面临着挑战。

Pipeline 的多实例计划如下:

  • Leader & Worker 模式,两者在部署上不辨别状态,仅为 Replicas 多实例:

    • 应用 ETCD 选举,每个实例都能够是 Leader。
    • 只有 Leader 开启 Queue Manager 和 Dispatcher,散发工作给 Worker。
    • Leader 自身也能够作为 Worker,反对单节点部署模式,Leader 必须开启 Reconciler。
  • Dispatch 应用有界负载的一致性哈希算法:

    • 应用一致性哈希来分配任务。但一致性哈希会超载,例如某些热点内容会继续打到一个节点上。
    • 有界负载算法(The bounded-load algorithm):

      1. 只有服务器未过载,申请的调配策略与一致性哈希雷同。
      2. 过载服务器的溢出负载将在可用服务器之间调配。
    • Pipeline 实例增减时,曾经被调配的流水线不重新分配,尽可能减少切换老本,避免反复推动;新增的流水线应用一致性 Hash 算法进行调配。

在之前,咱们应用过分布式锁的计划来做多实例协调。和选举比起来,竞争会更大,在具体实现里,如果想要多实例同时推动流水线,那竞争的资源就是流水线 ID。每个 ID 会申请一个分布式锁,而且这个分布式锁是阻塞性的,保障每个流水线 ID 肯定会被推动,人造做了多节点重试。

分布式架构

该分布式架构是典型的 AP 模型,数据层面遵循最终一致性。

这套分布式架构的外围指标(典型场景)是在网络分区的状况下,保障边缘集群的定时工作失常执行。

咱们来比照一下 原有部署架构(运行时以 K8s 为例):

  • 核心 Pipeline 间接负责流程推动,调用边缘 K8s 创立 Job。
  • 当网络分区时,原有部署架构下,定时工作无奈失常执行。

分布式架构

  • 核心下发工作定义,由 Edge Pipeline 负责推动,直连 K8s,更加稳固。
  • 在网络分区复原时,被动上报执行数据,实现数据最终一致性。

在代码层面,咱们应用同一份代码构建出同一个镜像,通过配置(不同的部署模式)使得各个实例各司其职。

另外,在数据同步时,咱们遇到了前端 JavaScript Number 类型 53 位最大值问题,对 SnowFlake ID 进行了 64bit -> 53bit 的革新,在保障唯一性的前提下,尽可能地减少可用性(生命周期约为 10 年,同时反对 4096 个分布式节点,每 10ms 可生成 64 个 ID)。

性能个性

这里简略列举一些比拟常见的性能个性:

  • 配置即代码
  • 扩大市场丰盛
  • 可视化编辑
  • 反对嵌套流水线
  • 灵便的执行策略,反对 OnPush / OnMerge 等触发策略
  • 反对工作流优先队列
  • 多维度的重试机制
  • 定时流水线及定时弥补性能
  • 动静配置,反对“值”和“文件”两种类型,均反对加密存储,确保数据安全性
  • 上下文传递,后置工作能够援用前置工作的“值”和“文件”
  • 凋谢的 OpenAPI 接口,不便第三方零碎疾速接入

一些实现细节

如何实现上下文传递(值援用)

在一条流水线中,节点间除了有依赖程序之外,肯定会有数据传递的需要。

值援用:

  • 每个节点的非凡输入 (按格局写入指定文件或者打印到规范输入) 会被保留在 Pipeline 数据库中;
  • 后续节点通过 outputs 语法申明的表达式会在节点开始执行前被替换为真正的值。

举例:

如图所示:第一个节点 repo 拉取代码;第二个节点 build erda 则是构建 Erda 我的项目。

在例子中,第二步构建时同时用到了“值援用”和“文件援用”两种援用类型,是进顺次入代码仓库,指定 GIT_COMMIT 进行构建。

如何实现上下文传递(文件援用)

  • 文件援用比值援用简单,因为文件的数据量比值大得多,不能存储在数据库中,而是存储在卷中。
  • 这里又依据是否应用共享存储而分为两种状况,两者的区别在于申请的卷的类型和个数。
  • 对于流水线使用者而言,没有任何区别。


应用共享存储


不应用共享存储

如何实现缓存减速

在许多流水线场景中,同一条流水线的屡次执行之间是有关联的。如果可能用到上一次的执行后果,则能够大幅缩短执行工夫。

典型场景是 CI/CD 构建,咱们以 Java 利用 Maven 构建举例:岂但同一条流水线不同的屡次执行能够复用 ${HOME}/.m2 目录(缓存目录),甚至同一个利用下的多个分支之间都能够应用同一个缓存目录,就像本地构建一样~

举例:

依然应用后面的例子,在第二步 build erda 里加上 cache 即可。

Action Agent

在 Pipeline 里,每个节点都会对应一个 Action 类型,并且在扩大市场中都能够找到。为了更加不便 Action 开发者进行开发,咱们提供了很多便捷的机制。如:

  • 对敏感日志进行脱敏解决,保障数据安全
  • 无感知的谬误剖析和数据上报
  • 文件变动监听及实时上报
  • ……

上述所有机制都是由 Action Agent 程序实现的,它是一个动态编译的 Go 程序,能够运行在任意 Action 镜像中。Agent 残缺的执行链路如下:

Action Executor 扩大机制

Pipeline 之所以好用,是因为它提供了灵便统一的流程编排能力,并且能够很不便地对接其余单任务执行平台,这个平台自身不须要有流程编排的能力。

在 Pipeline 中,咱们对一个工作执行的形象是 ActionExecutor。一个执行器只有实现单个工作的创立、启动、更新、状态查问、删除等根底办法,就能够注册成为一个 ActionExecutor:

  • 失当的工作执行器形象,使得 Batch/Streaming/InMemory Job 的配置形式和应用形式完全一致,流批一体,对使用者屏蔽底层细节,做到无感知切换。
  • 在同一条流水线中,能够混用各种 ActionExecutor。

调度时,依据工作类型智能调度到对应的工作执行器上,包含 K8sJob、Metronome Job、Flink Job、Spark Job 等等。


Go 接口定义

AOP 扩大点机制

AOP 扩大点机制是借鉴 Java 里 Spring 的概念应运而生的。

咱们把代码要害节点进行裸露,不便开发同学在不批改外围代码的前提下定制流水线行为。AOP 扩大点机制曾经应用 Erda Infra 的模块化思维重构,整个扩大点的插件开发和编排更为灵便,如下图所示:


AOP 在 Pipeline 外部的应用

这个能力后续咱们还会凋谢给用户,让用户能够在 pipeline.yaml 中应用编程语言申明和编排扩大点插件,更灵便地管制流水线行为。


咱们致力于决社区用户在理论生产环境中反馈的问题和需要,
如果您有任何疑难或倡议,
欢送关注 【尔达 Erda】公众号 给咱们留言,
退出 Erda 用户群参加交换或在 Github 上与咱们探讨!

正文完
 0