写点什么

如何快速开发 Serverless Devs Package ?

作者:Serverless Devs
  • 2022 年 2 月 12 日
  • 本文字数:8710 字

    阅读完需:约 29 分钟

如何快速开发 Serverless Devs Package ?

作者 | 江昱(阿里云 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:​


|



与 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 --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": []}



其中,这些参数的含义:​




一个更为具体的例子是,在上面的案例代码中,有一个 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: 版本更新内容



其中:​



最新版本的规范 (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.0access: "{{ 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)

用户头像

还未添加个人签名 2018.09.12 加入

更多内容关注 Serverless 微信公众号(ID:serverlessdevs),汇集 Serverless 技术最全内容,定期举办 Serverless 活动、直播,用户最佳实践。

评论

发布
暂无评论
如何快速开发 Serverless Devs Package ?