作者 | 江昱(阿里雲 Serverless 產品經理)
前言
Serverless Devs 一直在以開原始碼、開放生態的模式進行建設,所以在社群使用者參與 Serverless Devs 的建設過程中,就會有兩條途徑:
1、參與貢獻程式碼:參與程式碼的貢獻相對於來說是有著清晰明確的流程,並且也是參與開源專案常見的途徑,Serverless Devs 的貢獻者文件,可以參考程式碼貢獻文件;
2、參與貢獻 Package:可以開發應用或者元件,釋出到 Serverless Registry,以供更多人學習、參考或者使用;這一部分內容可以參考本文;
Serverless Devs Package 介紹
在說什麼是 Serverless Devs Packages 之前,需要先說一下 Serverless Registry,相信很多研發同學都是知道,不同語言/生態都有自己的包管理平臺,例如 Python 語言的 Pypi,Node.js 的 NPM。
而所謂的包管理平臺,粗暴來說就是管理 “包” 的,這裡的 “包”,往往指的是別人已經封裝了的某些功能或者能力,我們可以直接使用,而無需我們重複造輪子。
說兩個比較形象的例子,如果是搞人工智慧,我們不太現實要手動的來寫各種演算法,往往會通過 Sklearn,Tensorflow 等對應的包來快速的載入某些模型,然後在這個基礎上再進步的開發和完善。
而在 Serverless 領域,我們也希望有一個類似的包管理平臺,那就是 Serverless Registry:
|
Serverless Reigstry | Python Pypi | Nodejs NPM | |
---|---|---|---|
儲存內容 | Serverless packages,(包括 Components 和 Application) | Python packages | Nodejs packages |
是否開放標準 | 是 | 是 | 是 |
官方源 | registry.devsapp.cn/simple | pypi.python.org | registry.npmjs.org |
其它源舉例 | Github registry 、 Gitee registry | 清華源 、豆瓣源 | tnpm、cnpm |
是否支援私有化 | 支援 | 支援 | 支援 |
配套工具 | Serverless Devs 開發者工具 | Python包管理工具(pip) | Node.js打包管理工具(npm) |
配套命令 | s | pip | npm |
如何使用 | 在s.yaml中直接引用 | 安裝之後,在程式碼中引用 | 安裝之後,在程式碼中引用 |
與 Python 的 Pypi,Node.js 的 NPM 不同的是,在 Serverless Regsitry 中,Package 是分成兩類的,一類是 Component,一類是 Application。
針對 Component 和 Application 的通俗來作區分:
- Component:指的是元件,類似於一個指令碼,通過這個指令碼可以做一些事情。例如部署一個函式到某個雲平臺,除錯某個函式,檢視某個函式的日誌等;
- Application:指的是應用,類似於一個案例。例如通過某個 Application,可以讓使用者快速的建立一個 Hello World 的應用,建立一個音視訊處理的應用等;
在 Serverless Devs 的規範中,有關於二者的一個區別圖:
而關於 Component 和 Application 的關係是:Application 是一個應用案例的定義,需要通過 Component 進行部署上線。
或許上面的表示有些許的抽象,其實可以用一個形象的案例進行解釋。例如:
- 你通過 Python 的 Tensorflow 框架,做了一個人臉識別的應用,那麼此時 Tensorflow 就可以認為是一個 Component,而人臉識別的應用就可以認為是一個 Application;
- 你通過 Node.js 的 Express、Fs、Path 等依賴,做了一個個人部落格,那麼此時 Express、Fs、Path 等依賴,就可以認為是不同的 Component,而做出來的這個部落格,就可以認為是一個 Application;
- Serverless Registry Model
- Serverless Package Model
開發 Package
開發者開發 Serverless Package 的流程相對來說是比較簡單的。因為在 Serverless Devs 開發者工具中,已經提供了相對完整的腳手架能力。
開發者只需要執行s init,並且選擇Dev Template for Serverless Devs即可:
選擇完成,不難發現,此時會讓我們繼續選擇是開發 Component 還是開發 Application:
開發 Component
當選擇Component Scaffolding之後,需要給即將開發的 Component 起一個名字(例如deployfunction):
此時,可以根據體統提示,進入到 Component 的專案目錄:
此時,可以通過 IDE 開啟當前專案,並通過npm進行依賴安裝_(因為 Serverless Devs 是基於 Typescript 的專案,所以元件的開發僅支援 Typescript 和 Node.js 語言)_:
此時,可以開啟專案中src/index.ts檔案,不難發現已經存在一個案例:
import logger from './common/logger';
import { InputProps } from './common/entity';
export default class ComponentDemo {
/**
* demo 例項
* @param inputs
* @returns
*/
public async test(inputs: InputProps) {
logger.debug(input: ${JSON.stringify(inputs.props)}
);
logger.info('command test');
return { hello: 'world' };
}
}
在該檔案中,我們不難發現存在一個test(inputs)方法,該方法是一個列印inputs引數,並返回hello world的案例,但是通過這個簡單的案例,我們可以瞭解到幾個事情:
公共方法就是使用者可以使用的命令
在專案中,我們可以寫多個方法對外暴露,目前只有一個test,但是我們可以增加任何public方法,而這些方法將會成為該元件的命令。例如:
public async test(inputs: InputProps) {
logger.debug(input: ${JSON.stringify(inputs.props)}
);
logger.info('command test for test');
return { hello: 'world' };
}
public async deploy(inputs: InputProps) {
logger.debug(input: ${JSON.stringify(inputs.props)}
);
logger.info('command test for deploy');
return { hello: 'world' };
}
此時,我們在使用該元件時,元件就具備了兩個命令:test 命令和 deploy 命令,為了驗證我們的想法,我們可以對專案進行基礎的開發態的編譯:npm run watch:
此時,我們可以找到 example 目錄,進行 deploy 方法的測試,例如:
通過 example 下面的 s.yaml 檔案,我們不難看出,這個 yaml 有兩個 service_(分別是 component-test 和 component-test2)。_
並且這兩個 service,都用了我們同一個元件。所以,在執行s deploy之後獲得到預期的結果:即執行了 deploy 方法。
同樣的,我們也可以執行 test 命令看一下效果:
要實現的邏輯可以在方法內自由實現
換句話來說,Serverless Devs 工具在載入元件的時候,實際上就是將對應的引數,傳遞到指定的方法,並且執行該方法。所以,你要實現什麼功能都可以寫在對應的方法裡。
以 Serverless Registry Component 專案為例,我在該專案中,存在一個 Login 的功能,所以我在 Login 中實現了以下內容:
/**
* demo 登陸
* @param inputs
* @returns
*/
public async login(inputs: InputProps) {
const apts = {
boolean: ['help'],
alias: {help: 'h'},
};
const comParse = commandParse({args: inputs.args}, apts);
if (comParse.data && comParse.data.help) {
help([{
header: 'Login',
content: Log in to Serverless Registry
}, {
header: 'Usage',
content: $ s cli registry login <options>
}, {
header: 'Options',
optionList: [
{
name: 'token',
description: '[Optional] If you already have a token, you can configure it directly',
type: String,
}
],
}, {
header: 'Examples without Yaml',
content: [
'$ s cli registry login',
'$ s cli registry login --token my-serverless-registry-token',
],
},]);
return;
}
const tempToken = comParse.data ? comParse.data.token : null
let st = 0
let user
if (tempToken) {
const fd = await fse.openSync(${getRootHome()}/serverless-devs-platform.dat
, 'w+')
await fse.writeSync(fd, tempToken)
await fse.closeSync(fd)
st = 1
} else {
const token = random({length: 20})
const loginUrl = https://github.com/login/oauth/authorize?client_id=beae900546180c7bbdd6&redirect_uri=http://registry.devsapp.cn/user/login/github?token=${token}
// 輸出提醒
logger.warn("Serverless registry no longer provides independent registration function, but will uniformly adopt GitHub authorized login scheme.")
logger.info("The system will attempt to automatically open the browser for authorization......")
try {
await sleep(2000)
opn(loginUrl)
} catch (e) {
logger.info("Failed to open the default address. Please try to open the following URL manually for authorization: ")
logger.info(loginUrl)
}
await logger.task('Getting', [
{
title: 'Getting login token ...',
id: 'get token',
task: async () => {
for (let i = 0; i < 100; i++) {
await sleep(2000)
const tempResult = await request('http://registry.devsapp.cn/us...', {
params: {
token: token,
},
})
if (!tempResult.Error && tempResult.safety_code) {
// 或得到結果, 儲存狀態
const fd = await fse.openSync(${getRootHome()}/serverless-devs-platform.dat
, 'w+')
await fse.writeSync(fd, tempResult.safety_code)
await fse.closeSync(fd)
st = 1
user = tempResult.login
break
}
}
},
}
])
}
if (st == 1) {
logger.log(${user ? user + ': ' : ''}Welcome to Serverless Devs Registry.
, "green");
} else {
logger.error("Login failed. Please log in to GitHub account on the pop-up page and authorize it, or try again later.")
}
return null;
}
在該方法中主要存在幾個事情:
- 對inputs引數解析,獲取使用者輸入的引數內容;
- 如果使用者輸入的引數帶有-h或者--help引數,則輸出對應的幫助資訊;
- 如果使用者輸入的引數存在--token,則將--token對應的值存入到某個檔案中;
- 如果使用者沒有帶有--token輸入,則直接開啟瀏覽器,訪問 Serverless Registry 的登陸地址,並進行相關登陸token的獲取;
那麼同樣的方法,如果是一個部署函式的方法或者命令,我們是不是可以在這個裡面實現打包壓縮程式碼,然後呼叫相關建立函式,更新函式的介面進行函式的建立呢?再比如,想要做一個刪除函式的方法,是不是可以在裡面呼叫刪除函式的介面呢?
所以可以認為,無論想要實現什麼功能,都可以在對應的方法中實現。
關於開發過程中的一些規範
在上面我們說到,Serverless Devs 會帶著某些引數呼叫該方法,那麼引數是什麼樣子的?格式如何,我們該如何解析呢?
再比如,專案最後的 return 有什麼用處?如何在專案中獲取使用者的金鑰資訊?使用者在 Yaml 中寫的各種引數如何獲取?使用者在執行命令時候傳遞的引數如何獲取?
其實這些都可以參考:Serverless Devs Package 的開發規範文件的元件模型程式碼規範,在這裡我們不難發現:
入參 inputs 的結構為:
{
"command": "",
"project": {
"projectName": "",
"component": "",
"provider": "",
"access": ""
},
"credentials": {},
"prop": {},
"args": "",
"argsObj": []
}
其中,這些引數的含義:
目錄 | 含義 |
---|---|
command | 使用者所執行的命令 |
project | 使用者的專案基本資訊 |
credentials | 使用者的金鑰資訊 |
prop | 使用者配置的屬性/引數 |
args | 使用者傳遞的引數(字串形式) |
argsObj | 使用者傳遞的引數(解析後的,以陣列形式傳遞) |
一個更為具體的例子是,在上面的案例程式碼中,有一個 test 方法,該方法就是功能實現的方法。此時當使用者使用 test 命令時,系統就會攜帶引數呼叫該方法。以一個真實案例作為舉例說明:
該元件名為hexo,元件核心程式碼如上所示,具備一個 test 方法,此時使用者側的 Yaml 為:
edition: 1.0.0 # 命令列YAML規範版本,遵循語義化版本(Semantic Versioning)規範
name: FullStack # 專案名稱
access: xxx-account1 # 祕鑰別名
services:
HexoComponent:
component: hexo
props:
region: 'cn-hangzhou'
codeUri: './src'
當使用者執行s test mytest -a -b abc,此時,元件程式碼中的test方法,收到的inputs引數實際上是:
{
"command": "test",
"project": {
"projectName": "HexoComponent",
"component": "hexo",
"provider": "alibaba",
"access": "release"
},
"credentials": {
"AccountID": "",
"AccessKeyID": "",
"AccessKeySecret": ""
},
"prop": {
"Region": "cn-hangzhou",
"CodeUri": "./src"
},
"args": "mytest -a -b abc",
"argsObj": [
"mytest", "-a", "-b", "abc"
]
}
此時 test 方法會列印日誌資訊等,並返回最終的結果給命令列工具:{ "hello": "world" }
而關於如何返回幫助檔案,如何獲取金鑰資訊,如何解析使用者的輸入內容,則可以參考 Serverless Devs 提供的 core 包:
在該工具包中,我們可以看到諸多的方法助力我們快速的使用:
例如,獲取使用者使用金鑰,就可以直接引入 core 包,使用對應的getCredential方法即可:
- 使用方法 1 :不傳任何引數的時候,會獲取 default 金鑰資訊
const { getCredential } = require('@serverless-devs/core');
async function get() {
const c = await getCredential();
console.log('c', c);
}
- 使用方法 2 :傳引數,獲取指定的金鑰資訊
const { getCredential } = require('@serverless-devs/core');
async function get() {
// 元件接收的inputs
const inputs = {};
const c = await getCredential(inputs, 'custom', 'AccountIdByCustom', 'SecretIDByCustom');
console.log('c', c);
}
元件的描述
在完成我們的元件功能編寫之後,就可以進行元件的描述,所謂的元件的描述,就是要告訴 Serverless Registry,這是一個什麼元件,有哪些功能。描述內容在publish.yaml中:
關於該檔案的內容以及部分引數的取值,可以參考元件模型後設資料。
當然,除了 publish.yaml 之外,在 Serverless Package 的規範中,目錄哪還有其他的檔案:
|- src # 目錄名字可以變更
| └── 程式碼目錄
|- package.json: 需要定義好main
|- publish.yaml: 專案的資源描述
|- readme.md: 專案簡介
|- version.md: 版本更新內容
其中:
目錄 | 必須 | 含義 |
---|---|---|
src | 推薦存在 | 統一放置功能實現,當然也可以換成其他的名稱,或者平鋪到專案下,但是推薦通過src來做統一的存放 |
package.json | 必須存在 | Node.js的package.json,需要描述清楚元件的入口檔案位置 |
publish.yaml | 必須存在 | Serverless Devs Package的開發識別文件 |
readme.md | 必須存在 | 對該元件的描述,或幫助文件資訊 |
version.md | 推薦存在 | 版本的描述,例如當前版本的更新內容等 |
最新版本的規範 (0.0.2 版本),將會在近期上線,和 0.0.1 版本不同的是,新版本的 Properties 引數將遵循 JSON Scheme 規範,目前可參考 pr#386)
開發 Application
當選擇Application Scaffolding之後,需要給即將開發的 Application 起一個名字(例如helloworld):
Serverless Package 中的 Application 開發相對更為簡單,無論是任何程式語言,無論是任何專案,只要可以通過 Serverless Devs 開發者工具進行部署,就可以把它包裝成為一個應用。
或者更為準確的來表述,只要你現在有一個可以通過 Serverless Devs 直接部署的專案,那麼你就可以:
- s init 建立一個應用模板
- 把你的那個專案,脫敏後,直接放在src目錄下
- 對應用進行描述,例如編輯publish.yaml,編輯version.md,編輯readme.md等
這一部分具體情況,可以參考應用模型文件:
特殊格式:在應用模型中,需要存在src/s.yaml檔案,作為Serverless Devs識別和使用的資源、行為描述檔案,在該檔案中,可能涉及到部分內容是需要使用者進行填寫的,例如使用者的金鑰名字,使用者部署業務的地域等。此時可以參考:
- "{{ access }}" :直接提醒使用者需要輸入access這樣的一個引數,作為Yaml中所必須的引數;
- '{{ bucket | alibaba oss bucket }}' ::直接提醒使用者需要輸入bucket這樣的一個引數,作為Yaml中所必須的引數,並以|之後的內容"alibaba oss bucket"作為解釋這個引數的含義;
例如,在某應用的s.yaml中表現為:edition: 1.0.0
access: "{{ access }}"
services:
website-starter:
component: devsapp/website
actions:
pre-deploy:
- run: npm install
path: ./
- run: npm run build
path: ./
props:
bucket: '{{ bucket | alibaba oss bucket }}'
src:
codeUri: ./
publishDir: ./build
index: index.html
region: cn-hangzhou
hosts:
- host: auto
釋出 Package
在完成 Serverless Package 的開發之後,為了給更多人使用,還可以將釋出到 Registry 中。
釋出到 Github/Gitee
關於釋出到 Github 或者 Gitee 中,方法很簡單:
- 建立一個 Repo(程式碼倉庫)
- 將整個應用或者元件推到該倉庫
- 釋出一個 Release:此時,在客戶端,使用者就可以通過s set registry進行registry的切換,來使用相對應的功能了。例如,我在 Github 的賬戶為 anycodes,我就可以建立一個倉庫,名字為 demo,此時,我將我的元件/應用上傳到這個倉庫中,然後釋出一個 Release。此時,我在客戶端,將 registry 切換到 Github,然後就可以:
- 在使用元件的時候,指定元件名為倉庫,例如 anycodes/demo
在初始化應用的時候,也可以直接指定應用,例如 anycodes/application
釋出到 Serverless Registry
若想把 Package 釋出到 Serverless Registry,可以考慮使用 Serverless Registry Component。
(Serverless Registry 的客戶端工具,也是一個元件,所以可以認為 Serverless Registry Component 的這個專案,本身也是當前文章的一個最佳實踐。)
在完成元件或者應用的開發流程之後,需要:
- 註冊並登入 Serverless Registry,實際上就是執行s cli registry login
- 進行元件的釋出,實際上就是s cli registry publish
當然,Serverless Registry Component 這個專案,除了登陸和釋出之外,還有諸多其他的功能,例如:
- 檢視當前登陸賬號釋出過的 Package
- 檢視某個 Package 的版本資訊
- 檢視某個 Package 指定版本資訊
- 刪除某個指定版本的 Package
- 對登陸 token 進行更新
總結
眾所周知,一個完整的技術架構的發展,離不開生態社群對他的賦能,無論是 Docker 的 Dockerhub 還是 Python 的 Pypi,再或者是 Node.js 的 NPM,生態的活躍度和我們開發者的幸福感是正相關的。
我們也非常希望 Serverless Devs 可以通過 Serverless Regsitry 這樣一個開放的生態,和更多的人一同玩轉 Serverless 架構,也期待更多優秀的 Package,被更為廣泛的應用。
( END)
更多內容關注 Serverless 微信公眾號(ID:serverlessdevs),彙集 Serverless 技術最全內容,定期舉辦 Serverless 活動、直播,使用者最佳實踐。