头图

更灵活的 serverless framework 配置文件

前言

再经过前置教程的部署之后,不知道你有没有注意这样一个问题,就是我们部署的函数名,以及 API网关endpoint,它们的名称和路径都带一个 dev?

这个就是 stage 导致的了,我们执行 sls deploy 部署的时候,由于没有指定 --stage 的参数,导致它默认就是 dev,所以我们之前部署的函数名称,网关里面都带它。

那么它有什么作用呢?实际上这个值就是用来给你的函数,以及对应的服务去区分阶段/环境的。

比如我们一个提供 web 服务的函数,我们自己人为划分出三个环境:

  1. dev 用于给开发者自行测试
  2. sit 用于进行集成测试
  3. prod 生产环境

不同的环境,它们各自的 API网关 分配的 endpoint 也是不同的。

假如你有一个域名,你就可以把域名多配置一些主或多级域名,把它们的 CNAME 解析到指定的 API网关 地址来使用。当然你仅仅在域名控制台,直接解析是不生效的,因为API网关有个双重验证,你必须进入API网关自定义域名界面,创建自定义域名,并绑定 ACM 证书,审核通过后解析才能生效。

什么是 ACM证书ACM完整名称为Amazon Certificate Manager点击这里查看更多

放心,ACM 证书 申请和颁发非常简单,它的功能和申请流程和我们申请免费的 SSL/TLS 证书是一致的,都是我们域名多解析一个 CNAME的事情。

这时候我们自然可以利用 cli option 去设置:

"scripts": {
  "deploy:dev": "sls deploy",
  "deploy:sit": "sls deploy -s sit",
  "deploy:prod": "sls deploy -s prod"
},

但是随着项目的日益复杂,你会发现使用 CLI 命令,不断的去增加配置项,这种方式既繁琐,又效率低下,有什么方式可以用一个变量去控制大量的配置呢?

<!-- 普通的 nodejs 项目我们通常会想到 dotenv,幸运的是,serverless framework 同时也给我们提供了更多的选择。 -->
serverless framework里,通常我们可以使用 动态变量 的方式去解决这个问题。

动态变量

什么是动态变量?实际上它们就是特殊写法的字符串罢了。变量常见的写法如下所示:

${variableSource}
${sls:stage}-lambdaName
${env:MY_API_KEY}
${file(create_request.json)}
${self:service}:${sls:stage}:UsersTableArn

serverless.yml 支持使用上述变量的方式,来实现配置的引用与读取,它们是非常有用的,毕竟你不可能把某些配置项,诸如 secret 什么的直接 inline 写在 yml 文件里。

其中 ${} 就是变量引用的写法,会在 sls cli 运行的时候,把它们替换成真正的值。

# ${} 第二个参数是默认值
otherYamlKey: ${variableSource, defaultValue}

这里介绍一些常用的变量:

self

首先必须要讲的就是 self 了,它是我们应用配置自身其他值的关键,self 指向的就是我们 yml 配置的根节点。这里我给出一个示例,相信聪明的你可以一眼看出它的用法。

service: new-service
provider: aws
custom:
  globalSchedule: rate(10 minutes)
  # 引用的第一行 service: new-service
  serviceName: ${self:service}
  # 引用的上一行 serviceName: ${self:service}
  exportName: ${self:custom.serviceName}-export

functions:
  hello:
    handler: handler.hello
    events:
      # ${self:someProperty} 
      # self 指向的就是 yml 配置的根节点,所以才能 
      - schedule: ${self:custom.globalSchedule}
resources:
  Outputs:
    NewServiceExport:
      Value: 'A Value To Export'
      Export:
        Name: ${self:custom.exportName}

env

顾名思义,使用系统配置的环境变量:

service: new-service
provider: aws
functions:
  hello:
    name: ${env:FUNC_PREFIX}-hello
    handler: handler.hello

这个一般配合 dotenv 等工具比较好用。

sls

这个变量可以获取一些 Serverless Core 值,比如 instanceIdstage,用例如下

service: new-service
provider: aws
 
functions:
  func1:
    name: function-1
    handler: handler.func1
    environment:
      APIG_DEPLOYMENT_ID: ApiGatewayDeployment${sls:instanceId}
      STAGE: ${sls:stage}

其中 ${sls:stage} 指令的实质实际上是 ${opt:stage, self:provider.stage, "dev"} 的缩写形式,所以你也明白为什么默认值是 dev 了。

opt

这个变量就是去取 CLI 传入的 Options 里面的值:

service: new-service
provider: aws
functions:
  hello:
    name: ${opt:stage}-hello
    handler: handler.hello

file

模块化配置的核心方法/变量,使用这个变量方法可以去读取文件,并进行引用,例如我们可以在这里引入另外的 yml,json,js文件:

# 你甚至可以引入整个yml文件作为配置
custom: ${file(./myCustomFile.yml)}
provider:
  name: aws
  environment:
    # 引入 json 文件
    MY_SECRET: ${file(./config.${opt:stage, 'dev'}.json):CREDS}
 
functions:
  hello:
    handler: handler.hello
    events:
      - schedule: ${file(./myCustomFile.yml):globalSchedule} # Or you can reference a specific property
  world:
    handler: handler.world
    events:
      # 甚至可以引入 `js` 文件
      - schedule: ${file(./scheduleConfig.js):rate}

这时候它就会根据我们传入的 stage 参数,去读取相对路径下不同的配置文件了。

其中引入 js 文件的代码有一定的限制,它必须是 commonjs 格式,且必须导出一个js对象,或者是导出一个function。导出对象方式很简单且泛用性不强,我们这里以方法为例:

// 目前必须是 commonjs 格式,且返回一个对象作为值
// 方法同步/异步的都可以
module.exports = async ({ options, resolveVariable }) => {
  // We can resolve other variables via `resolveVariable`
  const stage = await resolveVariable('sls:stage');
  const region = await resolveVariable('opt:region, self:provider.region, "us-east-1"');
  ...
 
  // Resolver may return any JSON value (null, boolean, string, number, array or plain object)
  return {
    prop1: 'someValue',
    prop2: 'someOther value'
  }
}

我们可以在里面获取到其他的变量,并进行额外的计算处理,或者我们可以在这里请求远端获取数据作为部署的额外配置。

更多

除此之外,它还能引用到更多服务的变量,诸如 CloudFormation,S3SSM 等等服务,更多更全面的变量详见:官方的变量大全地址

配置支持的其他格式

实际上目前的 serverless cli 不止可以接受 serverless.yml,也可以接受包括 serverless.ts, serverless.json, serverless.js 格式。

这里笔者只推荐两种格式 serverless.ymlserverless.js

为什么不是 serverless.json?因为 json 文件表现力弱,甚至要 jsonc 才支持注释,所以放弃。

为什么不是 serverless.ts,因为直接使用会出错,详见 issues/48,不够省心。这点不如使用 js + jsdoc 的组合,智能提示有了,灵活性也有了。

所以我们总结一下 serverless.ymlserverless.js 的优点:

  • serverless.yml: 足够简单,配合动态变量比较灵活
  • serverless.js: 可以使用 nodejs api,也可以配合动态变量,更加灵活,而且配置可以使用原生 js 写法。

这里我们以 serverless.js 配置文件为例:

智能提示, 需要 npm i -D @serverless/typescript
/**
 * @typedef {import('@serverless/typescript').AWS} AWS
 * @type {AWS}
 */
const serverlessConfiguration = {
  service: 'aws-node-ts-hello-world',
  frameworkVersion: '3',
  provider: {
    // ...
  },
  functions: {
    // ...
  },
}

module.exports = serverlessConfiguration

当然由于它是 nodejs 运行时的缘故,你可以把配置文件拆分成多个文件,再使用 commonjs 原生的方式去引用它们。你也可以使用环境变量,node:fsnode:path 等等模块去按条件动态去生成配置文件,甚至使用某些第三方库做一些额外的工作,这实在是太灵活了!

Next Chapter

现在你已经浅尝辄止了 serverless framework 动态配置文件。

下一篇,《与传统 nodejs web 框架的结合》中,将会详细介绍如何部署 express/koa 和以它们2个为基底的 serverless 应用,欢迎阅读。

完整示例及文章仓库地址

https://github.com/sonofmagic/serverless-aws-cn-guide

如果你遇到什么问题,或者发现什么勘误,欢迎提 issue 给我


ice_breaker
17 声望0 粉丝