作者|林俊(万念)

来源|尔达 Erda 公众号

Erda Pipeline 是端点自研、用 Go 编写的一款企业级流水线服务。截至目前,已经为众多行业头部客户提供交付和稳定的服务。

为什么我们坚持自研,而不用 jenkins 等产品呢?在当时,至少有以下几点理由:

  • 时至今日,开源社区仍没有一个事实上的流水线标准
  • K8s、DC/OS 等的 Job 实现都偏弱,上下文传递等缺失,不满足我们的需求,更不要说 Flow 了
  • 自研能更快地响应业务需求,进行定制化开发

作为基础服务,Pipeline 在 Erda 内部支撑了 CI/CD、快数据平台、自动化测试平台、SRE 运维链路等产品化场景。本文就从几个方面来介绍一下 Pipeline。

为什么会有 Pipeline

这就需要从应用构建开始说起。Pipeline 的前身是 Packer 和 CI。

Packer

Erda 最开始是端点内部使用的 PaaS 平台。从 2017 年开始,Erda 就管理了公司所有的研发项目。项目下每个应用都逃不开 代码 -> 编译 -> 镜像制作 -> 部署 的标准流程。这个时候我们开发了 Packer,顾名思义,它是一个专门负责 打包 的组件。用户需要提供 Dockerfile,这在当时还是有着较高学习成本的。

CI

随着 CI/CD(持续集成、持续交付)概念的深入人心,我们也推出了 Packer 的升级版 CI 。同时,基础设施即代码(IaC)的理念也在这里得到了实践:通过 erda.yaml 1.0 语法同时声明应用的微服务架构和构建过程。

在用户体验上,我们不再直接暴露 Dockerfile,而是把最佳实践以 BuildPack 大礼包的方式给到使用者,使用者甚至不需要声明应用的开发语言和构建方式,就可以通过 BuildPack 的自动探测和识别,完成 CI/CD 流程。

受限于单容器的运行方式,当时我们也遇到了一些问题,譬如把 CI 构建过程自定义能力开放、构建环境多版本问题等,这些问题在 Pipeline 里都迎刃而解。

Pipeline

今天回过头来看,从 CI 升级到 Pipeline 是一个很自然的过程:因为 CI/CD 本身就是一个很标准的流程,我们完全可以抽象出一个更通用的流程引擎,这就是 Pipeline。CI/CD 成为了 Pipeline 最开始支撑的场景。

在设计之初,我们就做了以下改进:

  • 对外:通过清晰易用的 pipeline.yaml 语法,降低使用者的上手成本。
  • 对内:抽象出任务定义,配合 ActionExecutor Plugin Mechenism(任务执行器插件机制),很方便地对接各个单任务执行平台,譬如 DC/OS Metronome、K8s Job、Flink/Spark Job 等。
  • 由 Pipeline 提供一致、强大的流程编排能力。

Pipeline 功能特性

Pipeline 有许多灵活、强大的功能,譬如:

  • 配置即代码,通过 pipeline.yaml 语法描述流程,基于 Stage 语法简化编排复杂度。
  • 丰富的扩展市场,平台内置超过百款开箱即用的 Action,满足大部分日常场景;同时可轻松扩展你自己的 Action。
  • 可视化编辑,通过图形界面交互快速配置流水线。
  • 支持嵌套流水线,在流水线级别进行复用,组合出更强大的流水线。
  • 灵活的执行策略,包括串并行、循环、分支策略、超时、人工确认等。
  • 支持工作流优先队列,优先级可实时调整,保证高优先级流水线优先执行。
  • 多维度的重试机制,支持断点重试、全流程重试。
  • 定时流水线,同时提供强大的定时补偿功能。
  • 动态配置,支持 文件 两种类型,均支持加密存储,确保数据安全性。
  • 上下文传递,后置任务可以引用前置任务的 文件
  • 开放的 OpenAPI 接口,方便第三方系统快速接入。
  • ······

Pipeline 架构

如上图所示,Pipeline 支持 UI / OPENAPI / CLI 多种方式进行交互。

Pipeline 本身支持水平扩展,保证高可用,还可以将其划分为:服务层、核心层和引擎层。下面我们详细介绍一下。

服务层

  • yaml parser 解析流程定义文件,支持灵活的变量语法。例如上下文值引用:${{ outputs.preTaskName.key }};配置管理引用:${{ configs.key }} 等。
  • 对接扩展市场获取扩展能力。

核心层

  • Cron 守护进程。
  • EventManager 抽象内部事件发送,使用适配器模式解耦监控指标上报、发送 ws 消息、支持 webhook 等。
  • AOP 扩展点机制(借鉴 Spring),把代码关键节点进行暴露,方便开发同学在不修改核心代码的前提下定制流水线行为。这个能力后续我们还会开放给调用方,包括用户,支持他们去做一些有意思的事情。

目前许多有意思的功能都是通过扩展点机制实现的,譬如自动化测试报告嵌套生成、队列弹出前检查、接口测试 Cookie 保持等:

引擎层

引擎层包括:

  • 流程推进器(Reconciler)
  • 优先队列管理器
  • 任务执行器插件机制

具体内容在下一节会展开讲解。

中间件依赖

我们尽可能做到简化中间件依赖,使部署更简单。

  • 使用 MySQL 做数据持久化。
  • 使用 etcd watch 功能实现多实例状态同步以及分布式锁。
  • 使用 etcd key ttl 实现数据 defer GC。

流水线是如何被推进的

在引擎侧,pipeline.yaml 被解析为 DAG(Directed Acyclic Graph,有向无环图) 结构后被推进。

换句话说,引擎并不认识、也不关心 pipeline.yaml 语法,用户侧完全可以提供多种多样的语法方便不同用户使用,只需要最终能被转换成 Pipeline 简单封装过的 DAG 结构

Pipeline 级别由推进器 Reconciler 根据 DAG 计算出当前可被推进的任务,每个任务异步去执行推进逻辑。

任务的推进由 TaskFramework 处理,其中抽象出 prepare -> create -> start -> queue -> wait 标准步骤。当有需要时也可以很方便地进行标准扩展。

当任意一个任务推进完毕时,会再次递归调用 reconcile 方法去重复上述流程,直到流程整体执行完毕。

Reconciler 中 通过 DAG 计算可调度任务代码如下

// getSchedulableTasks return the list of schedulable tasks.
// tasks in list can be schedule concurrently.
func (r *Reconciler) getSchedulableTasks(p *spec.Pipeline, tasks []*spec.PipelineTask) ([]*spec.PipelineTask, error) { // construct DAG
dagNodes := make([]dag.NamedNode, 0, len(tasks))
for _, task := range tasks {
dagNodes = append(dagNodes, task)
}
_dag, err := dag.New(dagNodes,
// pipeline DAG 中目前可以禁用任意节点,即 dag.WithAllowMarkArbitraryNodesAsDone=true
dag.WithAllowMarkArbitraryNodesAsDone(true),
)
if err != nil {
return nil, err
} // calculate schedulable nodes according to dag and current done tasks
schedulableNodeFromDAG, err := _dag.GetSchedulable((&spec.PipelineWithTasks{Tasks: tasks}).DoneTasks()...)
if err != nil {
return nil, err
}
......
}

ActionExecutor 插件机制

把复杂留给自己,把简单留给别人。

在前文我们说到:由流水线提供灵活、一致的流程编排能力。它的前提是单个任务的执行已经被很好的抽象了。

在 Pipeline 中,我们对一个任务执行的抽象是 ActionExecutor:

type ActionExecutor interface {
Kind() Kind
Name() Name Create(ctx context.Context, action *spec.PipelineTask) (interface{}, error)
Start(ctx context.Context, action *spec.PipelineTask) (interface{}, error)
Update(ctx context.Context, action *spec.PipelineTask) (interface{}, error) Exist(ctx context.Context, action *spec.PipelineTask) (created bool, started bool, err error)
Status(ctx context.Context, action *spec.PipelineTask) (apistructs.PipelineStatusDesc, error)
// Optional
Inspect(ctx context.Context, action *spec.PipelineTask) (apistructs.TaskInspect, error) Cancel(ctx context.Context, action *spec.PipelineTask) (interface{}, error)
Remove(ctx context.Context, action *spec.PipelineTask) (interface{}, error)
}

因此,一个执行器只要实现 单个任务创建、启动、更新、状态查询、删除 等基础方法,就可以注册成为一个 ActionExecutor。

恰当的任务执行器抽象,使得 Batch/Streaming/InMemory Job 的配置和使用方式完全一致,批流一体,对使用者屏蔽底层细节,做到无感知切换。在同一条流水线中,可以混用各种 ActionExecutor。

调度时,Pipeline 根据任务类型和集群信息,将任务调度到对应的任务执行器上。

目前我们已经拥有许多的 ActionExecutor:

插件化的开发机制,使我们在未来对接其他任务引擎也变得非常简单,例如对接 Jenkins 成为一个 ActionExecutor。

这里举一个真实的例子:在自动化测试平台里,之前每一个 API 都会启动一个容器去执行,而容器的启停最快也需要数秒,这和 API 接口正常毫秒级的耗时比起来,慢了几个数量级。得益于 ActionExecutor 插件机制,我们快速开发了基于内存的 API-Test 任务执行器,很快就解决了这个问题,使用者不需要做任何调整,节省了很多时间成本。

更友好的用户接入层 pipeline.yaml

pipeline.yaml 是 IaC 的一个实践,我们通过 YAML 格式描述流水线定义,基于 Stage 语法简化编排复杂度。

一个简单的示例如下所示:

version: 1.1

cron: 0 */10 * * * ?

# stage 表示 阶段,多个 stage 串行成为 stages
stages: # 一个 stage 内包含多个 并行 的 Action
- stage:
- git-checkout: # Action 类型
params:
depth: 1
- stage:
- buildpack:
alias: backend
params:
context: ${{ dirs.git-checkout }}
resources:
cpu: 0.5
mem: 2048
- custom-script:
image: centos:7
commands: # 支持直接执行命令
- sleep 5
- echo hello world
- cat ${{ dirs.git-checkout }}/erda.yml # 这里通过 ${{ dirs.git-checkout }} 语法来引用文件

以 Pipeline 为技术底座

目前,以 Pipeline 作为技术底座,向上支撑了:

  • DevOps CI/CD 场景,包括 Erda 自身的持续集成和 Release 版本发布。
  • 快数据平台:工作流编排,批流一体,支持工作流优先级队列,保证高优先级数据任务必须执行。至今已为多家世界 500 强企业和头部客户提供稳定服务。
  • 自动化测试平台:测试流程编排,API(出参、断言)、数据银行等不同类型的任务统一编排。
  • SRE 集群运维链路。
  • 提供无限扩展:基于 ActionExecutor 扩展机制和扩展市场。

开源架构升级

目前,Pipeline 所有代码均已完成开源。我们正在进行的重构工作包括:

  • 使用 Erda-Infra 微服务架构重新梳理功能模块
  • Pipeline 平台支持独立部署,UI 自动适配
  • 通过 ActionExecutor 插件机制支持使用者本地 Agent,充分利用本地资源
  • 在 GitHub 上推出 Erda Cloud Pipeline App,提供免费的 CI 能力

结束语

最后,我们欢迎有更多的同学来使用流水线,不论是代码级的使用,还是通过 Erda Cloud 来体验我们的服务。

欢迎 GitHub 提交 Issue 和 PR !

• Erda Github 地址:https://github.com/erda-project/erda

• Erda Cloud 官网:https://www.erda.cloud/

面向多场景而设计的 Erda Pipeline的更多相关文章

  1. loadrunner中面向目标场景的设计

    在一个面向目标的方案中,可以定义五种类型的目标:虚拟用户数.每秒点击次数(仅 Web Vuser).每秒事务数.每分钟页面数(仅 Web Vuser)或方案的事务响应时间.使用“编辑方案目标”对话框可 ...

  2. vue移动端金融UI组件库滴滴MandMobile面向金融场景设计附功能思维导图

    vue移动端金融UI组件库滴滴MandMobile面向金融场景设计附功能思维导图 Mand Mobile是面向金融场景设计的移动端组件库,基于Vue.js实现.目前已实际应用于滴滴四大金融业务板块的1 ...

  3. 乘风破浪,遇见华为鸿蒙智能终端系统(HarmonyOS 2),打造面向全场景的分布式操作系统

    什么是鸿蒙智能终端系统(HarmonyOS 2) HarmonyOS 是新一代的智能终端操作系统,为不同设备的智能化.互联与协同提供了统一的语言.带来简洁,流畅,连续,安全可靠的全场景交互体验. ht ...

  4. 面向未来的友好设计:Future Friendly

    一年前翻译了本文的一部分,最近终于翻译完成.虽然此设计思想的提出已经好几年了,但是还是觉得应该在国内推广一下,让大家知道“内容策略”,“移动优先”,“响应式设计”,“原子设计”等设计思想和技术的根源. ...

  5. [转帖]kafka入门:简介、使用场景、设计原理、主要配置及集群搭建

    kafka入门:简介.使用场景.设计原理.主要配置及集群搭建 http://www.aboutyun.com/thread-9341-1-1.html 还没看完 感觉挺好的. 问题导读: 1.zook ...

  6. 领域建模-模型验证与面向资源的API设计

    使用 UMLet 建模 1. 使用类图,分别对 Asg_RH 文档中 Make Reservation 用例以及 Payment 用例开展领域建模.然后,根据上述模型,给出建议的数据表以及主要字段,特 ...

  7. loadrunner基础学习笔记七-面向目标场景

    部署应用程序之前,要执行验收测试以确保系统能够承担预期的实际工作量. 可以为想要生成的每秒点击次数,每秒事务数或事务响应时间设置目标 loadrunner将使用面向目标的场景自动生成所需的目标,当应用 ...

  8. kafka入门:简介、使用场景、设计原理、主要配置及集群搭建(转)

    问题导读: 1.zookeeper在kafka的作用是什么? 2.kafka中几乎不允许对消息进行"随机读写"的原因是什么? 3.kafka集群consumer和producer状 ...

  9. 使用WCF实现SOA面向服务编程—— 架构设计

    原文地址:http://www.cnblogs.com/leslies2/archive/2011/03/29/1997889.html SOA本身就是一种面向企业级服务的系统架构,简单来说,SOA就 ...

随机推荐

  1. Android编译执行envsetup.sh,产生工具命令m、mm、mmm、mmma、tapas 、croot、cgrep、jgrep、 resgrep、godir

    一般来说编译一个sdk或者一个比较大的工程项目,第一步都是执行 envsetup.sh这个脚本,比如编译android,qt源码以及其他一些嵌入式的sdk. 而且执行的时候需要特别注意使用 sourc ...

  2. JAVA笔记6__抽象类/接口/多态/instanceof关键字、父类设计法则

    /** * 抽象类:很多具有相同特征和行为的类可以抽象为一个抽象类 * 1.抽象类可以没有抽象方法,有抽象方法的类必须是抽象类 * 2.非抽象类继承抽象类必须实现抽象方法[可以是空实现] * 3.抽象 ...

  3. 『学了就忘』Linux基础命令 — 39、挂载U盘和挂载NTFS分区

    目录 1.在Linux系统中挂载U盘 (1)插入U盘 (2)查询U盘设备文件名 (3)挂载U盘 (4)U盘中的中文乱码 (5)U盘卸载 2.在Linux系统中挂载NTFS分区 (1)Linux的驱动加 ...

  4. Python 官方研讨会:彻底移除 GIL 真的可行么?

    作者:Łukasz Langa 译者:豌豆花下猫,来源:Python猫 原文:https://lukasz.langa.pl/5d044f91-49c1-4170-aed1-62b6763e6ad0 ...

  5. Python基础(数据类型与变量、字符串和编码)

    #!/usr/bin/env python3 # -*- coding: utf-8 -*- # name = 200 # if name > 100: # print(name,'大于100' ...

  6. koa2使用ejs模板引擎

    在koa中使用ejs并不需要像在node中一样安装了还要引用,只需要npm了就行,同时还需要安装koa-views模块.如: const views = require('koa-views'); 对 ...

  7. jpg与jpeg的区别在哪

    JPG文件的优点是体积小巧,并且兼容性好,因为大部分的程序都能读取这种文件,这是因为JPG格式不仅是一个工业标准格式,而且更是web的标准文件格式.JPG文件如此拥有如此便利的条件,难怪得到了业余玩家 ...

  8. 第二次SQLServer试验解

    1 --给BookInfo表的BookId建立主键约束,给BookInfo表的BookName建立非空约束 2 create table BookInfo( 3 BookId int primary ...

  9. [spojSUBST1]New Distinct Substrings

    求出后缀数组和height数组,然后因为子串即后缀的前缀,考虑不断新增后缀然后计算贡献,如果以sa的顺序新增那么第i个就会产生n-sa[k]+1-h[k](n-sa[k]+1为总方案,h为不合法的方案 ...

  10. [atAGC050F]NAND Tree

    当$n$为偶数,暴力$o(n)$枚举第一次操作,以下只考虑$n$为奇数的情况 此时,$n-1$即操作次数为偶数,找到最小的$i$(其中$1\le i\le \frac{n-1}{2}$),满足第$2i ...