最近的一个项目,需要实现一个工作任务流(task pipeline),基于之前CICD的经验,jenkins pipeline和drone的pipeline进入候选。

drone是基于go的cicd解决方案,github上有1.6万+star,本文简单对比了其和jenkins的区别,重点介绍了drone的pipeline原理,并简单分析了代码。

jenkins 与 drone

对比项 jenkins drone
pipeline定义 编写jenkinsfile 编写流程yml
运行方式 在一个pod里运行 每一步骤起对应的container,通过挂载volume实现数据共享
运行环境 物理机或者容器环境,包括K8S docker容器环境
开发语言 java golang

drone pipeline好处是相对更轻量级,yml定义也相对简洁清晰,按照功能来划分容器,可以方便的实现task的复用,而jenkins则是完全打包到一个镜像,会造成单个镜像体积过大,比如jenkins的单个镜像超过2G。

drone的pipeline,是基于https://github.com/cncd/pipeline 实现的,这里简单分析下其原理。

编译和执行 drone pipeline

要了解一个程序的原理,先从输入输出讲起。

先安装:

go get -u github.com/cncd/pipeline
go install github.com/cncd/pipeline/pipec

然后测试

cd $GOPATH/github.com/cncd/pipeline/samples/sample_1
# ll
total 28
drwxr-xr-x 2 root root 4096 Jan 22 11:44 ./
drwxr-xr-x 13 root root 4096 Jan 22 11:02 ../
-rw-r--r-- 1 root root 549 Jan 22 11:02 .env
-rw-r--r-- 1 root root 6804 Jan 22 16:30 pipeline.json
-rw-r--r-- 1 root root 229 Jan 22 11:02 pipeline.yml
-rw-r--r-- 1 root root 138 Jan 22 11:02 README.md
  • pipeline.yml 定义文件
  • pipeline.json 编译后的配置文件
  • .env 环境变量

先来查看pipeline.yml 定义

workspace:
base: /go
path: src/github.com/drone/envsubst clone:
git:
image: plugins/git
depth: 50 pipeline:
build:
image: golang:1.7
commands:
- go get -t ./...
- go build
- go test -v

上面的yml定义了:

  • 工作目录workspace
  • 初始化工作,git clone仓库,仓库地址在.env里定义
  • 然后是定义pipeline,
    • pipeline下面是step数组,这里只有一个build
    • 使用golang:1.7镜像
    • 构建命令在commands数组里定义

通过pipec compilecompile配置文件:

# pipec compile
Successfully compiled pipeline.yml to pipeline.json

查看编译后的pipeline.json

{
"pipeline": [
{
"name": "pipeline_clone_0",
"alias": "git",
"steps": [
{
"name": "pipeline_clone_0",
"alias": "git",
"image": "plugins/git:latest",
"working_dir": "/go/src/github.com/drone/envsubst",
"environment": {
"CI": "drone",
"CI_BUILD_CREATED": "1486119586",
"CI_BUILD_EVENT": "push",
"CI_BUILD_NUMBER": "6",
"CI_BUILD_STARTED": "1486119585",
"CI_COMMIT_AUTHOR": "bradrydzewski",
"CI_COMMIT_AUTHOR_NAME": "bradrydzewski",
"CI_COMMIT_BRANCH": "master",
"CI_COMMIT_MESSAGE": "added a few more test cases for escaping behavior",
"CI_COMMIT_REF": "refs/heads/master",
"CI_COMMIT_SHA": "d0876d3176965f9552a611cbd56e24a9264355e6",
"CI_REMOTE_URL": "https://github.com/drone/envsubst.git",
"CI_REPO": "drone/envsubst",
"CI_REPO_LINK": "https://github.com/drone/envsubst",
"CI_REPO_NAME": "drone/envsubst",
"CI_REPO_REMOTE": "https://github.com/drone/envsubst.git",
"CI_SYSTEM": "pipec",
"CI_SYSTEM_ARCH": "linux/amd64",
"CI_SYSTEM_LINK": "https://github.com/cncd/pipec",
"CI_SYSTEM_NAME": "pipec",
"CI_WORKSPACE": "/go/src/github.com/drone/envsubst",
"DRONE": "true",
"DRONE_ARCH": "linux/amd64",
"DRONE_BRANCH": "master",
"DRONE_BUILD_CREATED": "1486119586",
"DRONE_BUILD_EVENT": "push",
"DRONE_BUILD_LINK": "https://github.com/cncd/pipec/drone/envsubst/6",
"DRONE_BUILD_NUMBER": "6",
"DRONE_BUILD_STARTED": "1486119585",
"DRONE_COMMIT": "d0876d3176965f9552a611cbd56e24a9264355e6",
"DRONE_COMMIT_AUTHOR": "bradrydzewski",
"DRONE_COMMIT_BRANCH": "master",
"DRONE_COMMIT_MESSAGE": "added a few more test cases for escaping behavior",
"DRONE_COMMIT_REF": "refs/heads/master",
"DRONE_COMMIT_SHA": "d0876d3176965f9552a611cbd56e24a9264355e6",
"DRONE_JOB_STARTED": "1486119585",
"DRONE_REMOTE_URL": "https://github.com/drone/envsubst.git",
"DRONE_REPO": "drone/envsubst",
"DRONE_REPO_LINK": "https://github.com/drone/envsubst",
"DRONE_REPO_NAME": "envsubst",
"DRONE_REPO_OWNER": "drone",
"DRONE_REPO_SCM": "git",
"DRONE_WORKSPACE": "/go/src/github.com/drone/envsubst",
"PLUGIN_DEPTH": "50"
},
"volumes": [
"pipeline_default:/go"
],
"networks": [
{
"name": "pipeline_default",
"aliases": [
"git"
]
}
],
"on_success": true,
"auth_config": {}
}
]
},
{
"name": "pipeline_stage_0",
"alias": "build",
"steps": [
{
"name": "pipeline_step_0",
"alias": "build",
"image": "golang:1.7",
"working_dir": "/go/src/github.com/drone/envsubst",
"environment": {
"CI": "drone",
"CI_BUILD_CREATED": "1486119586",
"CI_BUILD_EVENT": "push",
"CI_BUILD_NUMBER": "6",
"CI_BUILD_STARTED": "1486119585",
"CI_COMMIT_AUTHOR": "bradrydzewski",
"CI_COMMIT_AUTHOR_NAME": "bradrydzewski",
"CI_COMMIT_BRANCH": "master",
"CI_COMMIT_MESSAGE": "added a few more test cases for escaping behavior",
"CI_COMMIT_REF": "refs/heads/master",
"CI_COMMIT_SHA": "d0876d3176965f9552a611cbd56e24a9264355e6",
"CI_REMOTE_URL": "https://github.com/drone/envsubst.git",
"CI_REPO": "drone/envsubst",
"CI_REPO_LINK": "https://github.com/drone/envsubst",
"CI_REPO_NAME": "drone/envsubst",
"CI_REPO_REMOTE": "https://github.com/drone/envsubst.git",
"CI_SCRIPT": "CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVAp1bnNldCBEUk9ORV9ORVRSQ19VU0VSTkFNRQp1bnNldCBEUk9ORV9ORVRSQ19QQVNTV09SRAoKZWNobyArICJnbyBnZXQgLXQgLi8uLi4iCmdvIGdldCAtdCAuLy4uLgoKZWNobyArICJnbyBidWlsZCIKZ28gYnVpbGQKCmVjaG8gKyAiZ28gdGVzdCAtdiIKZ28gdGVzdCAtdgoK",
"CI_SYSTEM": "pipec",
"CI_SYSTEM_ARCH": "linux/amd64",
"CI_SYSTEM_LINK": "https://github.com/cncd/pipec",
"CI_SYSTEM_NAME": "pipec",
"CI_WORKSPACE": "/go/src/github.com/drone/envsubst",
"DRONE": "true",
"DRONE_ARCH": "linux/amd64",
"DRONE_BRANCH": "master",
"DRONE_BUILD_CREATED": "1486119586",
"DRONE_BUILD_EVENT": "push",
"DRONE_BUILD_LINK": "https://github.com/cncd/pipec/drone/envsubst/6",
"DRONE_BUILD_NUMBER": "6",
"DRONE_BUILD_STARTED": "1486119585",
"DRONE_COMMIT": "d0876d3176965f9552a611cbd56e24a9264355e6",
"DRONE_COMMIT_AUTHOR": "bradrydzewski",
"DRONE_COMMIT_BRANCH": "master",
"DRONE_COMMIT_MESSAGE": "added a few more test cases for escaping behavior",
"DRONE_COMMIT_REF": "refs/heads/master",
"DRONE_COMMIT_SHA": "d0876d3176965f9552a611cbd56e24a9264355e6",
"DRONE_JOB_STARTED": "1486119585",
"DRONE_REMOTE_URL": "https://github.com/drone/envsubst.git",
"DRONE_REPO": "drone/envsubst",
"DRONE_REPO_LINK": "https://github.com/drone/envsubst",
"DRONE_REPO_NAME": "envsubst",
"DRONE_REPO_OWNER": "drone",
"DRONE_REPO_SCM": "git",
"DRONE_WORKSPACE": "/go/src/github.com/drone/envsubst",
"HOME": "/root",
"SHELL": "/bin/sh"
},
"entrypoint": [
"/bin/sh",
"-c"
],
"command": [
"echo $CI_SCRIPT | base64 -d | /bin/sh -e"
],
"volumes": [
"pipeline_default:/go"
],
"networks": [
{
"name": "pipeline_default",
"aliases": [
"build"
]
}
],
"on_success": true,
"auth_config": {}
}
]
}
],
"networks": [
{
"name": "pipeline_default",
"driver": "bridge"
}
],
"volumes": [
{
"name": "pipeline_default",
"driver": "local"
}
],
"secrets": null
}

简单分析结构:

  • pipeline 定义了执行的stage,每个stage有一个或者多个step
  • networks、volumes、secrets 分别定义网络、存储和secrets
    • 通过network,实现container互通
    • 通过volumes实现数据共享

最后执行,通过pipec exec

# pipec exec
proc "pipeline_clone_0" started
+ git init
Initialized empty Git repository in /go/src/github.com/drone/envsubst/.git/
+ git remote add origin https://github.com/drone/envsubst.git
+ git fetch --no-tags --depth=50 origin +refs/heads/master:
From https://github.com/drone/envsubst
* branch master -> FETCH_HEAD
* [new branch] master -> origin/master
+ git reset --hard -q d0876d3176965f9552a611cbd56e24a9264355e6
+ git submodule update --init --recursive
proc "pipeline_clone_0" exited with status 0
proc "pipeline_step_0" started
+ go get -t ./...
+ go build
+ go test -v
=== RUN TestExpand
--- PASS: TestExpand (0.00s)
=== RUN TestFuzz
--- PASS: TestFuzz (0.01s)
=== RUN Test_len
--- PASS: Test_len (0.00s)
=== RUN Test_lower
--- PASS: Test_lower (0.00s)
=== RUN Test_lowerFirst
--- PASS: Test_lowerFirst (0.00s)
=== RUN Test_upper
--- PASS: Test_upper (0.00s)
=== RUN Test_upperFirst
--- PASS: Test_upperFirst (0.00s)
=== RUN Test_default
--- PASS: Test_default (0.00s)
PASS
ok github.com/drone/envsubst 0.009s
proc "pipeline_step_0" exited with status 0

pipeline 原理分析

编译过程

可以形象的理解为 .env+pipeline.yml --> pipeline.json

编译过程不复杂,主要是解析pipeline.yml为Config:

Config struct {
Cache libcompose.Stringorslice
Platform string
Branches Constraint
Workspace Workspace
Clone Containers
Pipeline Containers
Services Containers
Networks Networks
Volumes Volumes
Labels libcompose.SliceorMap
}

然后转换为json对应的config:

Config struct {
Stages []*Stage `json:"pipeline"` // pipeline stages
Networks []*Network `json:"networks"` // network definitions
Volumes []*Volume `json:"volumes"` // volume definitions
Secrets []*Secret `json:"secrets"` // secret definitions
}

该部分主要代码在pipeline/frontend里

执行过程

我们主要关注执行过程,主要代码在pipeline/backend里。

首先是读取配置文件为backend.Config

config, err := pipeline.Parse(reader)
if err != nil {
return err
}

然后创建执行环境,目前的代码仅docker可用,k8s是空代码。

var engine backend.Engine
if c.Bool("kubernetes") {
engine = kubernetes.New(
c.String("kubernetes-namepsace"),
c.String("kubernetes-endpoint"),
c.String("kubernetes-token"),
)
} else {
engine, err = docker.NewEnv()
if err != nil {
return err
}
}

接着开始执行

	ctx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout"))
defer cancel()
ctx = interrupt.WithContext(ctx) return pipeline.New(config,
pipeline.WithContext(ctx),
pipeline.WithLogger(defaultLogger),
pipeline.WithTracer(defaultTracer),
pipeline.WithEngine(engine),
).Run()

其中pipeline.NEW创建了Runtime对象;

type Runtime struct {
err error // 错误信息
spec *backend.Config // 配置信息
engine backend.Engine // docker engine
started int64 // 开始时间 ctx context.Context
tracer Tracer
logger Logger
}

其中Engine,操作容器的interface,目前仅docker可用。

// Engine defines a container orchestration backend and is used
// to create and manage container resources.
type Engine interface {
// Setup the pipeline environment.
Setup(context.Context, *Config) error
// Start the pipeline step.
Exec(context.Context, *Step) error
// Kill the pipeline step.
Kill(context.Context, *Step) error
// Wait for the pipeline step to complete and returns
// the completion results.
Wait(context.Context, *Step) (*State, error)
// Tail the pipeline step logs.
Tail(context.Context, *Step) (io.ReadCloser, error)
// Destroy the pipeline environment.
Destroy(context.Context, *Config) error
}

关注Run:

// Run starts the runtime and waits for it to complete.
func (r *Runtime) Run() error { // 延迟函数,用于销毁docker env
defer func() {
r.engine.Destroy(r.ctx, r.spec)
}() // 初始化docker engine
r.started = time.Now().Unix()
if err := r.engine.Setup(r.ctx, r.spec); err != nil {
return err
} // 依次运行stage
for _, stage := range r.spec.Stages {
select {
case <-r.ctx.Done():
return ErrCancel
// 执行
case err := <-r.execAll(stage.Steps):
if err != nil {
r.err = err
}
}
} return r.err
}

重点在于使用errgroup.Group通过协程方式运行step:


// 执行所有steps
func (r *Runtime) execAll(procs []*backend.Step) <-chan error {
var g errgroup.Group
done := make(chan error)
// 遍历执行step
for _, proc := range procs {
// 协程 exec
proc := proc
g.Go(func() error {
return r.exec(proc)
})
} go func() {
done <- g.Wait()
close(done)
}()
return done
} // 执行单个step
func (r *Runtime) exec(proc *backend.Step) error {
switch {
case r.err != nil && proc.OnFailure == false:
return nil
case r.err == nil && proc.OnSuccess == false:
return nil
} // trace日志
if r.tracer != nil {
state := new(State)
state.Pipeline.Time = r.started
state.Pipeline.Error = r.err
state.Pipeline.Step = proc
state.Process = new(backend.State) // empty
if err := r.tracer.Trace(state); err == ErrSkip {
return nil
} else if err != nil {
return err
}
} // docker engine执行
if err := r.engine.Exec(r.ctx, proc); err != nil {
return err
} // 记录日志信息
if r.logger != nil {
rc, err := r.engine.Tail(r.ctx, proc)
if err != nil {
return err
} go func() {
r.logger.Log(proc, multipart.New(rc))
rc.Close()
}()
} if proc.Detached {
return nil
} // 等待docker engine执行完成
wait, err := r.engine.Wait(r.ctx, proc)
if err != nil {
return err
} if r.tracer != nil {
state := new(State)
state.Pipeline.Time = r.started
state.Pipeline.Error = r.err
state.Pipeline.Step = proc
state.Process = wait
if err := r.tracer.Trace(state); err != nil {
return err
}
} if wait.OOMKilled {
return &OomError{
Name: proc.Name,
Code: wait.ExitCode,
}
} else if wait.ExitCode != 0 {
return &ExitError{
Name: proc.Name,
Code: wait.ExitCode,
}
}
return nil
}

作者:Jadepeng

出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi

您的支持是对博主最大的鼓励,感谢您的认真阅读。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

drone的pipeline原理与代码分析的更多相关文章

  1. 免费的Lucene 原理与代码分析完整版下载

    Lucene是一个基于Java的高效的全文检索库.那么什么是全文检索,为什么需要全文检索?目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结构的 ...

  2. OpenStack 虚拟机冷/热迁移的实现原理与代码分析

    目录 文章目录 目录 前文列表 冷迁移代码分析(基于 Newton) Nova 冷迁移实现原理 热迁移代码分析 Nova 热迁移实现原理 向 libvirtd 发出 Live Migration 指令 ...

  3. SQL注入原理及代码分析(二)

    前言 上一篇文章中,对union注入.报错注入.布尔盲注等进行了分析,接下来这篇文章,会对堆叠注入.宽字节注入.cookie注入等进行分析.第一篇文章地址:SQL注入原理及代码分析(一) 如果想要了解 ...

  4. XSS原理及代码分析

    前言 XSS又叫跨站脚本攻击,是一种对网站应用程序的安全漏洞攻击技术.它允许恶意用户将代码注入网页,其他用户在浏览网页时就会受到影响.XSS分为三种:反射型,存储型,和DOM型.下面我会构造有缺陷的代 ...

  5. lighttpd与fastcgi+cgilua原理、代码分析与安装

    原理 http://www.cnblogs.com/skynet/p/4173450.html 快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关 ...

  6. SQL注入原理及代码分析(一)

    前言 我们都知道,学安全,懂SQL注入是重中之重,因为即使是现在SQL注入漏洞依然存在,只是相对于之前现在挖SQL注入变的困难了.而且知识点比较多,所以在这里总结一下.通过构造有缺陷的代码,来理解常见 ...

  7. AbstractQueuedSynchronizer原理及代码分析

    一.AQS简介 AbstractQueuedSynchronizer(AQS)是java.util.concurrent并发包下最基本的同步器,其它同步器实现,如ReentrantLock类,Reen ...

  8. WordPress HOOK机制原理及代码分析

    WordPress强大的插件机制让我们可以自由扩展功能.网上对插件的使用以及开发方法都有大量资料可以查询. 今天我们就分析一下四个主要函数的代码,包括: add_action.do_action.ad ...

  9. Openvswitch原理与代码分析(6):用户态流表flow table的操作

    当内核无法查找到流表项的时候,则会通过upcall来调用用户态ovs-vswtichd中的flow table. 会调用ofproto-dpif-upcall.c中的udpif_upcall_hand ...

随机推荐

  1. (转!)Pyinstaller 打包发布经验总结

    原文地址 https://blog.csdn.net/weixin_42052836/article/details/82315118 具体的实现图待本人实现后贴上 原 Pyinstaller 打包发 ...

  2. vue与jquery合作

    2017年2月26日 14:59:34 星期日 场景: jquery的$.post, $.get是$.ajax的封装, 是异步的 因此, 有肯能在初始化vue实例的时候, 异步请求的结果还没返回, 这 ...

  3. 【原创】测试基础之http_load(1)简介、安装、使用

    http_load-09Mar2016官方:https://acme.com/software/http_load/ 一 简介 http_load - multiprocessing http tes ...

  4. [PHP]PDO各方法在发生MYSQL断开时的反应

    1.mixed PDO::errorCode ( void ) 如果单独执行此语句,并不能判断此时MYSQL是否已断开,它返回最上一次对MYSQL操作的错误码 2.public array PDO:: ...

  5. Eleaticsearch源码分析(一)编译启动

    转自:https://lunatictwo.github.io/2017/12/21/Eleaticsearch%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%B8% ...

  6. linux中ping带时间及打印内容到文件

    ping命令就不多说了,-i是时间间隔,-c是ping的次数 这种是每隔30秒ping一次,并在后面显示时间: ping 192.168.30.123 -i 30 | awk '{ print $0& ...

  7. 并发性能的隐形杀手之伪共享(false sharing)

    在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...

  8. 基于多进程的Tcp套接字服务器

    服务端 import socketfrom multiprocessing import Process def task(c): print('顾客吃点啥') while True: data = ...

  9. 四.awk、sde深度讲解

    ###sed### 查询 1创建测试文件 cat>person.txt<<EOF> 101,oldboy,CEO> 102,zhangyao,CTO> 103,Al ...

  10. 高斯消元模板!!!bzoj1013

    /* 高斯消元模板题 n维球体确定圆心必须要用到n+1个点 设圆心坐标(x1,x2,x3,x4...xn),半径为C 设第i个点坐标为(ai1,ai2,ai3,,,ain)那么对应的方程为 (x1-a ...