Serverless项目和常规项目不同,由于业务代码以云函数的形式存在,因此一个项目内将会包含多个分属不同业务逻辑的云函数代码,而一个比较好的实践就是把每一个云函数都提取出来单独以其函数功能命名形成一个代码文件。因此Serverless项目文件结构与传统项目文件结构有所不同。
本文将记录我使用Serverless框架构建无服务器项目过程中的诸多问题,文章前半部分将以Nx框架构建Serverless Typescript项目为目标,构建一个HelloWorld项目,而后半部分将会包括我在构建项目过程中所遇到的问题及解决方法。
我因为使用阿里云作为云提供商,因此下文所有的配置均以阿里云为例。
Serverless Framework
Serverless框架是用于自动化构建Serverless项目并自动化部署的一个框架,但国内直接访问Serverless框架的官网将会跳转到cn子域,在serverless中文网(应该是腾讯代理的)我无法找到其他Provider的操作说明和组件手册,这就很奇怪了,而且点击页面上的Serverless全球官网还是跳转到了cn子域,没办法我只有从谷歌搜索进入,发现好像从国内直接www.serverless.com会直接跳转到cn.serverless.com,比较坑。如果读者希望使用其他厂商的云服务就需要自己挂梯子看Provider References。
Serverless 框架安装
Serverless框架安装比较简单,直接使用npm进行全局安装,而其他的安装方式和升级方式请参考 Serverless Get-Started。
npm install -g serverless
遇到的问题
Serverless Aliyun组件大包上传超时
阿里云Serverless插件serverless-aliyun-function-compute
在上传大包(我这里的情况是30MB左右)后进行函数更新时默认的timeout是10s,但不知出于什么原因,可能是update时会将package解包然后再做成Docker镜像吧,这个10s的timeout对于大包更新来说时间太短了,基本上只有第一个函数更新成功,而后面的所有函数都更新失败了(我这里没有单独为函数package,因为我使用serverless-bundle好像不支持individually打包,不知道为啥),这里没有办法,我看了下这个插件的源码,没有提供更新函数时timeout的配置入口,而是直接没有传这个参数到implementation中,因此这里我们需要将node_modules内的这部分代码修改一下,我没有看完所有的源码,只是将deployFunciton这部分阶段的代码看了下,所以就不解释这个执行过程了。
但是可以看到是先是setupFunction文件中createOrUpdateFunctions执行,其中将functions forEach出来,每个function都调用了createOrUpdateFunction,而后createOrUpdateFunction针对每个云函数调用了this.provider.updateFunction,这里其实比较清楚的是provider是指的cloud provider这里就是aliyun自己(应该是为其他插件提供一个wrapper的能力吧)。
async function createOrUpdateFunctions() { for (var i = 0; i < this.functions.length; i++) { const func = this.functions[i]; await this.createOrUpdateFunction(func); } } async function createOrUpdateFunction(func) { if (this.functionMap.get(func.name)) { this.serverless.cli.log(`Updating function ${func.name}...`); try { await this.provider.updateFunction(func.service, func.name, func); this.serverless.cli.log(`Updated function ${func.name}`); } catch (err) { this.serverless.cli.log(`Failed to update function ${func.name}!`); throw err; } return; } }
JWT 阿里云API网关配置
JsonWebToken是由Auth0指定推广的一种signature规范,这里我们简单说明一下如何在阿里云Serverless技术栈中通过API Gateway完成JWT鉴权功能实现。
API网关中的JWT插件
我们先来说明一下如何在API网关中开启JWT鉴权功能,首先在API网关中添加JWT鉴权插件,在JWT鉴权插件的配置中下方部分为JWK格式的秘钥,JWK格式全称为JsonWebKey。阿里云官方文档中也提供了一个公开的JWK生成网站。这里我们需要将该配置中JWK配置项更新为自己的JWK。而后中间部分为JWT规范中用户自定义的claims段,这里面一般用于存储用户的UUID和权限,但需要注意的是claims段是不加密的,即此处不应该存储任何敏感数据。该配置项顶部为JWT令牌所处的位置,API网关将对绑定该插件的API进行令牌校验,如果在该配置块中的位置没有JWT令牌,则直接返回400,这里需要注意的是将不会返回任何可用数据,只会在响应Header中表明JWT required。
将需要权限的API绑定到JWT插件上
在JWK插件中可以将需要鉴权的API绑定起来。这里就不赘述了,比较简单。
JWT令牌生成
对于任何一个鉴权服务来说,都需要一个授权方和多个被授权方,这里授权方在云计算场景下是开放的RESTfulAPI云函数,该API通过API网关直接开放,可以设置一定的call limit,也可以开启防重放攻击。
这里我使用的是一个叫jsonwebtoken的库进行签名生成。
import * as jwt from 'jsonwebtoken' const private_key_sec = Buffer.from(process.env.JWT_SECRET); export const handler = (rawevent: any, context: any, callback: any) => { ......... //@ts-ignore let token = jwt.sign({ "user_uuid": "0d862d83-adb2-11ec-9ab1-00163e1c8a9c" }, private_key_sec, { expiresIn: process.env.EXPIRESIN, algorithm: process.env.ALG, keyid: process.env.KEYID, header: { kid: process.env.KEYID, alg: process.env.ALG } }) ...... callback(null, { token }); }
这里可以看到我的JWT_SECRET是通过环境变量注入到函数中的,在阿里云函数计算服务中,可以在函数级别添加环境变量,但没有一个进行敏感数据集中管理的机制,比较麻烦。
这里的jwt.sign()函数第二个参数接收的是对应的Secret,这里我是用的算法是RS256-SHA,因此这里需要填写对应的RSA私钥。
但是我们这里只有JWK,因此需要将JWK转换为RSA私钥。这里我使用了一个叫pem-jwk的库,将JWK转换为了RSA私钥。
然后在客户端上我们就可以将token填入对应的位置,进行鉴权请求了。
Prisma Package错误
愚蠢的BigInt错误
VPC错误(Virtual Private XXX)
2022-05-14 wep 撰写文章第一部分内容