青岛企业网站设计制作,软件开发的步骤,有哪些做平面设计好素材网站,吴江规划建设局网站依赖项的处理与层的创建与注册
依赖项的处理与层的创建与注册 新问题什么是 layer?layer 的创建与注册 与函数同时创建和绑定单独上传 layer 再绑定函数(推荐) 真正的运行时依赖 注册包的约定与平台强关联的运行时 1. 云端安装依赖2. 本地构建 Amazon Linux 2 容器环境3. 利用…
依赖项的处理与层的创建与注册
依赖项的处理与层的创建与注册 新问题什么是 layer?layer 的创建与注册 与函数同时创建和绑定单独上传 layer 再绑定函数(推荐) 真正的运行时依赖 注册包的约定与平台强关联的运行时 1. 云端安装依赖2. 本地构建 Amazon Linux 2 容器环境3. 利用 CI 构建并进行上传和部署 镜像部署Next Chapter完整示例及文章仓库地址
新问题
在一个 nodejs 函数里我们往往会去安装和使用各种各样的依赖包来辅助我们项目的开发。
这些依赖以及对应的版本都被注册在了 package.json里 (由 npm init创建)。在安装依赖之后它们会被存放在当前项目的 node_modules 目录里。而 sls deploy 默认也会把 node_modules 里所有文件打包压缩后和代码一起上传到云上。这点很好理解没有依赖项函数压根跑不起来。
但是当我们 node_modules 依赖足够大足够深之后整个 node_modules 就会变成一个黑洞。随随便便就有 1GB/2GB 甚至更多这时候去进行 sls deploy 就会变成一种折磨因为要压缩 node_modules这个行为耗时太长了即使压缩好了上传到 S3 对象存储也要花费很久的时间。
如何解决 这时候就需要让我们的 layer(层)出场了。
什么是 layer?
layer 你可以理解成预先放置在我们 Lambda 函数容器中的文件包你可以在里面放一些代码库数据文件配置文件甚至是一些自定义语言运行时。比如我在使用的时候往往会把 运行时 依赖的包打成 zip 上传上去又或者有些爬虫函数需要使用 chromium 来模仿用户的行为那么这种case则需要在 layer 里面直接内置一个 headless chromium。
这些文件最终都会被挂载到函数容器中的 /opt 目录。
layer 的创建与注册
这里我给出一个示例假设运行时的依赖只有 uuid (假设我们只依赖这一个包)
我们现在项目中安装 npm i uuid 并使用它:
// index.ts
import { v4 } from uuid
v4()
// ...code...这时候 uuid 在函数package.json这一层的 node_modules 文件夹里测试运行后运转良好。
让我们在当前目录下创建一个 layers 文件夹然后再在里面创建一个 uuid(名称可自定义)文件夹创建 package.json 并写入
{dependencies: {uuid: ^9.0.0}
}layers/uuid/package.json#dependencies 字段中的 uuid 即为你主目录下安装的版本。 然后执行 npm i/yarn 安装依赖 (不要使用pnpm)安装完成后会出现 layers/uuid/node_modules 文件夹接下来就可以 layer 的上传和绑定了。
与函数同时创建和绑定
我们可以复用原先部署函数的那个 serverless.yml 文件让它不但部署函数也同时创建 layer 并进行绑定。具体配置如下
layers:# layer 配置uuid:# 路径path: layers/uuid# aws lambda layer 里的名称name: ${sls:stage}-uuidfunctions:api:# ...package:individually: true# 既然我们把所有运行时都打成 layer 了自然不用上传 node_modules 了patterns:- !node_modules/**# layer 绑定配置layers:# 绑定 UuidLambdaLayer- !Ref UuidLambdaLayerenvironment:# 环境变量告诉函数应该从哪里找依赖NODE_PATH: ./:/opt/node_modules其中最重要的就是2个layers的配置了其中 !Ref UuidLambdaLayer 这个其实就是引用了layers.uuid只不过它的命名是一种约定即 uuid 的 u 大写加上 LambdaLayer于是就变成了 UuidLambdaLayer 了命名规则为 layer 名称的 Title_Case 加 LambdaLayer。 注意NODE_PATH: ./:/opt/node_modules 这个环境变量是必不可少的不然会导致无法从 layer 中加载 node_modules 相关配置的参考链接 此时使用 sls package 会在 .serverless 目录下生成 2 个 zip:
api.zip 函数代码包uuid.zip layer包
这2个压缩包的名字就是我们在 serverless.yml注册的名字。
sls deploy 会把它们依次上传先layer后function并把layer上传部署后的结果注册进我们的function从而达成绑定的效果。
单独上传 layer 再绑定函数(推荐)
除了与函数同时创建和绑定的形式我们还可以分步骤单独上传 layer 再绑定函数这也是我推荐的方式。
这个就顾名思义我们可以先上传layer拿到结果在把结果写到函数的 serverless.yml 中去。这样步骤方面分为了 2 步但是好处却是显而易见的。
它适用于这样的场景我们的 layer 包很大且不经常更新这种情况是没有必要每次都去打包上传 layer 的。
我们部署只需要部署我们自己的函数代码然后告诉 AWS 应该去绑定哪个 layer 的哪个版本就行。
按照这样的思路我们就可以把刚刚那个 serverless.yml 拆成 2 个 yml 文件:
单独上传 layer 配置文件函数的 deploy 配置文件加一行绑定 layer 的配置即可
单独部署 layer 的配置内容如下:
# layers/serverless.yml
layers:uuid:path: uuidname: ${sls:stage}-uuid执行 sls deploy 后上传部署成功会显示:
layers:uuid: arn:aws-cn:lambda:cn-northwest-1:000000000000:layer:dev-uuid:2这一个字符串就是接下来我们需要写入函数 yml 配置进行绑定的关键当然当时没保存刷了terminal也没关系这个信息通过在当前目录执行 sls info 还会显示出来。
接下来我们去函数的 yml 配置去绑定 layers:
# serverless.yml
functions:api:# ....layers:# - !Ref UuidLambdaLayer 改为- arn:aws-cn:lambda:cn-northwest-1:000000000000:layer:dev-uuid:2这样再在函数这一层执行 sls deploy 这层绑定关系就完成了同时上传速度也要比 与函数同时创建和绑定 快不少因为这种方法只要上传函数的代码包再通过调用 AWS API 告诉它们这层绑定关系即可。
真正的运行时依赖
之前有一个细节没有讲为什么我们上传 layer 是单独建一个 layers 的目录在里面单个单个上传而不是把外面函数的 node_modules 整个打包上传上去呢
其实那样也可以但是不够完美因为这样做会导致layer代码包中的文件过于冗余。
举个简单的例子默认情况下 npm 安装包时会把 devDependencies 和 dependencies 都安装在 node_modules。
比如 dependencies 里就是一些 express/lodash 啥的devDependencies 里面就是 eslint/sass/webpack 这些开发时用的包运行代码的时候用不着。
这时候你直接打包上传 node_modules 显然是可以的因为你所有的运行时依赖已经在里面了不幸的是 eslint 等等许多无关紧要的包也进去了这些加起来有可能会占用你 layer 包整体体积的一般以上甚至更多显然这是没有必要的。
所以你必须找到真正的运行时依赖
注册包的约定
首先你必须在安装包时正确的注册 devDependencies 和 dependencies。
我们把运行时用的到的放在 dependencies 里到时候也从这里面抽出包名和版本做成 layer。至于devDependencies 里我们只会把那些开发时用得到的包放在里面它们就没有必要上传了本地运行即可。
与平台强关联的运行时
其次你必须很清楚你的运行时是 nodejs 而不是什么浏览器。
虽然说 nodejs 是跨平台的但是我们使用的第三方库里有时候会存在着很多和平台强关联的二进制文件。
比如有些包会在下载下来之后调用 postinstall 脚本获取我们的系统信息(平台,cpu架构)然后根据这些信息去使用不同的二进制文件。
又或者有些包基于 C addons 的安装好之后才在我们机器上实时去编译生成适配我们系统平台的二进制文件。
这就导致一个问题假如你直接用 win/macos 开发你安装的二进制包大概率无法在 aws lambda 环境下运行因为它的环境是 Amazon Linux 2
所以我们应该在挑选与线上 Lambda 运行时相似的容器中进行开发上传代码和层 (layer)
具体怎么做呢这里给出三种解决方案:
1. 云端安装依赖
顾名思义直接在云上的目标容器环境中进行安装依赖这个行为这也是最简单的解决方案。
2. 本地构建 Amazon Linux 2 容器环境
这要求我们在 docker 容器中开发拉下 Amazon Linux 2 的镜像在里面开发并上传层函数和代码包。
3. 利用 CI 构建并进行上传和部署
这个思路便是由环境相同或者相似的 CI 容器进行上传和部署。
比如我们可以利用 Github Action 去构建和上传大致的配置如下:
name: Layeron:workflow_dispatch:jobs:upload:name: uploadtimeout-minutes: 15strategy:fail-fast: falsematrix:# 找一个相似的镜像 osos: [ubuntu-latest]node-version: [18]runs-on: ${{ matrix.os }}steps:- name: Check out codeuses: actions/checkoutv3with:fetch-depth: 2- name: Configure AWS credentials from Test accountuses: aws-actions/configure-aws-credentialsv3with:aws-region: cn-northwest-1aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}- name: Setup Node.js environmentuses: actions/setup-nodev3with:node-version: ${{ matrix.node-version }}- name: Install Serverlessrun: yarn global add serverless- name: Install puppeteer Layerrun: yarnworking-directory: apps/aws-express-api-ts/layers/puppeteer- name: sls deployrun: yarn deployworking-directory: apps/aws-express-api-ts/layers镜像部署
当然假如你使用镜像去部署 lambda那以上这些问题都可以避免但是代价便是冷启动时间比较长。
不过很多技术上的问题都是可以通过付出更多金钱的方式去解决的这点就综合考虑利弊吧。
Next Chapter
现在你已经学会了 layer 的用法和一些稀奇古怪的场景。
下一篇《lambda nodejs 函数降低冷启动时间的最佳实践》中将会详细介绍如何优化我们自身的代码欢迎阅读。
完整示例及文章仓库地址
https://github.com/sonofmagic/serverless-aws-cn-guide
如果你遇到什么问题或者发现什么勘误欢迎提 issue 给我