作者|林俊(万念)

来源|尔达 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. SpringMVC配置版到注解版

    什么是springmvc? 1.1.什么是MVC MVC是模型(Model).视图(View).控制器(Controller)的简写,是一种软件设计规范. 是将业务逻辑.数据.显示分离的方法来组织代码 ...

  2. JMeter源码导入到Intellij IDEA (八)

    亲测:jmeter源码导入Intellij IDEA,导入,编译,运行! 一.环境:windows10操作系统,jdk1.8,Intellij IDEA 2018.3,jmeter5.0 下载jmet ...

  3. LeetCode 113. 路径总和 II C++

    提交结果:内存超100%,用时超69% /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNo ...

  4. [python]Appium+python +pytest 实现APP自动化,基于安卓

    1.安卓环境搭建 &关于app自动化,个人觉得安装过程比较复杂,脚本难度实现和web自动化差不多封装关键字即可,因此,下面会写安装.启动APP以及过程中遇到的一些坑(这一篇偏向解释给个人) & ...

  5. linux删除文件未释放

    https://access.redhat.com/solutions/2316 $ /usr/sbin/lsof | grep deleted ora 25575 data 33u REG 65,6 ...

  6. 基于EPPlus和NPOI实现的Excel导入导出

    基于EPPlus和NPOI实现的Excel导入导出 CollapseNav.Net.Tool.Excel(NuGet地址) 太长不看 导入 excel 文件流将会转为 ExcelTestDto 类型的 ...

  7. Python 爬取 拉钩

    ... from urllib import request from urllib import parse from urllib.error import URLError import jso ...

  8. 一个简单的golang项目,实验 gitlab-ci-cd Pipelines

    至少两台主机,gitlab + gitlab-runner gitlab + gitlab-runner安装略 项目源码:https://gitee.com/M27149/testgo.git 在自建 ...

  9. 使用PAM模块实现普通用户之间su免密切换

    参考自:Allow user1 to "su - user2" without password https://unix.stackexchange.com/questions/ ...

  10. Java将增加虚拟线程,挑战Go协程

    我们知道 Go 语言最大亮点之一就是原生支持并发,这得益于 Go 语言的协程机制.一个 go 语句就可以发起一个协程 (goroutin).协程本质上是一种用户态线程,它不需要操作系统来进行调度,而是 ...