怡红公子 360云计算
女主宣言
本文的作者为奇舞团前端工程师怡红公子,他是基于ThinkJS 开发的高人气开源博客平台 Firekylin (over 1.2k Stars) 的作者,今天他分享的是使用 Drone.io 取代 travis-ci 做日常开发持续集成工作的经验。本文最先发布于怡红院落,转载已获取作者授权。
PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!
前言
我在很多 Github 项目中都会使用 travis-ci 来做自动化构建任务,其中的一项主要内容就是代码提交后自动执行单元测试计算测试覆盖率。但是 travis-ci 对于其它平台(例如 Gitlab)以及公司内网仓库来说都是不支持的,所以萌生了想要找另外一款 CI 工具代替 travis-ci 的想法。
1
基于Docker的CI工具 - Drone
对比了几款 CI 产品之后,我最终选择了 Drone。它是一款使用 Go 开发的开源的 CI 自动构建平台,能够单独部署,支持常见的 Git 仓库,例如 Github, Gitlab, Bitbucket 以及 Gogs 等。Drone 主要有以下几个方面吸引我:
原生 Docker 支持
环境部署算是自动化构建比较复杂的一点了,目前最优解就是使用 docker 容器化技术打包我们的环境。原生 docker 的支持让我们不需要额外的在构建脚本中增加 docker 的命令,简单配置就可以为我们带来集成 docker 所引入的一系列的好处:环境隔离、标准化镜像。并且它的每一个插件都是一个镜像,让我们的自动构建环境模块化。
新版的 Drone 将其拆分成了 Server 端和 Agent 端,Server 用来处理任务分发,Agent 用来执行自动化构建任务。简单增加 Agent 容器实例,我们就可以非常方便的横向扩容 Drone。
PipeLine AS Code
在项目根目录有一个 .drone.yml 的文件,这个是 Drone 构建脚本的配置文件,它随项目一块进行版本管理,开发者不需要额外再去维护一个配置脚本。其实现代 CI 程序都是这么做了,这个主要是相对于 Jekins 来说的。虽然 Jekins 也有插件支持,但毕竟还是需要配置。
另外就是基于 yaml 的配置文件非常简单(当然也要视需求而定),例如一个简单的构建脚本如下:
pipeline:
build:
image: node:6.6.0
commands:
- npm install --silent
- npm test
比起 Jekins 纷繁复杂的配置,Drone 几行就搞定了构建的需求。
丰富的插件支持
http://plugins.drone.io/ 这个是 Drone 的插件商店,包含了常见的一些需求插件,例如:
构建后发送消息:slack, telegram, line, facebook, discord, gitter, email...
构建成功后发布:npm, docker, github release, google container...
构建成功后部署:AWS, Kubernetes, rsync, scp, ftp...
pipeline 中简单的增加插件镜像配置就可以支持插件,例如:
pipeline:
build:
image: node:6.6.0
commands:
- npm install --silent
- npm test
publish:
image: plugins/npm
secret: [ npm_username, npm_pwd, npm_email ]
username: ${NPM_USERNAME}
password: ${NPM_PWD}
email: ${NPM_EMAIL}
when:
event: [ tag ]
notify:
image: appleboy/drone-telegram
secret: [ telegram_token, telegram_to ]
token: ${TELEGRAM_TOKEN}
to: ${TELEGRAM_TO}
由于 Drone 是基于 Docker 的,甚至是所有的插件也都是一个 docker 镜像。这代表着我们可以使用任何语言来开发 Drone 插件,最后只要打包成镜像就好了。
其它 CI 工具
Drone 相对于 Jekins 来说优势比较明显,复杂的配置以及需要插件支持的构建脚本 pipeline 化都是我没考虑它的原因。不过 Jekins 其丰富的插件扩展,针对于无项目的自动化构建来说还是非常不错的。Gitlab-CI 也是一款不错的工具,不过从名字上也能看出它的缺点,就是只支持 Gitlab,而且无法扩展配置文件,对于正好在使用 Gitlab 且需求不高的同学可以试试。
2
安装配置 Drone
下面我将来介绍下如何配置安装 Drone。官方推荐使用 docker-compose 来启动服务,所以首先我们要准备好 docker 和 docker-compose。
安装 docker
docker 服务提供了桌面版,服务器版和云服务版不同操作系统的多种支持,可以在官方文档中找到对应的安装方法。这里以 Debain 服务器安装为例。Docker 支持 Debian 7.7+ 以上的系统版本, 需要 3.10 版本以上的 Linux 内核环境。
首先我们添加 docker 源的密钥:
$ sudo apt-get update
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common
$ curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add -
然后我们添加 Docker 官方源并安装 Docker:
$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) \
stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce
执行以下命令确保我们已经安装正确:
$ sudo docker run hello-world
相比 docker 的安装,docker-compose 的安装就比较简单了。它是用 python 写的一个 docker 多镜像编排启动的工具,我们可以直接使用 pip 来安装它:
$ pip install docker-compose
Git仓库准备
CI 可持续构建的一个大前提是基于仓库的,所有的构建第一步都需要去仓库拉取代码。Drone 支持 Github,Gitlab,Bitbucket,Gogs, Gitea, coding 多个仓库类型,其中支持 OAuth 的仓库需要在网站申请密钥。这里以 Github 为例。在 New Oauth Application 页申请密钥,其中 callback URL 需要填写为<你的CI地址>/authorize。申请好后记录下你的 client id 和 client secret 以便后续使用。
创建 compose 配置文件
我们新建一个空目录防止我们的配置文件文件:
$ mkdir drone
$ cd drone
$ vim docker-compose.yml
在配置文件中,我们设置 docker-compose.yml 的格式为 2 号版本,定义两种服务。
drone-server :使用 drone/drone:0.8.2 版本镜像,将启动监听 3800 上的主 Drone 服务容器,9000 端口来开放给 Agent。我们将在容器内挂载 /etc/drone 目录,以便 drone 可以保留数据。配置服务自动重新启动,并添加一些环境变量。
drone-agent:使用 drone/agent:0.8.2 版本镜像,将 docker 启动句柄挂载到容器 /var/run/docker.sock 文件中,以便 drone 可以使用 docker 来执行镜像构建任务。环境变量中需要配置 drone server 的地址以及 server 的密钥,以便于 server 进行通信。
version: '2'
services:
drone-server:
image: drone/drone:0.8.2
ports:
- 3800:8000
- 9000
volumes:
- /etc/drone:/var/lib/drone/
restart: always
environment:
# 是否允许注册,false 后只有在 DRONE_ADMIN 变量中指定的账户才能登录
- DRONE_OPEN=true
# Drone可公开访问的地址
- DRONE_HOST=http://ci.eming.li
# 配置 Git 仓库,只能同时使用一种仓库
# Github 仓库需要配置上问申请到的 client id 和 client secret
- DRONE_GITHUB=true
- DRONE_GITHUB_CLIENT=xxxxx
- DRONE_GITHUB_SECRET=xxxxx
# Gogs 配置仓库地址即可
- DRONE_GOGS=false
- DRONE_GOGS_URL=http://git.eming.li
# Drone Server 和 Agent 的通信密钥
- DRONE_SECRET=xxxxx
drone-agent:
image: drone/agent:0.8.2
command: agent
restart: always
depends_on:
- drone-server
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
# 配置 SERVER 地址
- DRONE_SERVER=drone-server:9000
# 配置与 SERVER 通信的密钥,需要与 Server 配置的保持一致
- DRONE_SECRET=xxxxx
其中的通信密钥相当于 drone 的密码,最好为一个长串的随机字串防止被破解。可以在命令行中使用以下内容生成
$ LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 65 && echo
配置文件完成之后,我们就可以使用以下命令启动服务了:
$ docker-compose -f /etc/drone/docker-compose up
docker-compose 会自动帮我们去下载镜像并根据配置初始化容器。一切就绪之后,我们使用 http://<host>:3800 就可以访问到 drone 了。
配置 Nginx
带端口访问总是让人不爽,老方法我们配置下 Nginx 做下反向代理就可以了,以下是具体的 nginx 配置文件示例:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ci.eming.li;
set $drone_port 3800;
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:$drone_port;
proxy_redirect off;
proxy_http_version 1.1;
proxy_buffering off;
chunked_transfer_encoding off;
}
location ~* /ws {
proxy_pass http://127.0.0.1:$drone_port;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
}
}
修改 server_name 和 $drone_port 为正确的值即可,最后记得别忘了重启 Nginx 服务。这样我们就能直接使用 http://ci.eming.li 来访问 drone 了。
进程守护
刚才使用 docker-compose up 命令启动如果退出终端了之后服务就会停止,所以我们需要后台执行。我们可以直接使用 nohup 方法启动:
$ nohup docker-compose -f docker-compose.yml up &
除了使用 nohup 之外,我们还可以使用 systemctl 来启动进程。我们创建一个 drone.service 服务文件:
$ vim /etc/systemd/system/drone.service
复制以下内容:
[Unit]
Description=Drone server
After=docker.service nginx.service
[Service]
Restart=always
ExecStart=/usr/local/bin/docker-compose -f /etc/drone/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose -f /etc/drone/docker-compose.yml stop
[Install]
WantedBy=multi-user.target
第一部分告诉 systemd 在 Docker 和 Nginx 可用之后启动此服务。 第二部分告诉 init 系统在发生故障时自动重新启动服务。 然后,它使用 Docker Compose 和我们之前创建的配置文件定义启动和停止 Drone 服务的命令。 最后,最后一节定义了如何使服务在启动时启动。完成后保存文件并使用如下命令启动服务:
$ systemctl start drone
使用如下命令可以查看服务启动状态, 如状态显示为active (running)则服务运行正常。
$ systemctl status drone
这样我们的安装过程就结束了,访问 Drone,使用对应仓库的账户登录。如果是 Github 的话会使用 OAuth 连接到 Github 进行授权申请,如果是 Gogs 则需要输入 Gogs 的账号和密码。
3
User Guide
接下来我将来介绍重头戏——如何在项目中使用 Drone 完成自动化构建任务。首先我们要在 drone 中开启自动构建,实际上它会在对象的 Git 仓库中添加 webhooks 方便当开发者执行对应 git 操作后触发自动构建任务。
.drone.yml
在《基于DOCKER的CI工具—DRONE》一文中我们有说过它的每一个构建插件都是镜像,能够模块化的配置我们的构建任务。将构建任务理解成是一条管道,项目流经管道做各种具体的任务。以下是一个简单的管道流程,代码克隆下来后执行安装并执行测试,完成后使用使用 Telegram 通知用户。
通过编辑放置在项目的根目录下的 .drone.yml 文件,我们可以对构建流进行配置。下面就是上图构建流程的配置:
pipeline:
build:
image: node:6.6.0
commands:
- npm install --silent
- npm test
telegram:
image: appleboy/drone-telegram
token: XXX:XXXXX
to: 337635385
message: >
{{#success build.status}}
主人,{{repo.owner}}/{{repo.name}}第{{build.number}}次构建成功!
{{else}}
主人,{{repo.owner}}/{{repo.name}}第{{build.number}}次构建失败了,快来修理下吧。
{{/success}}
在配置文件中我们总共定义了两个管道任务:
build: 我们为 build 任务指定了 node:6.6.0 镜像,并在镜像中执行 npm install --silent 命令安装依赖。最后执行 npm test 来运行单元测试脚本。
telegram: 我们为 telegram 任务指定了 appleboy/brone-telegram 镜像,并定义了一些镜像需要的环境参数。包括 telegram 的机器人 token,指定了消息的发送对象 to 以及消息的模板。
将其保存提交后就能正常触发任务并使用 telegram 接收消息了。
telegram
telegram 插件需要设置 bot token,没有的需要先关注 @BotFather 并输入 /netbot 命令创建一个 bot,这样你就能获取到 token 了。
而 to 参数需要配置的是发送用户的 ID,这个比较坑爹了。最开始以为是 username,输入之后一直没有反应,后来才反应过来可能是唯一 ID。不过 telegram ID 的获取比较困难,有人为此特地做了一个机器人。关注 @userinfobot 后和它发个消息它就会告诉你你自己的 ID 啦。这里有个小技巧是把别人的消息转发给它,它会给出对象的 ID。
关于 telegram 插件更多的配置可以参考:http://plugins.drone.io/appleboy/drone-telegram/
Secrets
由于我们将 token 直接写在了 .drone.yml 的配置文件中,项目其它人也都可见,这会带来一些潜在的安全问题。为此我们可以使用 drone 的 secrets 功能。我们小改一下配置文件:
telegram:
image: appleboy/drone-telegram
secrets: [ telegram_token, telegram_to ]
token: ${TELEGRAM_TOKEN}
to: ${TELEGRAM_TO}
同时我们在网站后台添加两个名为 telegram_token 和 telegram_to 的密钥:
保存后重新执行构建任务即可,这样我们就做到了隐藏敏感信息的目的。当然其实这么做也有缺陷,因为在网站后台添加的密钥是对管道中所有的构建插件全局共享的,如果开发者在管道中增加其它插件并 echo 或者其它操作,一样也是能获取到密钥的。为此我们就需要使用 drone-cli 命令来添加密钥,它支持指定密钥的作用域镜像。
$ drone secrets add \
--repository=lizheming/qalarm \
--name=telegrame_to \
--value=337635385 \
--image=appleboy/telegram
4
Troubleshooting
构建任务无故退出
实际使用过程中我发现我的构建任务总是莫名其妙就失败,提示我被 killed 掉,并显示 exit code 137。最开始我一直在查 137 退出码,发现就是 kill -9 的退出,不明所以。后来偶然尝试以 docker killed 为关键词搜索,才发现原来是内存爆了 docker 执行 OOM 把容器杀掉了。后来按照作者建议给机器加到 2G 的内存就没问题了。
5
总结
基本上 Drone 的流程是我想要的 CI 模式了。不过人无完人,drone 也有一些不足,主要是:
不同版本的文档太多,老文档的一些功能都不支持了。
社区还不够发展,找个问题比较困难。不过好在有个官方论坛,作者回复倒是挺勤快的。
前端界面比较简单,很多功能还是得依靠命令行。
不过这些问题都不影响我对它的看法,相信它会越来越好。
“
相关资料:
https://aisensiy.github.io/2017/08/04/drone-best-ci/
https://blog.wu-boy.com/2017/09/why-i-choose-drone-as-ci-cd-tool/
http://docs.drone.io/installation/
https://www.howtoing.com/how-to-install-and-configure-drone-on-ubuntu-16-04
https://discourse.drone.io/t/http-request-in-drone-are-all-connect-timeout/1083/2
https://github.com/drone/drone/issues/825
https://github.com/drone/drone/issues/390
http://docs.drone.io/getting-started/
https://core.telegram.org/bots/api
https://blog.wu-boy.com/2017/11/drone-secret-security/