您好,登錄后才能下訂單哦!
首先介紹下在本文出現的幾個比較重要的概念:
函數計算(Function Compute): 函數計算是一個事件驅動的服務,通過函數計算,用戶無需管理服務器等運行情況,只需編寫代碼并上傳。函數計算準備計算資源,并以彈性伸縮的方式運行用戶代碼,而用戶只需根據實際代碼運行所消耗的資源進行付費。函數計算更多信息 參考。
Funcraft:Funcraft 是一個用于支持 Serverless 應用部署的工具,能幫助您便捷地管理函數計算、API 網關、日志服務等資源。它通過一個資源配置文件(template.yml),協助您進行開發、構建、部署操作。Fun 的更多文檔 參考。
OSS: 對象存儲。海量、安全、低成本、高可靠的云存儲服務,提供99.9999999999%的數據可靠性。使用RESTful API 可以在互聯網任何位置存儲和訪問,容量和處理能力彈性擴展,多種存儲類型供選擇全面優化存儲成本。
ROS:資源編排(ROS)是一種簡單易用的云計算資源管理和自動化運維服務。用戶通過模板描述多個云計算資源的依賴關系、配置等,并自動完成所有資源的創建和配置,以達到自動化部署、運維等目的。編排模板同時也是一種標準化的資源和應用交付方式,并且可以隨時編輯修改,使基礎設施即代碼(Infrastructure as Code)成為可能。
CI/CD: CI/CD 是一種通過在應用開發階段引入自動化來頻繁向客戶交付應用的方法。CI/CD 的核心概念是持續集成、持續交付和持續部署。
本文打算以一個簡單的函數計算項目為例,在此基礎上編寫測試用例,進行配置,讓其支持 CI/CD 工作流程。實現如下四個小目標:
這里以大家熟悉的 Github 倉庫為例,并結合 Travis CI 。當用戶往示例項目 push 或者 PR(Pull Request)時,會自動觸發 Travis CI 的工作任務,進行單元測試、構建打包和部署發布。
示例項目地址為: https://github.com/vangie/tz-time ,該項目是基于 FC Http trigger 實現的簡單 web 函數,訪問放函數是會返回指定時區的當前時間。項目目錄結構如下
tz-time
├── .funignore
├── .travis.yml
├── Makefile
├── bin
│ ├── delRosStack.sh
│ ├── deployE2EStack.sh
│ └── waitForServer.sh
├── deploy.log
├── index.e2e-test.js
├── index.integration-test.js
├── index.js
├── index.test.js
├── jest.config.e2e.js
├── jest.config.integration.js
├── package-lock.json
├── package.json
└── template.yml
部分文件作用介紹:
.funignore
- Funcraft 部署時忽然的文件清單.travis.yml
- Travis CI 配置文件index.js
- 函數入口文件測試通常非常如下三類:單元測試、集成測試和 E2E 測試。在函數計算場景下,這三類測試可以通過如下方法實現。
fun local invoke/start
模擬運行函數本例子只實現了單元測試,集成測試和 E2E 測試對于 travis 示例來說觸發方法類似,實現方法可以參見上面的方法提示進行配置。
FC 函數的單元測試和普通的函數并無二致。采用熟悉的單元測試框架即可,本例中使用了 jest 進行測試。下面看看一個測試用例的代碼片段
jest.mock('moment-timezone');
const { tz } = require('moment-timezone');
const { handler } = require('./index');
const EXPECTED_DATE = '2018-10-01 00:00:00';
const TIMEZONE = 'America/New_York';
describe('when call handle', () => {
it('Should return the expected date if the provied timezone exists', () => {
const mockReq = {
queries: {
tz: TIMEZONE
}
}
const mockResp = {
setHeader: jest.fn(),
send: jest.fn()
}
tz.names = () => [TIMEZONE];
tz.mockImplementation(() => {
return {
format: () => EXPECTED_DATE
}
})
handler(mockReq, mockResp, null);
expect(mockResp.setHeader.mock.calls.length).toBe(1);
expect(mockResp.setHeader.mock.calls[0][0]).toBe('content-type');
expect(mockResp.setHeader.mock.calls[0][1]).toBe('application/json');
expect(mockResp.send.mock.calls.length).toBe(1);
expect(mockResp.send.mock.calls[0][0]).toBe(JSON.stringify({
statusCode: '200',
message: `The time in ${TIMEZONE} is: ${EXPECTED_DATE}`
}, null, ' '));
});
});
通過 jest.mock 對 moment-timezone 進行 mock,讓 tz 被調用的時候返回預先設定好的值,而不是一個動態變化的時間。
通常該類單元測試分為三步:
如果依賴包不存在原生依賴(依賴 linux 下的可執行文件或者 so 庫文件)的使用 npm test 觸發測試即可,如果有原生依賴,那測試需要跑在 fun 提供的 sbox 模擬環境里,使用如下命令觸發
fun install sbox -f tz-time --cmd 'npm install'
本例子中的集成測試會借助 fun local start 命令把函數在本地啟動起來,由于函數配置了 http trigger,所以可以通過 http 請求調用函數。
集成測試我們還是才是 jest 框架進行編寫,為了區別于單元測試文件
*.test.js
,集成測試文件使用
.integration-test.js
文件后綴。為了讓 jest 命令獨立的跑集成測試用例而不是和單元測試混和在一起,需要編撰如下文件 jest.config.integration.js
module.exports = {
testMatch: ["**/?(*.)integration-test.js"]
};
然后在 package.json 中配置 scripts
"scripts": {
"integration:test": "jest -c jest.config.integration.js"
}
于是可以通過執行 npm run integration:test 來執行集成測試。
然后在此基礎上在 Makefile 中添加 integration-test 目標:
funlocal.PID:
fun local start & echo $$! > $@
integration-test: funlocal.PID
bin/waitForServer.sh http://localhost:8000/2016-08-15/proxy/tz-time/tz-time/
npm run integration:test
kill -2 `cat $<` && rm $<
integration-test 目標依賴 funlocal.PID 目標,后者負責啟動一個 fun local 進程,該進程會在本地啟動 8000 端口。解讀一下上面的 Makefile 代碼
fun local start & echo $$! > $@
啟動 fun local 進程,并將進程 PID 寫入到目標同名文件 funlocal.PIDbin/waitForServer.sh http://localhost:8000/2016-08-15/proxy/tz-time/tz-time/
通過一個 url 測試 fun local 進程是否啟動完成。kill -2 `cat $<` && rm $<
測試完成以后銷毀 fun local 進程。
npm run integration:test
會啟動若干的測試用例,其中一個測試用例如下:
const request = require('request');
const url = 'http://localhost:8000/2016-08-15/proxy/tz-time/tz-time/';
describe('request url', () => {
it('without tz', (done) => {
request(url, (error, response, data) => {
if (error) {
fail(error);
} else {
const resData = JSON.parse(data);
expect(resData.statusCode).toBe(200);
expect(resData.message).toContain('Asia/Shanghai');
}
done();
});
});
});
端對端測試和集成測試的測試用例非常的類似,區別在于測試的服務端,端對端測試部署一套真實的環境,集成測試通過 fun local 本地模擬。
本例中借助
fun deploy --use-ros
部署一套環境,環境名稱為
tz-e2e-
前綴帶上時間戳,這樣每次測試都會部署一套新的環境,不同環境之間相互不會影響。測試完成再通過 aliyun-cli 工具把 ROS 的 stack 刪除即可。
下面端對端測試的 Makefile 目標:
stack_name := tz-e2e-$(shell date +%s)
e2e-test:
# deploy e2e
bin/deployE2EStack.sh $(stack_name)
# run test
npm run e2e:test
# cleanup
bin/delRosStack.sh $(stack_name)
bin/deployE2EStack.sh $(stack_name)
負責部署一個新的 ROS stack。部署之前需要使用 fun package 構建交付物,具體如何構建交付物可以參考下一小節。npm run e2e:test
運行端對端測試bin/delRosStack.sh $(stack_name)
測試完成之后,清理部署的 ROS stack,會釋放掉響應的云資源。
fun package
命令可被用于構建交付物,
fun package
需要指定一個 OSS 的 bucket。fun package 命令會完成如下步驟:
生成的 template.packaged.yml 文件就是最終交付物,可以通過 fun deploy 命名進行部署。
當構建環節生成了交付物以后,就可以通過 fun deploy 進行部署了。持續部署需要解決如下兩個問題:
fun deploy
借助于 ROS,可以輕松的解決上述問題。
fun deploy --use-ros --stack-name tz-staging --assume-yes
其中:
--use-ros
表示借助于 ROS 進行部署,其工作機制是將 template.yml 推送到 ROS 服務,由 ROS 服務執行每個服務的新建和更新操作。如果沒有該參數,fun 就會在本地解析 template.yml,調用 API 進行資源創建。ROS 有個額外的好處是可以進行部署的回滾,失敗的時候能自動進行回滾。--stack-name
指定一個 stack 的名稱,stack 是 ROS 的概念,可以理解為一套環境。--assume-yes
用于無人值守模式,跳過確認提示。注意,此處如果不指定參數
--use-ros
,fun deploy 會采用直接調用云資源 API 進行部署, 這是 fun deploy 的默認部署方式,雖然也基本實現了冪等部署,但是僅支持部署有限的云資源(FC、OTS、API Gateway等),遠不及 ROS 豐富,而且也沒法做到 ROS 已支持的回滾和一鍵刪除,所以此處不推薦。
上面所有步驟的腳本化配置可以參考 Makefile 和 .travis.yml 文件。通過上述兩個文件可以實現 Github 和 Travis CI 的聯動,實現基于代碼提交觸發的 CI/CD。
本文講述了 FC 函數
“ 阿里巴巴云原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦云原生流行技術趨勢、云原生大規模的落地實踐,做最懂云原生開發者的技術圈。”
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。