Drone是一个Golang技术栈的CI解决方案,功能和Jenkins之类的CI工具类似。
优点:
缺点:
简单选择:
相关资源:
任务
:构建、发布、部署,其内部会有多个行为共同组成一个pipelinestep
step
可以完成一个特定的pipeline,e.g
/tmp/drone-${RANDOM}/drone/src
/private/var/folders/...
C:\windows\drone-%RANDOM%\drone\src
~
或$HOME
,获得的地址就是workspace的根目录,而不是
当前runner的用户homepipeline
是否要触发
step
是否要触发
Drone这个CI其实很简单,也说不上什么架构。一般Drone会有一个单点Server,在git工具上注册好webhook接收事件,然后启动多个Runner来处理Server上产生的任务。这些Runner可以在同一台主机上,也可以分散在多台不同的主机上,官方文档的指引中要求不要将runner和server放在同一台主机上。
官方说明:
Drone supports configuring and orchestrating multiple pipelines. This is useful when you need to fan-out and distribute your build tasks across multiple machines to reduce build times, or to execute your build tasks across multiple platforms (e.g. amd64 and arm64).
用途1:
在处理大型项目任务的时候,将任务拆散,分散到不同的物理机上进行并行处理,加速整个构建进程。如果是不同主机的话,需要用到2.4
提到的pipeline trigger,将不同的pipeline设定好自己的运行目标主机。保证同一个repo在不同主机上触发的时候,只有当前主机(runner)应该执行的pipeline得到执行。
用途2:
确定pipeline之间的先后顺序,通过在pipeline一级定义depends_on
来申明相互之间的依赖关系。这里特别需要注意的是:pipeline之间是不共享workspace的。所以每一步都需要单独做类似于依赖安装之类的工作,这需要特别小心。
---
kind: pipeline
# ...
---
kind: pipeline
name: p3
steps:
- name: notify
# ...
depends_on:
- p1
- p2
这部分内容与 Multiple Pipeline 的关系,和之前讲过的 trigger 与 condition 的关系一样。Multiple Pipeline 是pipeline级别的,而Parallelism则是step级别的,本质上仍旧是并行运行。
kind: pipeline
# ...
steps:
# ...
- name: s3
commands:
- # ...
depends_on:
- s1
- s2
需要在git repo软件上申请一个OAuth应用。这里我使用的是Gitea,就拿它来做演示:
点击:右上角头像 > 设置(下拉菜单) > 应用(页面中间)
,打开的页面是管理用户级别的Access Tokens
和OAuth2应用
的。
找到:创建新的 OAuth2 应用程序
这一栏,输入:
重定向 URI
:http://${drone_host}:${drone_ip}
/login点击创建应用
。
在打开的页面上显示了OAuth2应用的相关信息,这里需要记下:
Golang应用程序,使用上最容易的方式仍旧是镜像:drone/drone。
drone服务器启动时的环境变量文档在:Drone Documentation > Installation > Reference。
$ docker run -it -d \
--name drone-server \
-p 18980:80 \
--log-driver=json-file \
--log-opt max-size=512m \
-v /tmp/drone/data:/data \ # sqlite db file
-e DRONE_LOGS_DEBUG=true \
-e DRONE_LOGS_COLOR=true \
-e DRONE_LOGS_PRETTY=true \
-e DRONE_LOGS_TRACE=true \
-e DRONE_AGENTS_ENABLED=true \ # drone server running with no agents, start runners manually
-e DRONE_GITEA_SKIP_VERIFY=true \ # gitea: use http protocol
-e DRONE_GITEA_SERVER=http://host.docker.internal:13000 \ # gitea: server address with protocol
-e DRONE_GITEA_CLIENT_ID=fd023edb-7976-4d50-a92f-b16612683240 \ # gitea: oauth client id
-e DRONE_GITEA_CLIENT_SECRET=S4Q6PktE3dKNHPUzZrTdyNJsTThwal4doUWf6jf4eRA= \ # gitea: oauth client secret
-e DRONE_RPC_SECRET=d9856af41ffe31f5e8025be020e981be \ # drone: runner shall connect server with this secret
-e DRONE_SERVER_HOST=host.docker.internal:18980 \ # drone: server address without protocol
-e DRONE_SERVER_PROTO=http \ # drone: server with http protocol
drone/drone:1.6.3
访问drone的地址:http://host.docker.internal:18980
,会跳转到gitea进行授权,授权完成后会跳转回drone。这时应该就已经以刚才制作OAuth2应用的用户身份登录drone了。
这里说明下,因为gitea和drone都运行在容器内,他们之间相互访问是通过容器名来进行的,而host主机访问这两者都是通过loopback地址,两者之间存在差异,会导致OAuth的过程失败。因此这里统一使用docker MAC desktop版本的host.docker.internal
域名来贯通内部和外部。但需要额外修改host主机的/etc/hosts
配置,把这个host解析为loopback地址。
刚完成授权的账户是没有任何代码库的,打开drone首页发现是空列表(即便当前通过OAuth2授权的账号在gitea内有代码库):
点击右上角的SYNC
就会开始和gitea同步账户的代码库数据,完成后即可获得所有当前账号在gitea内所拥有的代码库列表:
这时候账号下的drone项目都是未激活
状态,需要点击面板上项目右边的ACTIVATE
进行激活:
点击SAVE
保存即可。经过这几个操作,drone即激活了对这几个代码库的webhook事件监听。
至此为止drone本体的安装基本上完成了,后续还需要配置下runner。
点击右上角的用户头像,然后点击User settings
会打开用户的设置页面。在这个页面上能找到用户的token。如果有需要使用drone API的话(或者是cli命令行工具),这个token是必须的。
服务器设置完成后,如果没有单独安装和运行runner,服务器上所有触发的事件都会是Pending
状态,而且没有任何日志。
关于Pending的debug,可以看下官方的一篇帖子:Builds are Stuck in Pending Status。
Runner的安装见官方文档:Drone Documentation > Installation > Runners。Runner也分不同的类型,一般常用的是:
Runner的类型和pipeline的类型并不要求一致,runner只是pipeline的运行者,不管是什么类型的pipeline,都可以在任何类型的runner上运作。举例来说,一个docker类型的pipeline,在exec runner上就会在主机上启动docker容器并运行,而docker runner则是直接在docker容器内运行。
Exec Runner
Exec Runner需要手动在~/.drone-runner-exec/config
创建一个配置文件:
DRONE_DEBUG=true
DRONE_LOG_FILE=/tmp/drone-runner-exec.txt
DRONE_LOG_FILE_MAX_SIZE=50
DRONE_RPC_DUMP_HTTP=true
DRONE_RPC_DUMP_HTTP_BODY=true
DRONE_RPC_PROTO=http
DRONE_RPC_HOST=host.docker.internal:18980
DRONE_RPC_SECRET=d9856af41ffe31f5e8025be020e981be
DRONE_RUNNER_PATH=$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
更多的配置项可以查阅官方文档:Exec Runner > Installation > Configuration Reference。
配置文件里最重要的是DRONE_RPC_HOST``DRONE_RPC_SECRET
这两项。修改完成后记得重启runner:
$ drone-runner-exec service stop && drone-runner-exec service start
Runner在执行pipeline操作时候的用户即是runner启动时的用户,这需要非常小心,否则会搞错用户导致权限错误。此外,在2.3 workspace的时候也提到过了,在runner执行pipeline的时候,~
指向的是workspace的根目录,而不是用户的home,这点要非常小心。
举例来说,存放在$HOME/.docker
下的凭证在exec runner运行的时候是不可访问的(~
会定位到workspace而不是$HOME
),这会导致私有registry的访问失败。同样的,pipeline运行时的PATH也不同于用户自身的PATH,需要在配置文件中设置好DRONE_RUNNER_PATH
。
Docker Runner
这部分就非常简单了,无非就是一个docker容器,直接使用对应的官方镜像即可:drone/drone-runner-docker。
相关配置项可以查阅官方文档:Docker Runner > Installation > Configuration Reference。
在执行pipeline的时候不可避免需要访问一些密码秘钥之类的信息,而构建过程中,理论上runner只能访问代码库里的内容。这就造成了问题,因为代码库并不是安全的存放秘钥的地方。针对这样的问题,drone有对应的解决方案。
在drone ui仓库的设置中,有一处:
设置完成后,就会保存在drone系统中:
后续在pipeline可以如下的形式进行访问:
kind: pipeline
name: default
steps:
- name: build
image: alpine
environment:
USERNAME:
from_secret: docker_username
PASSWORD:
from_secret: docker_password
更多关于secret的设置及访问,可以参见官方文档:Drone Documentation > Configuration > Secrets。在这方面,drone支持得非常全面,甚至外部的加密设施也可以支持访问。
在pipeline应用的过程中,比较常见的问题是对于私有registry的访问。毕竟现在的CI基本上都围绕着镜像打转,抓取镜像进行部署或者构建镜像向上推送都是常见需求。
官方对此也有解决方案:How to pull private images with 1.0。
上述的内容基本上把CI的最基础的应用都讲到了,如果只是简单的提交代码 > 触发webhook > drone pipeline
这个流程的话,是没有问题的。
那么接下来就可以考虑下稍微复杂点的需求:
然后我简单说下结论:在当前的版本(drone/drone:1.6.3),这个需求做不到
,如果是按官方的API和功能的话。而使用一些非官方的hacking思路,则可以做到基本上一致的效果
。
根据一开始说的,如果单独只是一个构建pipeline,很简单就能做到。而如果要实现刚才列出的需求,就需要在构建pipeline完成并退出之前触发一个额外的事件,只要能触发额外的事件,再配合上:trigger & condition,保证代码库的pipeline启动的时候,只有部署pipeline进入工作,且只有正确的runner实例进入工作,那一切就通了。
那么,能不能手动触发一个部署呢?研究了下官方的资料,发现正常途径基本上做不到,只有一些非正常的思路才可以做到。下面说下思路。
首先想到的是看看官方的API中有没有可用的命令(cli实际上就是官方API的封装):API文档、CLI文档。
发现官方有一个命令:
$ drone build promote --help
NAME:
drone build promote - promote a build
USAGE:
drone build promote [command options] <repo/name> <build> <environment>
OPTIONS:
--param value, -p value custom parameters to be injected into the job environment. Format: KEY=value
--format value format output (default: "...")
结合.drone.yml
使用:
kind: pipeline
type: exec
name: test
platform:
os: darwin
arch: amd64
steps:
- name: test
commands:
- echo "tested"
when:
event:
- tag
---
kind: pipeline
type: exec
name: build
platform:
os: darwin
arch: amd64
steps:
- name: build
commands:
- echo "built"
when:
event:
- tag
depends_on:
- test
---
kind: pipeline
type: exec
name: notify
platform:
os: darwin
arch: amd64
steps:
- name: notify
commands:
- drone build promote fullstack/gateway 100 production
environment:
DRONE_SERVER: http://host.docker.internal:18980
DRONE_TOKEN: K5i8g6PMlLM3tWaPDRagigPkBrl1YKGu
when:
event:
- tag
depends_on:
- build
---
kind: pipeline
type: exec
name: deploy
platform:
os: darwin
arch: amd64
steps:
- name: deploy
commands:
- echo "deployed"
when:
event:
- promote
target:
- production
测试后发现,如果build
参数给的是已经存在的构建的话,则会重复之前的构建流程;而如果给的是一个很大的不存在该build的值(比如上面举例的100),则构建在notify这一步失败,并报错:
+ drone build promote fullstack/gateway 100 production
client error 404: {"message":"sql: no rows in result set"}
查找了下相关资料,并没有找到官方文档,仅在论坛中找到几篇讨论:
根据官方人员的解释:
When you deploy, you are essentially “promoting” a build, which means you need to provide the build number you are promoting, and the environment you are promoting to. For example:
drone deploy octocat/hello-world 42 production
In the above example, you are promoting build 42 to production. This will execute a deployment event in the system with environment name production.
也就是说你只能对已经存在
的build进行触发让这个已经测试构建完成的
build进行部署,而不能从头创建一个新的build。也就是说,上述的工具能大致满足上面提出的那串需求,但本质上还是不同的:
官方有一个插件叫:Downstream Build。乍看之下貌似是满足需求的,但我实际扒了下源码,发现这个插件的本质其实就是封装了下官方的API。所以实际上和4.5.1一样,不行。
后续的思路是尝试使用webhook插件,在构建pipeline完成之后用webhook触发一个构建:Webhook。
文档工作总是半吊子,参数有几个根本不知道具体应该填什么,只能随便尝试下:
$ docker run --rm \
-e PLUGIN_URLS=http://host.docker.internal:18980/hook \
-e PLUGIN_USERNAME=root \
-e PLUGIN_PASSWORD=Abcd1234_ \
-e PLUGIN_DEBUG=true \
-e PLUGIN_SKIP_VERIFY=true \
-e DRONE_REPO_OWNER=fullstack \
-e DRONE_REPO_NAME=gateway \
-e DRONE_COMMIT_SHA=2087d78f48 \
-e DRONE_COMMIT_BRANCH=master \
-e DRONE_BUILD_EVENT=deployment \
plugins/webhook:latest
Webhook 1
URL: http://host.docker.internal:18980/hook
METHOD: POST
HEADERS: map[Authorization:[Basic cm9vdDpBYmNkMTIzNF8=] Content-Type:[application/json]]
REQUEST BODY: {"repo":{"owner":"fullstack","name":"gateway"},"build":{"tag":"","event":"deployment","number":0,"commit":"2087d78f48","ref":"refs/heads/master","branch":"master","author":"","message":"","status":"success","link":"","started":0,"created":0}}
RESPONSE STATUS: 200 OK
RESPONSE BODY:
返回结果是200,正常,但结果本身为空
。查看drone的UI发现实际上没有任何build被触发。这就很无奈了,这里的问题可能出在:
无论如何,后续如果要继续的话就需要看源码了,插件的以及服务器的源码。
到这里就是非正常思路了。
因为gitea可以正常触发drone的webhook,思路就是使用curl仿造gitea的请求,发送到drone。其实drone官方也有对webhook的描述:How to use Global Webhooks,但实在是没什么正式的支持,API之类的易用性的工具都没有,只有这篇文章。
gitea官方有webhook相关的文档:Webhooks。
gitea的钩子可以在这里找到,点击下面的链接可以查看请求细节:
当然这也非常麻烦,而且gitea发送出去的webhook请求里有非常多repo详细信息,如果要做到自动化的话,这块的获取也是很麻烦的一件事情。
另一个非正常思路就是定义一个pipeline,仅在指定的runner上运行,然后命令中执行脚本,查询私有registry,看目标仓库(镜像)的目标tag是否存在,存在的话就进行部署。而在其他的runner上进行构建和镜像推送操作。
这样就能做到一开始提的需求,虽然恶心了点。
EOF