原文出處:DevOps:持續整合&持續交付(Docker、CircleCI、AWS)javascript
這篇文章將一步一步介紹如何使用 Docker、GitHub Flow、CircleCI、AWS Elastic Beanstalk 與 Slack 來完成持續整合與持續交付的開發流程。php
持續整合&持續交付(Continuous Integration & Continous Delivery),簡稱 CI & CD,具體介紹能夠參考「山姆鍋對持續整合、持續部署、持續交付的定義」這篇文章。html
簡單來說就是盡量減少手動人力,將一些平常工做交給自動化工具。例如:環境建置、單元測試、日誌紀錄、產品部署。java
安裝:node
- node: 0.10
這篇文章以 Node.js 的應用程式做為範例,其餘語言(Ruby on Rails、Python、PHP)也同樣適用此工做流程。python
創建一個專案資料夾(這裡以 hello-ci-workflow
為例):git
bash$ mkdir hello-ci-workflow $ cd hello-ci-workflow
初始化 Node.js 的環境,填寫一些資料之後會在目錄下產生一個 package.json
的檔案:github
bash$ npm init
安裝 Node.js 的 web framework,以 Express 為例:web
bash$ npm install express --save
--save
:寫入package.json
的 dependencies。docker
完成之後,package.json
大概會長這個樣子:
json// package.json { "name": "hello-ci-workflow", "main": "index.js", "dependencies": { "express": "^4.12.3" }, "scripts": { "start": "node index.js" } }
在 index.js
裡寫一段簡單的 Hello World! 的程式:
javascript// index.js var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World!'); }); var server = app.listen(3000, function () { var host = server.address().address; var port = server.address().port; console.log('Example app listening at http://%s:%s', host, port); });
執行 npm start
或 node index.js
:
bash$ npm start
打開瀏覽器 http://localhost:3000
看結果:
安裝 Node.js 的單元測試,以 Mocha 為例:
bash$ npm install mocha --save-dev
--save-dev
: 寫入package.json
的 devDependencies,正式上線環境不會被安裝。
json// package.json { "name": "hello-ci-workflow", "main": "index.js", "dependencies": { "express": "^4.12.3" }, "devDependencies": { "mocha": "^2.2.4" }, "scripts": { "start": "node index.js" } }
根目錄 test
資料夾,並新增一個測試腳本 test.js
:
bash$ mkdir test $ cd test $ touch test.js
加入一筆錯誤的測試 assert.equal(1, [1,2,3].indexOf(0))
:
javascript// test/test.js var assert = require("assert") describe('Array', function(){ describe('#indexOf()', function(){ it('should return -1 when the value is not present', function(){ assert.equal(1, [1,2,3].indexOf(0)); }) }) })
執行 mocha 測試:
bash$ ./node_modules/.bin/mocha Array #indexOf() 1) should return -1 when the value is not present 0 passing (9ms) 1 failing
結果顯示 1 failing
,測試沒通過,因為 [1,2,3].indexOf(0)
回傳的值不等於 -1
。
將 test.js
的測試修正:
javascript// test/test.js assert.equal(-1, [1,2,3].indexOf(0));
再次執行 mocha 測試:
bash$ ./node_modules/.bin/mocha Array #indexOf() ✓ should return -1 when the value is not present 1 passing (6ms)
結果顯示 1 passing
,通過測試。
初始化 git 環境:
bash$ git init .
輸入 git status
會顯示目前哪些檔案有過更動:
bash$ git status On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) index.js node_modules/ package.json test/
將 node_modules
加到 .gitignore
黑名單,因為這個資料夾是由 npm install
自動產生的,不須要放到 GitHub 上:
yaml# .gitignore # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules
將更動 commit:
bash$ git add . $ git commit -m "first commit"
打開 GitHub,新增一個 repository:
輸入 repository 的名稱,以 hello-ci-workflow
為例:
使用 git remote add
將新創建的 GitHub repository 加入到 remote:
bash$ git remote add origin https://github.com/<USER_NAME>/hello-ci-workflow.git
<USER_NAME>
改爲本身的帳號。
使用 git push
將程式碼傳到 GitHub:
bash$ git push -u origin master
成功之後前往 https://github.com/<USER_NAME>/hello-ci-workflow
就能夠看到剛才上傳的檔案:
帳號:CircleCI
點選左邊欄的 Add Projects
按鈕:
選擇本身的 GitHub 帳號:
搜尋要加入的 GitHub repository,然後點選 Build project
按鈕,以 hello-ci-workflow
為例:
完成之後 CircleCI 就會自動執行第一次的建構,不過因為還沒加入測試腳本,因此建構結果會顯示 no test:
在專案根目錄底下創建一個 circle.yml
,並加入 mocha test:
yaml# circle.yml machine: node: version: 0.10 test: override: - ./node_modules/.bin/mocha
完成之後將檔案 push 上 GitHub:
bash$ git add circle.yml $ git cimmit "add circle.yml" $ git push
Push 成功之後,CircleCI 會自動觸發建構和測試:
測試通過,建置成功:
目前開發中比較經常使用的 workflow 有 Git flow 和 GitHub flow 兩種,能夠參考如下幾篇文章:
Git flow:
GitHub flow:
這裡我們使用 GitHub flow,它的核心精神是:
為了確保 master 這條主線上的程式碼都是穩定的,因此建議開發者依照不一樣的功能、創建不一樣的分支,這裡以 test-github-flow
為例,使用 git branch
新增分支、然後 git checkout
切換分支:
bash$ git branch test-github-flow $ git checkout test-github-flow
在 test.js
裡加入一行錯誤的測試 assert.equal(3, [1,2,3].indexOf(5))
:
javascript// test/test.js // ... assert.equal(3, [1,2,3].indexOf(5));
bash$ git add test/test.js $ git commit -m "add a error test case"
Push 到 GitHub 的 test-github-flow 分支:
bash$ git push -u origin test-github-flow
打開 GitHub 之後,會出現 test-github-flow
分支的 push commits,點選旁邊的 Compare & pull request
按鈕:
點選之後會進入 Open a pull request 的填寫頁面,選擇想要 merge 的分支、輸入描述之後,點選 Create pull request
按鈕:
新增一個 pull request 之後,其餘人就會在 GitHub 上出現通知:
點進去之後能夠看見相關的 commits 與留言,可是下面有一個紅紅大大的叉叉;因為每次 GitHub 只要有新的 push,就會觸發 CircleCI 的自動建置和測試,並且顯示結果在 GitHub 上:
點選叉叉,前往 CircleCI 查看錯誤緣由:
就會發現剛剛 push 到 test-github-flow 的測試沒通過:
回到 GitHub,因為測試沒通過,因此審查者不能讓這筆 pull request 被 merge 回 master。
找到剛剛 commit 的那段程式碼,留言告知請開發者修正錯誤之後,再從新 commit push 上來:
修正 test.js
的測試腳本:
javascript// test/test.js // ... assert.equal(-1, [1,2,3].indexOf(5));
再次 commit & push:
bash$ git add test/test.js $ git commit -m "fix error test case" $ git push
回到 GitHub 的 pull request 頁面,能夠看到最新一筆的 commit 成功通過 CircleCI 的測試了:
審查之後,確定沒有問題,就能夠點選 Merge pull request
的按鈕,將 test-github-flow
的程式碼 merge 回主線 master
:
安裝:
- boot2docker: 1.5(Mac only)
- docker: 1.5
什麼是 Docker?為什麼要用它?
因為 Docker 最近很火,因此網路上不缺關於介紹它的文章,原諒我這裡只稍微提一下:
以往開發人員面對開發環境不一樣的問題,經常出現「明明在個人電腦上能夠跑」的囧境,因此為瞭解決這類問題,一般會使用虛擬機器(VM)搭配一些工具(Vagrant、Chef)來協助統一開發人員、測試人員、上線產品的執行環境。
Docker 也是類似的解決方案,不一樣於 VM 的是,Docker 運行起來更輕巧、可攜度更高。配置好一份設定之後,就能夠讓你們馬上進入開發狀況,減少沒必要要的環境問題,提高效率。
在專案根目錄底下創建一個 Dockerfile
:
yaml# Dockerfile # 從 [Docker Hub](https://hub.docker.com/) 安裝 Node.js image。 FROM node:0.10 # 設定 container 的預設目錄位置 WORKDIR /hello-ci-workflow # 將專案根目錄的檔案加入至 container # 安裝 npm package ADD . /hello-ci-workflow RUN npm install # 開放 container 的 3000 port EXPOSE 3000 CMD npm start
使用 docker build
建構您的 image:
bash$ docker build -t hello-ci-workflow .
-t hello-ci-workflow
是 image 名稱。
使用 docker run
執行您的 image:
bash$ docker run -p 3000:3000 -d hello-ci-workflow
-d
在背景執行 node,可使用docker logs
看執行結果。
打開瀏覽器 http://localhost:3000
看結果:
其實每一次都要
build
和run
還蠻麻煩的,推薦能夠試試 Docker Compose,用起來有點像 Vagrant。
修改 circle.yml
:
yaml# circle.yml machine: # 環境改爲 docker services: - docker dependencies: override: # 建構方式使用 docker build - docker build -t hello-ci-workflow . test: override: - ./node_modules/.bin/mocha # 使用 curl 測試 docker 是否有順利執行 node - docker run -d -p 3000:3000 hello-ci-workflow; sleep 10 - curl --retry 10 --retry-delay 5 -v http://localhost:3000
Push 更新到 GitHub:
bash$ git add Dockerfile circle.yml $ git commit -m "add Docker" $ git push
查看 CircleCI 建構&測試結果:
帳號:Amazon Web Services
安裝:AWS EB CLI: 3.x
最後要將程式上線啦!現在 PaaS 雲端平臺的選擇很是多(Heroku、Google App Engine、Azure、OpenShift、Linode),這裡我選擇 Amazon 推出的 Elastic Beanstalk 當做範例,如下是它的特點:
初始化 EB 環境:
bash$ eb init -p docker
-p
能夠指定 EB 的應用平臺,例如 php 之類;這裡使用 docker。
該命令將提示您配置各種設置。 按 Enter 鍵接受預設值。
若是你已經存有一組 AWS EB 權限的憑證,該命令會自動使用它。
否則,它會提示您輸入Access key ID
和Secret access key
,必須前往 AWS IAM 創建一組。
初始化成功之後,可使用 eb create
快速創建各種不一樣的環境,例如:development, staging, production;這裡我們以 env-development
為例:
bash$ eb create env-development
等待 Elastic Beanstalk 完成環境的創建。 當它完成之後,您的應用已經備有負載均衡(load-balancing)與自動擴展(autoscaling)的功能了。
使用 eb open
前往目前版本的執行結果:
bash$ eb open env-development
稍微修改 index.js
:
javascript// index.js // ... app.get('/', function (req, res) { res.send('Hello env-development!'); }); // ...
執行 eb deploy
部署新版本到 AWS Elastic Beanstalk:
bash$ eb deploy env-development
部署完成之後,執行 eb open
打開網頁:
bash$ eb open env-development
env-development
上的應用程式更新完成。
git checkout
將分支切換回主線 master:
bash$ git checkout master
eb create
新增一組新的環境,做為產品上線用,命名為 env-production
:
bash$ eb create env-production
bash$ eb open env-production
這樣就成功啟動第二組機器了,目前我們有 env-development
和 env-production
兩組環境。
Dashboard
> Users
Create New Users
Create
Download Credentials
Dashboard
> Users
> CircleCI
Attach Pollcy
AWSElasticBeanstalkFullAccess
> Attach Pollcy
前往 CircleCI,設定您的 AWS 權限:
Project Settings
Permissions
> AWS Permissions
credentials.csv
,輸入 Access Key ID
& Secret Access Key
Save AWS keys
而後
在 .elasticbeanstalk
目錄底下,創建 config.global.yml
:
yaml# .elasticbeanstalk/config.global.yml global: application_name: hello-ci-workflow default_region: us-west-2 # EB 所在的 region,預設是 us-west-2
修改 circle.yml
:
yaml# circle.yml machine: # 安裝 eb 須要 python python: version: 2.7 services: - docker dependencies: pre: # 安裝 eb - sudo pip install awsebcli override: - docker build -t hello-ci-workflow . test: override: - npm test - docker run -d -p 3000:3000 hello-ci-workflow; sleep 10 - curl --retry 10 --retry-delay 5 -v http://localhost:3000 # 新增一筆部署腳本 deployment: production: branch: master commands: - eb deploy env-production
這樣就能在 GitHub 的 master 支線有更新時,觸發 CircleCI 的自動建置、測試、然後部署。
接下來馬上來試試看流程,修改 index.js
:
javascript// index.js // ... app.get('/', function (req, res) { res.send('Hello env-production!'); }); // ...
Commit & Push:
bash$ git add . $ git cimmit "test deploy production" $ git push
前往 CircleCI 看結果:
部署成功,eb open
打開瀏覽器來看看結果:
bash$ eb open env-production
帳號:Slack
到這邊其實已經差很少結束了,最後來講講 Slack 吧。
Slack 是一款給團隊使用的即時溝通工具,類似的產品還有 Gitter 與 HipChat。
至於跟 Skype、Lync 這些軟體有什麼不一樣的地方呢?
它們整合了許多開發工具(GitHub、CircleCI)的服務,例如 GitHub 有新的 push、pull request、issue;CircleCI 的單元測試沒有通過之類的通知,會即時出現在你的團隊的 Slack 上面,既然我們已經將大部分的工做自動化,勢必須要讓相關人員知道這些工具發生了哪些事情,因此使用 Slack 是必要的。
Configure Integrations
> CircleCI
Add CircleCI Integration
按鈕複製畫面上的 webhook URL
返回 CircleCI
Project settings
> Chat Notifications
貼上將複製的 Webhook URL
> Save
類似的步驟,將 GitHub 的通知加入 Slack:
測試 Slack 通知,是否能夠順利運做,新增一條 test-slack
分支:
bash$ git branch test-slack $ git checkout test-slack
修改 index.js
:
javascript// index.js // ... app.get('/', function (req, res) { res.send('Hello Slack!'); }); // ...
Commit & Push:
bash$ git add index.js $ git commit -m "index.js: update to test slack" $ git push -u origin test-slack
CircleCI 通過測試,開啟一個 Pull Request
test-slack
merge 回 master
,觸發 CircleCI 自動部署
That’s it! On your next build, you’ll start seeing CircleCI build notifications in your Slack chatroom.
結束!能夠看見 Slack channel 會顯示每一個步驟的通知過程:
eb open
打開瀏覽器查看結果,成功自動部署新版本:
bash$ eb open env-production
「書山有路勤為徑,學海無涯苦做舟。」——韓愈
DevOps 的開發流程與工具天天都在不斷推陳出新,請站在巨人的肩膀上、保持一顆「活到老、學到老」的心。
我將這個範例的程式碼放在 GitHub 上,有興趣的人能夠參考看看。
文章如有須要改進的地方,還請不吝指教,感激不盡。