用 Drone 打造 CI/CD flow

Graffine Wu
AsiaYo Engineering
Published in
14 min readAug 6, 2018

在程式開發的生涯裡,你是否曾踩過各種手動上線部署的坑?地雷之多也許都可以寫成一本書。如果沒有,那恭喜你,你應該有個強大的運維團隊在背後 cover。如果有,那這篇文章也許可以為你省下一些麻煩。本文的內容主要探討如何使用 Drone 這套工具來打造專屬於你的自動化 CI/CD flow

石碇 鱷魚島

以新創公司初期來談,在開發的資源上已經十分不足,很少會把時間投入在基礎建設上,甚至可能連版本控制都來不及做。隨著營運越來越上軌道,團隊茁壯之後,為了將開發的資源更有效地運用,導入版本控制以及自動化部屬流程就變成是個趨勢。本文主要描述我們如何從 FTP 到 git,又從 git 變化成 drone 這套服務。

什麼是 CI/CD?

文章開始前,我們先簡單介紹一下何謂 CI/CD

  • CI - Continuous Integration (持續整合)
  • CD - Continuous Delivery/Deployment (持續交付/部署)

這兩者的目的其實很單純,是為了讓團隊開發的功能可以透過自動化的方式進行小而且頻繁的測試部署到線上環境後交付給使用者。可以歸納成三個 actions:

  1. Continue to test every change.
  2. Continue to build every change.
  3. Continue to deploy every change.

這其實也是現代敏捷開發所秉持的精神,快速地將功能丟到市場上驗證,降低團隊開發與市場需求不一致時的反應時間。

為何選擇 Drone?

有許多平台或者工具都可以做到 CI/CD 的功能,諸如 GitLab CI, CircleCI, Jenkins, Travis CI 等等,那為何我們選擇了 Drone 呢?原因無他,只有一個字: (誤)。

在這邊先簡單介紹一下個人覺得 Drone 的優點

  • Open source — 在 github 上有將近 15000 顆星星,社群也算活躍。有提供企業版,但一般使用已經非常足夠。
  • 使用 Golang 撰寫 —service 啟動的速度非常快。
  • 整合多家 git service 平台 — Github/Bitbucket/Gitlab/Gogs。
  • Docker based — 整個 drone 的 flow 都是使用 docker 作為基底,可以快速部署到不同平台上。
  • Control flow by pipeline — CI/CD 的流程可以透過一個 pipeline 來描述,可讀性好上許多。
  • Plugable — Drone 允許你使用 docker 自行開發 plugin,在客製化上十分的方便。

Drone 當然也有缺點,這邊列出一些個人的觀點

  • Too young —目前(2018/08/06)的版本是 0.8.6,還有很多功能尚待完善 ,但社群十分活躍,可期待未來的發展。
  • Lacking of detail document — 雖然 drone 有提供一份 guideline document,但實際執行起來會發現有些細節在文件上沒有說明,此時你可能會需要翻一下 drone 的 discuss forum 以及 Github issue list
  • Plugin may not fit to you — drone 有官方提供或認可的 plugin 方便你使用,但有時可能不完全符合需求,你會需要自己實作 plugin。
  • Web ui is too simple — drone 有提供 web 管理介面,但老實說功能只能算是堪用,還有很多進步的空間。

如何啟動一個 Drone 服務?

Drone 本身可分為 server 與 agent 兩個服務,server 用來與 git service 溝通,透過 webhook 的方式抓取到 git server 的 event ,轉化成一個 task 丟給 agent 執行。 agent 則是實際上依照 pipeline 執行動作的角色,基本上可以同時存在多個 agents 來執行 pipeline 的動作。

安裝的方式非常簡單,以 bitbucket 為例,你只需要準備一個如下的 docker-compose.yml:

version: '2'services:
drone-server:
image: drone/drone:latest
ports:
- 80:8000
- 9000
restart: always
environment:
- DRONE_OPEN=true
- DRONE_ADMIN=${DRONE_ADMIN}
- DRONE_HOST=${DRONE_HOST}
- DRONE_BITBUCKET=true
- DRONE_BITBUCKET_CLIENT=${DRONE_BITBUCKET_CLIENT}
- DRONE_BITBUCKET_SECRET=${DRONE_BITBUCKET_SECRET}
- DRONE_SECRET=${DRONE_SECRET}
drone-agent:
image: drone/agent:latest
restart: always
depends_on:
- drone-server
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DRONE_SERVER=drone-server:9000
- DRONE_SECRET=${DRONE_SECRET}

然後使用 docker-compose up,這樣子就可以啟動一個包含 server 與 agent 的 drone 服務。

Drone 如何與 git server 溝通?

底下以 bitbucket 為例。

Bitbucket settings page

先進入 bitbucket 的 settings 頁面,在 OAuth 底下選擇 Add consumer,之後有兩個地方需要設定。

Authorization callback URL
Drone permission

Authorization callback URL

這個 URL 的格式為http://<public.domain>/authorize。登入 drone 的 web 頁面時,drone 會透過此 URL 進行身份認證,從而取得你的 bitbucket 裡的 repositories。

執得注意的是這個 domain 必須要是 public domain,無法使用 localhost,因此測試時推薦使用 ngrok 這個套件取得 public domain。

從 ngrok 取得的範例:http://b8485e4e.ngrok.io/authorize

Drone permission

Drone 需要的權限如下

  • Account: Email
  • Account: Read
  • Team Membership: Read
  • Repositories: Read
  • Webhooks: Read and Write

左圖的 Repositories 多了 Write 的權限,之後會再解釋用途為何。

設定完之後,你可以取得一組 key 以及 secret,接著將這組密鑰複製到 drone 的 docker-compose.yml 裡,drone server 的部分就算是大功告成,可以進入 drone 的 web page 了。

Drone web page

Agent pipeline

drone 的 pipeline 用來描述 CI/CD flow 的每個 steps,每個 agent 就是依照這個 pipeline 執行工作。你需要撰寫一個 .drone.yml,並將其放置在 repository 的根目錄底下。底下是一個官方的範例:

pipeline:
backend:
image: golang
commands:
- go build
- go test
frontend:
image: node
commands:
- npm install
- npm run test
- npm run build

pipeline 的撰寫在此不多加描述,可以參考官方文件

我們在設計 pipeline flow 時有幾個目標:

  1. Code review 並且 merge 後,可自動產生 docker image 並自動部署至遠端伺服器。
  2. 自動檢查 coding style 以及跑 unit testing。
  3. Release 時,自動為 docker image 以及 git commit 產生 semantic 版號,並且產生 change log。
  4. 部署完成後,發通知到 slack channel 上。

整體的流程在線上與測試環境稍有不同,如下圖所示:

測試環境

Testing environment flow

測試環境我們採用全自動化流程,因此只要測試跑過了,就會產生 docker image,並且部署到測試機器上。程式的編譯方式可以包裝在 drone 的 pipeline step 裡,docker 映像檔則可以透過撰寫 Dockerfile 處理。透過這樣的方式我們省略了人工介入的步驟,標準化了每次程式產出的結果。

線上環境

Production environment

線上環境與測試環境的流程最大的不同有兩點:

  1. 會針對 git commit point 以及 docker image 產出版號並更新 changelog
  2. 只做到 Continuous delivery

第一點是希望每次發版都能有 changelog 以供內部追蹤不同版本的差異。我們採用 Semantic Version 作為版號的規則,而 changelog 的部分則使用 commitizen 這套工具來處理。

如果你還記得的話,剛剛 drone 要求 write repository 的權限就是因為我們需要讓 drone 去更新 git 的資料。

第二點則是希望線上環境的部署必定要透過人工處理,以避免上線後有未預知的情況發生造成損失。

原則上我們可以透過高測試覆蓋率以及自動測試來儘量減少未知的情況,但我們內部的測試案例尚未進入穩定期,因此我們在線上環境採用半自動化的方式。

那線上環境該怎麼部署呢?其實很簡單,只需要在 drone pipeline 裡加入一個只供線上部署使用的 step,然後使用 drone 的 cli 執行部署

drone deploy <repo owner>/<repo name> <build number> <environment>

附上一個我們使用的範例給大家參考。

clone:
git:
image: plugins/git
tags: true
pipeline:
backend-linter:
image: asia.gcr.io/ay-gcp-999/php-tester:latest
pull: true
commands:
- run linter
when:
event:
exclude: [ deployment ]
backend-tester:
image: asia.gcr.io/ay-gcp-999/php-tester:latest
pull: true
secrets: [ git_ssh_key ]
commands:
- run unit test
when:
event:
exclude: [ deployment ]
pre-build:
image: docker:git
pull: true
secrets: [ gcs_upload_key ]
commands:
- prepare build data
when:
event:
exclude: [ deployment ]
production-changelog-tag:
image: asia.gcr.io/ay-gcp-999/commitizen
pull: true
commands:
- drone-changelog-tag $DRONE_BRANCH $DRONE_BUILD_NUMBER
when:
branch: master
event:
exclude: [ deployment ]
gcr-push:
image: plugins/gcr
pull: true
registry: <private gcr registry>
repo: <gcr repo folder>
tags:
- ${DRONE_COMMIT_BRANCH}
- ${DRONE_COMMIT_BRANCH}-${DRONE_BUILD_NUMBER}
- ${DRONE_COMMIT_BRANCH}-${DRONE_COMMIT:0:8}
secrets: [ gcr_json_key ]
when:
event:
exclude: [ deployment ]

deploy-testing:
image: appleboy/drone-ssh
pull: true
host: <host>
username: <username>
secrets:
- source: deploy_ssh_key
target: ssh_key
- source: gcr_json_key
target: gcr_json_key
envs: [ gcr_json_key ]
script:
- up docker
when:
branch: testing
event: push
deploy-production:
image: appleboy/drone-ssh
pull: true
host: <host>
username: <username>
secrets:
- source: deploy_ssh_key
target: ssh_key
- source: gcr_json_key
target: gcr_json_key
envs: [ gcr_json_key ]
script:
- up docker
when:
environment: production
event: deployment
slack:
image: plugins/slack
pull: true
webhook: <slack webhook url>
channel: <slack channel>
username: <usename>
when:
status: [ success, failure ]
template: >
{{#success build.status}}
{{build.author}} trigger {{repo.name}} build {{build.number}} by {{build.event}} to branch {{build.branch}} succeeded.
<{{build.link}}>
{{else}}
{{repo.name}} build {{build.number}} to branch {{build.branch}} failed. Fix me please.
<{{build.link}}>
{{/success}}
branches: [ master, testing ]

小結

整體來說,這次使用的經驗算是蠻不錯的,相比我之前曾經用過的一些工具來說,drone 要客製化功能其實只要包一個 docker 映像檔就可以達到,需要進一步標準化的話也可以使用許多不同的程式語言撰寫 drone plugin。如果有 plugin 可以支援多種部署方法論就完美了 (想太多…)

如果公司打算往容器化的方向邁進,可以試試這套工具,相信你一定不會失望。

撰寫的過程中我們有遇到一些狀況,例如找不到私有的 docker registery,抓不到 git 的 tag 所以無法正確更新版號,更新版號之後又重新觸發 drone flow 造成 infinite loop 等等,礙於篇幅,之後有機會的話再與大家分享。若有任何問題,也歡迎與我討論。

--

--

Graffine Wu
AsiaYo Engineering

Senior backend engineer at AsiaYo. Interested in system architecture and VIM.