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

推荐订阅源

博客园_首页
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cyber Attacks, Cyber Crime and Cyber Security
Project Zero
Project Zero
P
Proofpoint News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cisco Blogs
V
Vulnerabilities – Threatpost
G
GRAHAM CLULEY
N
News | PayPal Newsroom
NISL@THU
NISL@THU
雷峰网
雷峰网
J
Java Code Geeks
Latest news
Latest news
aimingoo的专栏
aimingoo的专栏
Microsoft Azure Blog
Microsoft Azure Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cisco Talos Blog
Cisco Talos Blog
Hacker News: Ask HN
Hacker News: Ask HN
AWS News Blog
AWS News Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
The Exploit Database - CXSecurity.com
P
Privacy International News Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
Vercel News
Vercel News
Spread Privacy
Spread Privacy
V2EX - 技术
V2EX - 技术
S
Schneier on Security
K
Kaspersky official blog
Recent Announcements
Recent Announcements
T
Threat Research - Cisco Blogs
B
Blog RSS Feed
S
SegmentFault 最新的问题
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
Hugging Face - Blog
Hugging Face - Blog
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
PCI Perspectives
PCI Perspectives
The GitHub Blog
The GitHub Blog
The Last Watchdog
The Last Watchdog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
Scott Helme
Scott Helme
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
量子位
A
Arctic Wolf

陈少文的网站

巨变与机遇的未来十年 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) 使用 Velero 备份 Kubernetes 集群 Kubernetes Cheat Sheet 开发 Tips(18) 如何构建一个 Java 工程 开发 Tips(17) KubeSpray 安装 Kubernetes 报错 ip in ansible_all_ipv4_addresses 基于 Kubernetes 和 Jenkins 搭建自动化测试系统 在 Kubernetes 上动态创建 Jenkins Slave 使用 Jenkins 进行服务拨测 开发 Tips(16) Kubernetes 签发 Ingress 证书及日常故障运维 Kubernetes 中 Deployment 的基本操作 Kubernetes 中的证书 如何使用 KubeBuilder 开发一个 Operator Kubernetes 1.6.0 安装问题汇总 镜像管理工具 -- Harbor 开发 Tips(15) Docker 如何拉取镜像 开发 Tips(14) 使用 Helm 安装 harbor 开发 Tips(13) 使用 S2I 构建云原生应用 在 Kubernetes 中使用 emptyDir、hostPath、localVolume 开发 Tips(12) 开发 Tips(11) 代码质量分析工具 SonarQube 使用 Kubeadm 安装 Kubernetes 集群 一起来学 Go --(4)常用函数 Kubernetes 中的 Ceph Kubernetes 之 Volumes Kubernetes 之 Labels、Selectors 开发 Tips(10) 开源正在重构商业模式 Kubernetes 之网络 Kubernetes 之 API 使用 Helm 和 Operator 快速部署 Prometheus Kubernetes 复杂有状态应用管理框架 -- Operator Kubernetes 的包管理器 -- Helm 一起来学 Go --(3)Go Modules 如何一步一步地优化博客方案 kubectl 实用指南 Kubernetes 中的基本概念 搭建远程 Kubernetes 开发环境 大公司和小公司的 ToB 思路 开发 Tips(9) Go 入门指南 一起来学 Go --(2)数据与逻辑结构 如何预防 Web 富文本中的 XSS 攻击 django-xss-cleaner 云工作时代 一起来学 Go --(1)背景与特点 SaaS 开发团队的不同阶段 你不知道的 Git 使用技巧 输出既服务 微服务设计 继续奔跑 开发 Tips(8) 从账户安全到二次验证 Django 性能之数据库查询优化 Django 性能之分库分表 敏捷开发之研发流程 打造一致性的团队 开发 Tips(7) Pytest 进阶学习之 Mock PaaS 部署之 buildpack Go 开发配置 领域输出才是 PaaS 的核心竞争力 Pytest 入门学习 开发 Tips(6) 如何使用 Jenkins、Docker、GitLab 搭建 Django 自动化部署流程
一个通用流水线设计
微信公众号 · 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 定义。因此,只要引擎支持上述功能,都可以实现。