关于serverless:依赖项的处理与层的创建与注册

7次阅读

共计 5004 个字符,预计需要花费 13 分钟才能阅读完成。

依赖项的解决与层的创立与注册

  • 依赖项的解决与层的创立与注册

    • 新问题
    • 什么是 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"

其中最重要的就是 2layers的配置了,其中 !Ref UuidLambdaLayer 这个其实就是援用了layers.uuid,只不过它的命名是一种约定,即 uuidu 大写加上 LambdaLayer,于是就变成了 UuidLambdaLayer 了,命名规定为 layer 名称的 Title_CaseLambdaLayer

留神!NODE_PATH: "./:/opt/node_modules" 这个环境变量是必不可少的,不然会导致无奈从 layer 中加载 node_modules

相干配置的参考链接

此时应用 sls package 会在 .serverless 目录下生成 2zip:

  • api.zip 函数代码包
  • uuid.zip layer 包

2 个压缩包的名字,就是咱们在 serverless.yml注册的名字。

sls deploy 会把它们顺次上传,先 layerfunction,并把 layer 上传部署后的后果,注册进咱们的function,从而达成绑定的成果。

独自上传 layer 再绑定函数(举荐)

除了与函数同时创立和绑定的模式,咱们还能够分步骤独自上传 layer 再绑定函数,这也是我举荐的形式。

这个就顾名思义,咱们能够先上传 layer 拿到后果,在把后果写到函数的 serverless.yml 中去。这样步骤方面分为了 2 步,然而益处却是不言而喻的。

它实用于这样的场景,咱们的 layer 包很大,且不常常更新,这种状况是没有必要每次都去打包上传 layer 的。

咱们部署只须要部署咱们本人的函数代码,而后通知 AWS 应该去绑定哪个 layer 的哪个版本就行。

依照这样的思路,咱们就能够把刚刚那个 serverless.yml 拆成 2yml 文件:

  1. 独自上传 layer 配置文件
  2. 函数的 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 安装包时,会把 devDependenciesdependencies 都装置在 node_modules

比方 dependencies 里就是一些 express/lodash 啥的,devDependencies 外面就是 eslint/sass/webpack 这些开发时用的包,运行代码的时候用不着。

这时候,你间接打包上传 node_modules 显然是能够的,因为你所有的运行时依赖曾经在外面了,可怜的是 eslint 等等许多无关紧要的包也进去了,这些加起来有可能会占用你 layer 包整体体积的个别以上甚至更多,显然这是没有必要的。

所以,你必须找到真正的运行时依赖!

注册包的约定

首先你必须在安装包时正确的注册 devDependenciesdependencies

咱们把运行时用的到的放在 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 给我

正文完
 0