依赖项的解决与层的创立与注册
-
依赖项的解决与层的创立与注册
- 新问题
- 什么是
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}-uuid
functions:
api:
# ...
package:
individually: true
# 既然咱们把所有运行时都打成 layer 了天然不必上传 node_modules 了
patterns:
- "!node_modules/**"
# layer 绑定配置
layers:
# 绑定 UuidLambdaLayer
- !Ref UuidLambdaLayer
environment:
# 环境变量,通知函数应该从哪里找依赖
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: uuid
name: ${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: Layer
on:
workflow_dispatch:
jobs:
upload:
name: upload
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
# 找一个类似的镜像 os
os: [ubuntu-latest]
node-version: [18]
runs-on: ${{matrix.os}}
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Configure AWS credentials from Test account
uses: aws-actions/configure-aws-credentials@v3
with:
aws-region: cn-northwest-1
aws-access-key-id: ${{secrets.AWS_ACCESS_KEY_ID}}
aws-secret-access-key: ${{secrets.AWS_SECRET_ACCESS_KEY}}
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: ${{matrix.node-version}}
- name: Install Serverless
run: yarn global add serverless
- name: Install puppeteer Layer
run: yarn
working-directory: apps/aws-express-api-ts/layers/puppeteer
- name: sls deploy
run: yarn deploy
working-directory: apps/aws-express-api-ts/layers
镜像部署
当然,如果你应用镜像去部署 lambda
,那以上这些问题都能够防止,然而代价便是冷启动工夫比拟长。
不过很多技术上的问题,都是能够通过付出更多金钱的形式去解决的,这点就综合思考利弊吧。
Next Chapter
当初你曾经学会了 layer
的用法和一些稀奇古怪的场景。
下一篇,《lambda nodejs 函数升高冷启动工夫的最佳实际》中,将会具体介绍如何优化咱们本身的代码,欢送浏览。
残缺示例及文章仓库地址
https://github.com/sonofmagic/serverless-aws-cn-guide
如果你遇到什么问题,或者发现什么勘误,欢送提 issue
给我