CI/CD - Continuous Integration & Continuous Delivery
CI/CD
Continuous Integrationapply changes everyday without inffect their service
- integrating or merging the code Changes frequently
- version control
- at least once per day
- tools: AWS CodeCommit
- Automating the build, test and deployment functions
- tools: CodeBuild. CodeDeploy
- Fully automated release process
- code is deployed into Staging or Production as soon as it has successfully passed through the release pipelines
- tools: CodePipeline
CI
- 降低風險。
- 減少人工手動的繁複程序。
- 可隨時產生一版可部署的版本。
- 增加系統透明度。
- 建立團隊信心。
針對軟體系統每個變動,能持續且自動地進行驗證。此驗證可能包含了:
- 建置 (build)
- 測試 (test)
- 程式碼分析 (source code analysis)
- 其他相關工作 (自動部署)
- 驗證完成後,進一步可以整合自動化發佈或部署 (Continuous Delivery / Continuous Deployment) 。
透過此流程可以確保軟體品質 不會因為一個錯誤變動而產生錯誤結果或崩潰(Crash)。 此流程中的各類工具,也會產生一些回饋給開發者或其他角色,包含網頁/報表等等,用來追蹤並改善軟體潛藏的問題。
goal:
- software development best practice
- make small changes & automate everything
- small, incremental code changes
- automate as much as possible
- slow, error prone, inconsistent, unscalable complex
- fast, repeatable, scalable, enables rapid deployment
- test each code change and catch bugs while they are small and simple to fix.
- code integration, build, test and deployment
CD
AWS Develop Tool
Other Tools
1
2
3
4
5
6
7
Git — 版本管理
GitHub — 程式碼託管、審查
CircleCI — 自動化建置、測試、部署
Docker — 可攜式、輕量級的執行環境(使用 Docker,統一開發者、測試人員、以及產品的執行環境。)
AWS Elastic Beanstalk — 雲端平台
Slack — 團隊溝通、日誌、通知
Action
ref:
Jenkins
- Jenkins的功能完整,也提供了上千個外掛 (Plugins) 來對應各種開發語言與工具。
- Jenkins目前已發展到了 2.x 版,新版本中對於 Pipeline 概念及容器 (Container) 整合也趨於完整,是一套可以自訂運用的系統。
- 但也因為其功能強大、客製程度高,上手需要一些時間。
- 然而一旦流程被定義,並整合好相關環境,它可以發揮持續性整合威力,大幅增加開發生產力
我們結合了 Git Flow + Protected Branch Flow 的開發流程,流程如下:
- 開發者 (Developer) create 一個功能分支 (feature branch)。
- 開發者提交一個 Pull Request, SCM 系統會自動觸發 Jenkins 進行建置以及測試。
- 這個觸發通常是經由 Webhook 來實現。
- 在軟體建置完成後,在 Jenkins 增加一個步驟來送出 Code Scan 的請求給 Sonarqube 系統。
- Sonarqube 在完成 Code Scan 後將結果寫回 SCM 系統。
- 在 SCM 上連結了 Slack,每個步驟完成(成功或失敗)的通知便可以送到 Slack 群組。
- 原碼審核者 (Reviewer) 可以到 SCM 上查看這個Pull Request的相關訊息,搭配 Code Scan的結果決定是否將這個分支合併 (merge) 回主線(develop/master branch)。
- 分支合併可以觸發另一個 CI 工作,使 Jenkins 將主線建置後部署到測試環境提供給其他人員進行測試。
在本地端測試 Node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# ---------------------------- 建立專案資料夾
# 以 hello-ci-workflow 為例:
$ mkdir hello-ci-workflow
$ cd hello-ci-workflow
# ---------------------------- 在本地端執行 Node.js
# 初始化 Node.js 的環境
$ npm init
# 填寫一些資料之後會在目錄下產生一個 package.json 的檔案:
# This utility will walk you through creating a package.json file.
# package name: (hello-ci-workflow)
# version: (1.0.0)
# description:
# entry point: (index.js)
# test command:
# git repository:
# keywords:
# author:
# license: (ISC)
# About to write to /Users/luo/Documents/code/hello-ci-workflow/package.json:
# {
# "name": "hello-ci-workflow",
# "version": "1.0.0",
# "description": "",
# "main": "index.js",
# "scripts": {
# "test": "echo \"Error: no test specified\" && exit 1"
# },
# "author": "",
# "license": "ISC"
# }
# Is this OK? (yes) yes
# 安裝 Node.js 的 web framework,以 Express 為例:
# --save: 寫入 package.json 的 dependencies。
$ npm install express --save
# 完成之後,package.json 大概會長這個樣子:
# add the "scripts" in your package.json
# package.json
{
"name": "hello-ci-workflow",
"version": "1.0.0",
"main": "index.js",
"scripts": { # the script it can run
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {},
"description": ""
}
# 在 index.js 裡寫一段簡單的 Hello World! 的程式:
# This app starts a server and listens on port 3000 for connections. The app responds with “Hello World!” for requests to the root URL (/) or route. For every other path, it will respond with a 404 Not Found.
# index.js:
var express = require('express');
var app = express();
const port = 3000
app.get('/', function(req, res){
res.send('Hello World!');
});
var server = app.listen(port, function(){
var host = server.address().address;
var port = server.address().port;
console.log("Example app listening at https://%s:%s", host, port);
});
# 執行 npm start 或 node index.js:
$ npm start
# 打開瀏覽器 https://localhost:3000 看結果:
測試 Node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 安裝 Node.js 的單元測試,以 Mocha 為例:
$ npm install mocha --save-dev
# --save-dev: 寫入 package.json 的 devDependencies,正式上線環境不會被安裝。
# package.json
{
"name": "hello-ci-workflow",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"mocha": "^8.2.1" # added
},
"description": ""
}
# 根目錄 test 資料夾,並新增一個測試腳本 test.js:
$ mkdir test
$ cd test
$ touch test.js
# 加入一筆錯誤的測試 assert.equal(1, [1,2,3].indexOf(0)):
# 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));
# return true, 0
})
})
})
# 執行 mocha 測試:
$ ./node_modules/.bin/mocha
# 結果顯示 1 failing,測試沒通過,因為 [1,2,3].indexOf(0) 回傳的值不等於 -1。
Array
#indexOf()
1) should return -1 when the value is not present
0 passing (9ms)
1 failing
# 將 test.js 的測試修正:
# test/test.js
assert.equal(-1, [1,2,3].indexOf(0)); # return false, -1
# 再次執行 mocha 測試:
$ ./node_modules/.bin/mocha
# 結果顯示 1 passing,通過測試。
Array
#indexOf()
✓ should return -1 when the value is not present
1 passing (6ms)
CircleCI
1. upload code to GitHub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# ---------------- 初始化 git 環境:
$ git init .
# Initialized empty Git repository in /Users/luo/Documents/code/hello-ci-workflow/.git/
# ---------------- 顯示目前哪些檔案有過更動:
$ 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/
# ---------------- .gitignore
# 將 node_modules 加到 .gitignore 黑名單
# 這個資料夾是由 npm install 自動產生的,不需要放到 GitHub 上:
vim .gitignore
# .gitignore
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
*.exe # 忽略所有 xxx.exe 的檔案
Builds*
*.debug
# ---------------- 更動 commit:
$ git add .
$ git commit -m "first commit"
# ---------------- repository:
# 打開 GitHub,新增一個 repository:
# 輸入 repository 的名稱,以 hello-ci-workflow 為例:
# 使用 git remote add 將新創建的 GitHub repository 加入到 remote:
$ git remote add origin https://github.com/ocholuo/hello.git
$ git remote set-url origin https://github.com/ocholuo/hello.git
$ git show-ref
a9f90c6c817514c9484de1df9f317f2840d8b24c refs/heads/main
# ---------------- 將程式碼傳到 GitHub:
$ git push -u origin origin/main
$ git push -u origin master
# 成功之後前往 https://github.com/<USER_NAME>/hello-ci-workflow 就可以看到剛才上傳的檔案:
2. setup CircleCI 加入 GitHub repository
1
2
3
# 搜尋要加入的 GitHub repository,然後點選 Build project 按鈕,以 hello-ci-workflow 為例:
# 完成之後 CircleCI 就會自動執行第一次的建構
# 因為還沒加入測試腳本,所以建構結果會顯示 no test:
3. 在 CircleCI 測試 Node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# ---------------- 在根目錄建立 circle.yml, 加入 mocha test:
# circle.yml
version: 2.1
orbs:
node: circleci/node@4.1.0
jobs:
test:
executor:
name: node/default
tag: '13.14'
steps:
- checkout
- node/install-packages
- run:
# command: npm run test
# command: npm start
command: ./node_modules/.bin/mocha
workflows:
test_my_app:
jobs:
- test
# # ---------------- push 上 GitHub:
$ git add circle.yml
$ git commit -m "add circle.yml"
$ git push
# Push 成功之後,CircleCI 會自動觸發建構和測試:
# 測試通過,建置成功:
4. Code Review with GitHub Flow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 建立一條分支
# 為了確保 master 這條主線上的程式碼都是穩定的,
# 所以建議開發者依照不同的功能、建立不同的分支,
# 這裡以 test-branch 為例
# ---------------- git branch 新增+切換分支:
$ git branch test-branch
$ git checkout test-branch
# ---------------- 在 test.js 裡加入一行錯誤的測試 assert.equal(3, [1,2,3].indexOf(5)):
assert.equal(3, [1,2,3].indexOf(5));
# ---------------- 加入 commits
$ git add test/wtest.js
$ git commit -m "add a error test case"
# ---------------- 新增一個 Pull Request
# Push 到 GitHub 的 test-branch 分支:
$ git push -u origin test-branch
# 打開 GitHub
# 出現 test-branch 分支的 push commits,點選旁邊的 Compare & pull request 按鈕:
# ---------------- 進入 Open a pull request 的填寫頁面
# 選擇想要 merge 的分支、輸入描述之後
# 點選 Create pull request 按鈕:
# 新增一個 pull request 之後,其他人就會在 GitHub 上出現通知:
# 點進去之後可以看見相關的 commits 與留言,但是下面有一個紅紅大大的叉叉;因為每次 GitHub 只要有新的 push,就會觸發 CircleCI 的自動建置和測試,並且顯示結果在 GitHub 上:
# 點選叉叉,前往 CircleCI 查看錯誤原因:
# 就會發現剛剛 push 到 test-branch 的測試沒通過:
# 回到 GitHub,因為測試沒通過,所以審查者不能讓這筆 pull request 被 merge 回 master。
# 找到剛剛 commit 的那段程式碼,留言告知請開發者修正錯誤之後,再重新 commit push 上來:
# 修正 test.js 的測試腳本:
assert.equal(-1, [1,2,3].indexOf(5));
# 再次 commit & push:
$ git add test/test.js
$ git commit -m "fix error test case"
$ git push
# 回到 GitHub 的 pull request 頁面,可以看到最新一筆的 commit 成功通過 CircleCI 的測試了:
5. Merge&部署
1
2
3
# 審查之後,確定沒有問題
# 點選 Merge pull request 的按鈕
# 將 test-branch 的程式碼 merge 回主線 master:
Docker
以往開發人員面對開發環境不同的問題,常常出現「明明在我的電腦上可以跑」的囧境,所以為了解決這類問題,通常會使用虛擬機器(VM)搭配一些工具(Vagrant、Chef)來協助統一開發人員、測試人員、上線產品的執行環境。
Docker 也是類似的解決方案,不同於 VM 的是,Docker 運行起來更輕巧、可攜度更高。配置好一份設定之後,就可以讓大家馬上進入開發狀況,減少不必要的環境問題,提升效率。
different
- setup a docker run
- use CircleCI to run on a docker (no need to build)
在本地测试 Docker 上的 Node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 在專案根目錄底下建立一個 Dockerfile:
# 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
FROM node:0.10
WORKDIR /hello-ci-workflow
ADD . /hello-ci-workflow
RUN npm install
EXPOSE 3000
CMD npm start
# 使用 docker build 建構 image:
# -t image _name。
$ docker build -t hello-ci-workflow .
# Sending build context to Docker daemon 8.998MB
# Step 1/6 : FROM node:0.10
# 0.10: Pulling from library/node
# 386a066cd84a: Pull complete
# 0adf07c73141: Download complete
# 使用 docker run 執行您的 image:
# -d 在背景執行 node,可以使用 docker logs 看執行結果。
# 打開瀏覽器 https://localhost:3000 看結果:
$ docker run -p 3000:3000 -d hello-ci-workflow
# 其實每一次都要 build 和 run 還蠻麻煩的,推薦可以試試 Docker Compose,用起來有點像 Vagrant。
在 CircleCI 測試 Docker 上的 Node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ---------------- 在根目錄建立 circle.yml
# 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 https://localhost:3000
# Push 更新到 GitHub:
$ git add Dockerfile circle.yml
$ git commit -m "add Docker"
$ git push
AWS Elastic Beanstalk
AWS Elastic Beanstalk
- 只需要上傳程式碼,Elastic Beanstalk 即可幫你完成
- 容量配置、負載均衡(load balancing)、自動擴展(auto scaling), 應用程式的運行狀況監控的部署。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 初始化 EB 環境:
$ eb init -p docker
# 該命令將提示配置各種設置。 按 Enter 鍵接受預設值。
# 已經存有一組 AWS EB 權限的憑證,該命令會自動使用它。
# 否則輸入 Access key ID 和 Secret access key,必須前往 AWS IAM 建立一組。
# 初始化成功之後,可以使用 eb create 快速建立各種不同的環境,例如:development, staging, production。
# 以 env-development 為例, 當它完成之後,您的應用已經備有負載均衡(load-balancing)與自動擴展(autoscaling)的功能了。
$ eb create env-development
# 使用 eb open 前往目前版本的執行結果:
$ eb open env-development
在本地端部署 AWS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 稍微修改 index.js:
# index.js
# ...
app.get('/', function (req, res) {
res.send('Hello env-development!');
});
# ...
# 執行 eb deploy 部署新版本到 AWS Elastic Beanstalk:
$ eb deploy env-development
# 部署完成之後,執行 eb open 打開網頁:
$ eb open env-development
在 CircleCI 部署 AWS
1
2
3
4
5
6
# git checkout 將分支切換回主線 master:
$ git checkout master
# eb create 新增一組新的環境,作為產品上線用,命名為 env-production:
$ eb create env-production
$ eb open env-production
Slack
。
This post is licensed under CC BY 4.0 by the author.
Comments powered by Disqus.