本文的作者為奇舞團前端工程師怡紅公子,他是基于ThinkJS 開發的高人氣開源部落格平台 Firekylin (over 1.2k Stars) 的作者,今天他分享的是使用 Drone.io 取代 travis-ci 做日常開發持續內建工作的經驗。
我在很多 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 中簡單的增加插件鏡像配置就可以支援插件,例如:
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 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 以便後續使用。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLi0zaHRGcWdUYuVzVa9GczoVdG1mWfVGc5RHLwkzX39GZhh2csATMflHLwEzX4xSZz91ZsADMx8FdsYkRGZkRG9lcvx2bjxSa2EWNhJTW1AlUxEFeVRUUfRHelRHL2EzXlpXazxyayFWbyVGdhd3LcV2Zh1Wa9M3clN2byBXLzN3btg3PnVGcq5SNyAzNzYzM4YDM3kjM0QTY0UjMwUDZ3ATO4UWYkJTOh9CX1AzLcdDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.jpeg)
建立 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
depends_on:
- drone-server
- /var/run/docker.sock:/var/run/docker.sock
# 配置 SERVER 位址
- DRONE_SERVER=drone-server:9000
# 配置與 SERVER 通信的密鑰,需要與 Server 配置的保持一緻
其中的通信密鑰相當于 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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
修改 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 檔案,我們可以對建構流進行配置。下面就是上圖建構流程的配置:
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:
secrets: [ telegram_token, 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 也有一些不足,主要是:
- 不同版本的文檔太多,老文檔的一些功能都不支援了。
- 社群還不夠發展,找個問題比較困難。不過好在有個官方論壇,作者回複倒是挺勤快的。
- 前端界面比較簡單,很多功能還是得依靠指令行。
不過這些問題都不影響我對它的看法,相信它會越來越好。