如何快速開發 Serverless Devs Package ?

語言: CN / TW / HK

作者 | 江昱 阿里雲 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' );

returnhello'world' };

}

public  async deploy(inputs: InputProps) {

logger.debug( `input:  ${ JSON .stringify(inputs.props)} ` );

logger.info( 'command test for deploy' );

returnhello'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/user/information/github', {
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;
}

在該方法中主要存在幾個事情:

  1. inputs 引數解析,獲取使用者輸入的引數內容;

  2. 如果使用者輸入的引數帶有 -h 或者 --help 引數,則輸出對應的幫助資訊;

  3. 如果使用者輸入的引數存在 --token ,則將 --token 對應的值存入到某個檔案中;

  4. 如果使用者沒有帶有 --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 直接部署的專案,那麼你就可以:

  1. s init 建立一個應用模板

  2. 把你的那個專案,脫敏後,直接放在src目錄下

  3. 對應用進行描述,例如編輯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 中,方法很簡單:

  1. 建立一個 Repo(程式碼倉庫)

  2. 將整個應用或者元件推到該倉庫

  3. 釋出一個 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 的這個專案,本身也是當前文章的一個最佳實踐。

在完成元件或者應用的開發流程之後,需要:

  1. 註冊並登入 Serverless Registry,實際上就是執行 s cli registry login
  2. 進行元件的釋出,實際上就是 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 免費開放超全開發者學習資料,將最前沿的技術知識沉澱送給各位。內含:技術電子書、技術大會資料合集、知識圖譜、18 節入門影片課等,助力所有開發者共同學習進步!

關注公眾號後臺回覆   學習   即可獲得開發者學習資料下載連結!