惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
B
Blog
IT之家
IT之家
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
量子位
博客园 - 叶小钗
T
Tailwind CSS Blog
小众软件
小众软件
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - Franky
雷峰网
雷峰网
博客园 - 三生石上(FineUI控件)
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Blog — PlanetScale
Blog — PlanetScale
V
V2EX
博客园_首页
I
InfoQ
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog

陈少文的网站

巨变与机遇的未来十年 Kubernetes 平台管理软件压力测试方案 使用镜像部署 Hexo 静态页面 终于等到你 - GitHub 镜像仓库服务(ghcr.io) 一起来学 Go --(6)Interface 一起来学 Go --(5)Goroutine 和 Channel 什么是函数式编程 如何在 Kubernetes 集群集成 Kata 柯里化与偏函数 使用 PyGithub 自动创建 Label 软件产品是团队能力的输出 Helm 2 、Helm 3 比较 IoT 变现 Kubernetes 中的 DNS 服务 国内的 Helm 镜像源 Harbor 使用自签证书支持 Https 访问 DevOps 工具链之 Prow 如何使用 kfctl 安装 Kubeflow VS Code 无法下载 Go 插件的工具包 工程师更应具有服务精神 你不知道的 Docker 使用技巧 使用 Docker 运行 Tensorflow 论中国 什么是左移 如何清空 Git 仓库全部历史记录 一禅小和尚 有风吹过厨房 时间的玫瑰 如何在 CentOS 安装 GPU 驱动 开发 Tips(19)
一个通用流水线设计
微信公众号 · 2021-05-19 · via 陈少文的网站

1. 解耦引擎释放流水线能力

在设计系统时,我们常面临两难。是内敛复杂度,对外提供单一易用的功能;还是释放复杂度,将灵活归还用户。这非常考验产品能力。

设计 CICD 系统时,我们可以直接将 Jenkinsfile、PipelineRun 等概念直接抛给用户,让用户自己学习相关领域的知识,再来使用产品。当然,也可以继续抽象,在人与系统之间建立模型,实现意识与指令的转换。我们想要更加易用的产品,因此选择屏蔽底层概念,继续抽象、建模。

从 Jenkins 、GitLab CI,再到 GitHub Actions、Tekton,新的基础设施总会有各种各样的基础组件涌现。我们想减少这种切换的成本,在各种引擎之间能够切换。技术在不断地更替,但我们想对用户保持一致。

虽然流水线相关技术在快速演进,但执行这件事的终究是人。人的知识是有传承的,无论技术怎样变化,做流水线引擎的社区是相对稳定的,都是有交集的一群人。这给解耦引擎,设计通用流水线提供了可能。

2. 流水线的数据模型

在很多 CICD 引擎中,能够找到相似的概念。

  • Jenkins

流水线包含很多个 Stage,而 Stage 中的 steps 包含多个串行执行的脚本。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                echo 'Building-1..'
                echo 'Building-2..'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing..'
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploying....'
            }
        }
    }
}
  • GitLab CI

流水线包含 build、test 两个 串行的 Stage,每个 Stage 包含若干并行执行的 Job 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
stages:
  - build
  - test

build-code-job:
  stage: build
  script:
    - echo "Check the ruby version, then build some Ruby project files:"
    - ruby -v
    - rake

test-code-job1:
  stage: test
  script:
    - echo "If the files are built successfully, test some files with one command:"
    - rake test1

test-code-job2:
  stage: test
  script:
    - echo "If the files are built successfully, test other files with a different command:"
    - rake test2
  • GitHub Actions

流水线由 jobs 定义,一个流水线有很多个可能的 job(示例中的 build 构成),每个 job 又包含很多串行的 steps。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
name: Octo Organization CI

on:
  push:
    branches: [$default-branch]
  pull_request:
    branches: [$default-branch]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Run a one-line script
        run: echo Hello from Octo Organization

基于以上的示例,我们对 Pipeline 进行抽象。

1
2
3
4
5
6
7
- pipeline
  - stage-1
  - step-1-1
  - step-1-2
  - stage-2
  - step-2-1
  - ...

如下图,一个流水线包含若干个 Stage, Stage 可以并行或者串行。Stage 中包含若干个串行的 Step 执行脚本。而在 Tekton 中,Stage 对应着 Task 。

一个流水线的运行时,可以是一个 Kubernetes 集群、一个物理机、一个 Container 环境等。

一个 Stage 的运行时,可能是一个 Pod、一个物理机、一个 Container 环境等。

一个 Step 有一个工作空间,然后执行 Shell Script。

流水线并不需要复杂的定义,即使几个简单的脚本,也可以编排复杂的逻辑。但是对流水线进行抽象和建模,有利于插件(Step)扩展,流水线产品本身的开发和维护。

流水线会经过一系列的 Manager 处理,与特定的运行时、执行引擎、凭证等关联起来。最终渲染出引擎接受的流水线描述,例如 Jenkins 的 Jenkinsfile、Tekton 的 Yaml。

3. 代码层的数据模型

下面给出核心数据结构的主要字段:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 定义运行时
type Runtime struct {
	Name        string
	Provider    interface{}
}

// 定义引擎
type Engine string

// 定义作用域,分为三个层级。
type Scope string

const (
	ScopePipeline Scope = "Pipeline"
	ScopeStage    Scope = "Stage"
	ScopeStep     Scope = "Step"
)

// 定义参数结构
type ParamSpec struct {
	Name string `json:"name"`
	Scope ParamType `json:"type,omitempty"`
	Default interface{} `json:"default,omitempty"`
}

// 定义插件模板
type Step struct {
	Name        string      `json:"name"`
	Params      []ParamSpec `json:"params,omitempty"`
	Script      string      `json:"script,omitempty"`
  Engine      string      `json:"engine"`
	Workspace   string        `json:"workspace,omitempty"`
}

// 定义组装流水线之后,插件(Step)相关字段
type PipelineStep struct {
	Name    string  `json:"name,omitempty"`
	StepRef string  `json:"StageRef,omitempty"`
	Params  []Param `json:"params,omitempty"`
	Status  string  `json:"status,omitempty"`
	Workspace string  `json:"workspace,omitempty"`
}

// 定义 Stage ,主要是一系列的 Steps
type Stage struct {
	ObjectMeta `json:"metadata"`
	Spec       StageSpec `json:"spec"`
}

type StageSpec struct {
	Description string         `json:"description,omitempty"`
	Params      []ParamSpec    `json:"params,omitempty"`
	Steps       []PipelineStep `json:"steps,omitempty"`
	Workspace   string         `json:"workspace,omitempty"`
}

// 定义组装流水线之后,阶段(Stage)相关字段
type PipelineStage struct {
	Name      string        `json:"name,omitempty"`
	StageRef  Stage         `json:"stageRef,omitempty"`
	Params    []Param       `json:"params,omitempty"`
	Workspace string        `json:"workspace,omitempty"`
	Status    string        `json:"status,omitempty"`
	Runtime   *Runtime
}

// 定义流水线的结构
type Pipeline struct {
	ObjectMeta `json:"metadata"`
	Spec       PipelineSpec `json:"spec"`
}

type PipelineSpec struct {
	Params      []ParamSpec     `json:"params,omitempty"`
	Stages      []PipelineStage `json:"stages,omitempty"`
	Workspace string `json:"workspace,omitempty"`
}

// 定义运行一条流水线相关的字段
type PipelineRun struct {
	ObjectMeta `json:"metadata,omitempty"`
	Spec       PipelineRunSpec `json:"spec,omitempty"`
	Status     string          `json:"status,omitempty"`
}

type PipelineRunSpec struct {
	PipelineRef string         `json:"pipelineRef,omitempty"`
	Params      []Param        `json:"params,omitempty"`
	Runtime     *Runtime
}

在代码层面,需要注意两点:

  • 模板与实例。模板是系统内置的引擎相关的框架或片段,例如 Step、Stage、Pipeline ;而实例是填充个性化参数之后的模板或片段,PipelienStep、PipelineStage、PipelineRun 。
  • 作用范围。参数中有一个字段 Scope ,用于表示参数在什么范围可见。实际上,真正使用参数的是 Step,但是组装之后 Stage 作用域的参数会被提升到 Stage 中。同理,Pipeline 作用域的参数会被提升到 Pipeline 中。参数在不同的层级会有不同的用处,从内向外抽取,从外向内注入。

4. 流水线运行时的数据与交互

上面是一个流水线的执行流程,可以对照着每个步骤查看,这里不再重复。下面主要以不同角色的视角描述流水线的运行。

4.1 系统内置 Step 插件模板

首先需要在系统中内置一些常用的插件 Script。

例如,Jenkins 的 Git Clone 插件

1
git(url: '${param.git_repo}', credentialsId: '${param.ssh-key}', branch: '${param.branch}', changelog: true, poll: false)

Jenkins 的构建并推送镜像。

1
2
3
4
5
withCredentials([usernamePassword(passwordVariable : 'DOCKER_PASSWORD' ,usernameVariable : 'DOCKER_USERNAME' ,credentialsId : "${param.credential_id}" ,)]) {
          sh 'echo "$DOCKER_PASSWORD" | docker login ${param.registry_server} -u "$DOCKER_USERNAME" --password-stdin'
          sh 'docker push  ${param.image_name}'
          sh 'docker logout'
        }

Jenkins 执行脚本。

1
sh '${param.script_content}'

当然也可以是其他引擎的插件片段,但主要是脚本片段 + 参数注入,因此不再列举。

4.2 创建流水线时,用户视角

如上图,用户首先会根据用户选择的引擎,得到一个 Step 模板列表。然后通过编排,将 Step 组装成一个流水线。

其中,Step-1、2、3 表示的是选择一个模板 Step 并初始化参数的实例。然后组装出 Pipeline 的数据结构保存在后端。

如果是使用模板进行创建,那么只需要提前帮用户初始化 Pipeline 数据结构即可。

4.3 创建流水线,开发人员视角

  • 前端研发

请求 Step 模板列表之后,根据用户输入的实例化参数,组装 Pipeline 结构。

  • 后端研发

将用户的 Pipeline 数据保存之后,根据 Step 模板信息,将 Pipeline 渲染成引擎需要的流水线描述。例如,生成 Jenkinsfile 文件,同步到 Jenkins 创建流水线。

4.4 运行时,数据流向及交互

  • 执行流水线

前端调用后端接口,获取 Pipeline 的定义,将 Pipeline 级别的参数弹框,让用户输入相关的参数。点击确认后,创建 PipelineRun 对象。

后端根据 PipelineRun 对象,触发引擎的执行接口,将 PipelineRun 中定制化的参数传入流水线执行。

  • 查看流水线

PipelineRun 即为流水线的执行历史,需要从引擎中查询流水线的状态,并写入 PipelineRun 对象。

  • 再次运行、暂停、继续、审批

在 PipelineRun 中记录有某次执行的全部记录,包括参数、Pipeline 定义。因此,只要引擎支持上述功能,都可以实现。