Serverless Framework 变量(Variables)与 Resolver 体系全解析:从${}动态配置到多账号解析原理
【免费下载链接】serverless⚡ Serverless Framework – Effortlessly build apps that auto-scale, incur zero costs when idle, and require minimal maintenance using AWS Lambda and other managed cloud services.项目地址: https://gitcode.com/GitHub_Trending/se/serverless
变量系统是 Serverless Framework 配置能力的基石:它允许你在serverless.yml中用${}语法动态引用环境变量、CLI 参数、外部文件、Git 信息、AWS SSM/S3 等外部数据源,并结合 stage 实现多环境差异化配置。本文以 docs/sf/guides/variables/README.md 为主线,完整讲解变量语法、Resolver/Provider 机制、默认值回退与递归引用,并深入packages/sf-core的解析器源码,帮助你既能直接上手配置,也能理解框架底层"如何解析与替换"的实现原理。
变量语法入门:${}引用与默认值
在 Serverless Framework 中,变量(Variable)允许你在serverless.yml的属性值中动态替换配置内容。它最常见的两个用途是:为服务提供密钥(secrets),以及在多 stage 工作流中提供差异化配置。
使用方式是将引用值用${}包裹:
# serverless.yml file yamlKeyXYZ: ${provider:resolver:key} # see list of current resolver providers below # this is an example of providing a default value as the second parameter otherYamlKey: ${provider:resolver:key, defaultValue}其中${provider:resolver:key}是完整的解析式:provider是解析提供方名称,resolver是解析器名称,key是待解析的数据键(例如 SSM 参数路径、S3 的bucket/key)。
重要限制:变量只能出现在serverless.yml属性的值中,不能用于属性键。例如不能在 custom resources 段落中通过变量生成动态 Logical ID——因为键的位置不会被变量系统扫描处理。从源码看,占位符收集器collectFromObject只递归遍历对象的value而不会改写key(见 placeholders.js)。
Resolver 与 Provider:变量的两级抽象
**Variable Resolvers(变量解析器)**允许你在serverless.yml中引用外部数据源。每个 Resolver 都有一个Provider父级,Provider 负责获取凭证(credentials)。例如awsProvider 下挂载了ssm与s3两个 Resolver,分别从 AWS SSM Parameter Store 和 S3 拉取数据。
Provider 还可以暴露内置变量,例如awsProvider 的accountId。这类变量由 Provider 直接解析,无需额外配置凭证来源(使用部署凭证本身)。
自定义 Provider 与 Resolver
你可以在stages段的resolvers块中自定义 Provider/Resolver 配置,随后用${customProviderName:customResolverName:key}语法引用自定义版本。
stages: default: resolvers: awsAccount1: type: aws profile: dev-account1-profile-name awsAccount2: type: aws profile: dev-account2-profile-name euS3: # custom resolver configuration defined for the awsAccount2 provider type: s3 region: eu-west-1 prod: resolvers: awsAccount1: type: aws profile: prod-account1-profile-name awsAccount2: type: aws profile: prod-account2-profile-name euS3: # custom resolver configuration defined for the awsAccount2 provider type: s3 region: eu-west-1 functions: hello: handler: handler.hello environment: ACCOUNT1_ID: ${awsAccount1:accountId} # built-in variable provided by the AWS provider SSM_VALUE: ${awsAccount1:ssm:/path/to/param} # uses the default resolver configuration even if it's not explicitly defined in the resolvers block EU_S3_VALUE: ${awsAccount2:euS3:myBucket/myKey} # uses the customized resolver configuration S3_VALUE: ${awsAccount2:s3:myBucket/myKey} # uses the default resolver configuration even if a customized one (euS3) is defined for the same provider这个例子的语义要点:
default与prod两个 stage 各自定义了相同的 Provider 名,但 profile 不同——框架解析时只保留当前 stage 与default的配置(源码中由pruneUnusedStages()删除无关 stage,见 manager.js),从而天然实现"同配置、多环境切换凭证"。${awsAccount1:accountId}引用的是aws类型 Provider 的内置变量。${awsAccount2:euS3:myBucket/myKey}用的是自定义解析器euS3(region 固定为eu-west-1)。${awsAccount2:s3:myBucket/myKey}即便同名 Provider 下定义了自定义euS3,仍会解析为默认的s3Resolver 配置。
关键规则:即使你不显式定义,也始终可以引用 Provider 提供的默认 Resolver。例如直接用${aws:s3:myBucket/myKey},它会使用部署所用的同一 AWS Provider(即凭证提供方)与默认 Resolver 配置;若定义了自定义 Provider 配置,则可用${customProviderName:s3:myBucket/myKey}。
与底层实现对照
上述两级抽象在源码中有清晰对应:
- 每个 Provider 是一个继承自
AbstractProvider的类,类上通过静态字段声明type、resolvers、defaultResolver(见 providers/index.js),实际 Provider 类由providerRegistry注册维护(registry/index.js)。 createResolverProvider(providers.js)按配置创建 Provider 实例,并把 provider 配置对象中所有{ type: ... }形式的分支注册为自定义 Resolver(addResolversForProvider);随后再补齐该 Provider 的其余默认 Resolver(Provider.resolvers),这就从实现上保证了"默认 Resolver 永远可用"。- 凭证解析由
AbstractProvider.resolveCredentials()钩子负责,且同一时刻只保存一份凭证 promise,避免并发重复获取;自定义 Provider 的profile等配置正是凭证获取的输入。 - 当配置中存在多个
type: aws的 Provider 而provider.resolver未指定时,框架会直接报错要求显式指定部署凭证来源;未定义任何自定义 Provider 时,则回退到default-aws-credential-resolver(用部署默认凭证链),逻辑见 manager.js。
支持的变量 Provider 清单
框架内置了多类 Provider,每个都可以像上面的语法一样按${provider:key}或${provider:resolver:key}引用。完整清单与独立文档如下(路径均为仓库根相对路径):
- Self-References:引用
serverless.yml自身属性 - Serverless 核心变量(Core Variables)
- 环境变量(Environment Variables)
- CLI 选项(CLI Options)
- 外部 YAML/JSON 文件(External Files)
- JavaScript 动态取值
- Git 信息
- AWS(含 SSM、S3、CloudFormation Outputs 等)
- HashiCorp(含 Terraform、Vault)
递归引用:在变量中嵌套变量
变量系统支持递归引用属性:你可以把多个取值来源自由组合。一个经典场景是把 stage 嵌入到文件名中,按环境加载不同配置文件:
provider: name: aws environment: MY_SECRET: ${file(./config.${sls:stage}.json):CREDS}如果执行sls deploy --stage qa,stage 被置为qa,内层${sls:stage}先解析为qa并拼入外层文件路径,最终读取config.qa.json中的CREDS键赋给MY_SECRET。若执行sls deploy --stage prod,则对应找到config.prod.json。
解析的完整过程如下:
stage由命令行选项--stage qa决定;如果命令行未提供,则${sls:stage}回退到provider.stage的值,仍未设置时默认取dev。- 内层
${sls:stage}解析为qa,并作为外层${file(...)}变量 key 的一部分参与求值。 - 定位并读取
./config.qa.json,取出其中的CREDS值。 - 将解析结果写入
MY_SECRET属性。
在底层,这种"先内后外"的顺序并非靠简单的字符串替换完成。框架将每一个${}视为图(Graph)中的一个节点,并通过依赖边保证嵌套占位符先于外层占位符被解析:extractPlaceholdersFromString递归扫描字符串中所有${、}配对,把内层占位符的解析结果回填到外层 key 中(见 placeholders.js 与#updatePredecessorNodes对前驱节点的更新逻辑)。若变量间形成循环引用,框架会抛出RESOLVER_CYCLIC_REFERENCE错误并指明循环链路(placeholders.js),且整张图在 graph.js 中被并行处理以提升大配置的解析速度。
用 Parameters 设置 stage 级变量
有时你希望直接在serverless.yml中定义一个贯穿全文的变量。这时可以使用Parameters:既能声明新变量,也能设置按 stage 区分的变量值。下面的例子按 stage 设置域名:
stages: default: params: domain: ${sls:stage}.example-dev.com prod: params: domain: example.com provider: environment: APP_DOMAIN: ${param:domain}在dev环境(默认 stage)解析得到dev.example-dev.com,在prod环境解析得到example.com。Params 的取值优先级与来源合并逻辑可在 manager.js 中看到:最终 params 由 CLI--param选项、params.default、params.<stage>以及stages.<stage>.params等逐级合并得到。完整用法请参考 Parameters 文档。
多配置文件:拆分庞大的serverless.yml
当serverless.yml中堆积了大量自定义资源时,文件会迅速膨胀。借助变量语法可以把资源定义拆到独立文件中:
resources: Resources: ${file(cloudformation-resources.json)}cloudformation-resources.json中定义的资源会被解析并加载进Resources段。
如果希望"内联资源 + 外部文件资源"并存,可以把resources写成数组,将多个来源合并:
resources: - Resources: ApiGatewayRestApi: Type: AWS::ApiGateway::RestApi - ${file(resources/first-cf-resources.yml)} - ${file(resources/second-cf-resources.yml)} - Outputs: CognitoUserPoolId: Value: Ref: CognitoUserPool注意:每个 CloudFormation 文件都必须以Resources实体开头:
Resources: Type: 'AWS::S3::Bucket' Properties: BucketName: some-bucket-name(注:此处示例沿用了原文档的写法;实际部署时通常还需为该 Bucket 资源提供合理的Type与所属Resources:层级。)
默认值(Default Values)与回退策略
框架提供了直观的多变量回退机制:当第一个变量取不到值时,自动尝试下一个来源,从而为"主数据源缺失"的场景提供兜底默认值。
例如用opt变量获取 CLI 选项:运行serverless deploy --memory 2048时读取memory;若未提供该选项,则使用默认值1024。
functions: hello: handler: handler.hello memorySize: ${opt:memory, 1024}默认值本身也可以是另一个变量,形成链式回退,例如${opt:memory, self:custom.defaultMemorySize}。
底层实现中,一个${a, b, c}会被拆解为多个 fallback:extractPlaceholderDetailsFromPlaceholderString用逗号切分出每个回退分支,带引号的字面量被解析为literalValue,否则继续按占位符解析(placeholders.js)。解析时按顺序逐个尝试各 fallback,遇到null/空值则跳到下一个;若全部失败,则抛出RESOLVER_MISSING_VARIABLE_RESULT错误并提示"请检查变量定义或提供默认值"(见 manager.js)。
将字符串变量读取为布尔值:strToBool
有些配置项要求布尔类型(如true/false)。当用变量提供该值时,来源往往返回字符串——比如 SSM 参数读出来的就是"true"/"false",直接赋值会因类型不符而出错。
此时可以用strToBool解析器把字符串显式转换为布尔值:
provider: tracing: apiGateway: ${strToBool(${ssm:API_GW_DEBUG_ENABLED})}转换规则(先统一转为小写再判断):
${strToBool(true)} => true ${strToBool(false)} => false ${strToBool(True)} => true ${strToBool(False)} => false ${strToBool(TRUE)} => true ${strToBool(FALSE)} => false ${strToBool(0)} => false ${strToBool(1)} => true ${strToBool(2)} => Error ${strToBool(null)} => Error ${strToBool(anything)} => Error也就是说:只有true/1与false/0(大小写不敏感)是合法输入,其余值一律抛错。其实现位于StrToBoolProvider(str-to-bool.js):内部用trueStrings = {'true','1'}、falseStrings = {'false','0'}两个集合,先对输入trim().toLowerCase()再做集合判定,匹配失败即抛出含明确提示的错误。测试用例位于 packages/sf-core/tests/integration/resolvers/str-to-bool(以 yml fixture 驱动端到端验证)。
核心变量sls:instanceId 与 stage
框架内部本身也会初始化若干核心变量,这些值通过{sls:}前缀暴露给用户复用。
instanceId
instanceId是每次运行 Serverless CLI 时生成的随机 ID,适用于需要"可预测的随机值"的场景(例如给 API Gateway 部署追加唯一后缀):
service: new-service provider: aws functions: func1: name: function-1 handler: handler.func1 environment: APIG_DEPLOYMENT_ID: ApiGatewayDeployment${sls:instanceId}在源码中它并不是真正的随机数,而是new Date().getTime().toString()生成的时间戳字符串,且在SlsProvider 的静态字段上缓存,保证一次 CLI 运行周期内该值恒定(见 sls.js)。
stage
stage是当前 CLI 使用的 stage 值。${sls:stage}相当于快捷写法${opt:stage, self:provider.stage, "dev"}——即:优先取--stage命令行选项,其次取provider.stage,最后默认dev。这与解析管理器resolveStage()的逻辑一致:命令行未提供 stage 时,从provider.stage取值;provider.stage为空或可解析为占位符时最终落回dev(manager.js)。
AWS 专属变量与配置(速览)
awsProvider 除了提供ssm、s3、cf(CloudFormation Outputs)三类 Resolver 外,还暴露几个常用内置变量(详见 AWS Variables 文档):
${aws:accountId}:基于已配置 AWS 凭证解析出的账号 ID。${aws:region}:相当于${opt:region, self:provider.region, "us-east-1"}。${aws:partition}:由 region 本地推导(不发起 AWS API 调用),用于拼接跨分区 ARN,例如us-east-1 → aws、cn-north-1 → aws-cn、us-gov-west-1 → aws-us-gov,未知区域回退aws:
service: new-service provider: name: aws functions: func1: name: function-1 handler: handler.func1 environment: QUEUE_ARN: arn:${aws:partition}:sqs:${aws:region}:${aws:accountId}:my-queue自定义awsProvider 的常见配置项包括profile、region、accessKeyId/secretAccessKey/sessionToken、dashboard(是否使用 Serverless Dashboard Provider 凭证)等。各 Resolver 的独立文档可继续阅读 S3、SSM & Secrets Manager、CloudFormation Outputs。
解析引擎是怎么工作的:一个图驱动的替换过程
了解了上述所有变量用法后,理解整个解析器的运转顺序会更有帮助。从 resolvers/index.js 与 manager.js 可以看到框架分阶段解析serverless.yml,而不是一次性暴力替换:
- 校验配置:
ensureNoParamsAndStagesTogether、validateCustomResolverConfigs、validateResolversUniqueness分别校验 params 与 stages 是否混用、自定义 Resolver 配置是否合法、是否有重复名称(validation.js)。 - 确定 stage:无
--stage时取provider.stage,否则默认dev;同时加载.env与.env.<stage>文件(loadEnvFiles)。 - 首轮受限解析:先用一组"轻量 Provider"(
env、opt、file、sls、strToBool、git、self、param,见 manager.js)解析org、app、service、provider.region、params、provider.profile、provider.resolver等关键路径——因为这些路径可能决定后续凭证与阶段选择。 - 确定凭证 Resolver:依据
provider.resolver、配置中type: aws的 Provider 数量决定"谁提供部署凭证";只保留当前 stage 与default段。 - 二次扫描 + 全量解析:重新收集占位符构建依赖图,把自定义 Provider 与内置 Provider 一并注册进图,然后按依赖边并行解析所有剩余占位符并回填到配置。
- 输出明细:若开启了相关输出,
printResult会以表格形式打印每个替换项(含配置路径Path、原始值Original、解析结果Resolved以及 Provider/Resolver 类型明细),便于排障(见 index.js)。
顺带一提,占位符语法本身也在 placeholders.js 中做了归一:主正则^([^:()]+)(?:\((.*)\))?(?::(.*))?$负责解析${provider:resolver:key}与${file(...)}风格;另一条 legacy 正则还兼容${ssm(...):...}、${s3(...):...}、${cf(...):...}等旧式写法,并对${AWS::xxx}伪参数、CloudWatch 动态标签、Fn::Sub中的!字面量做豁免(不当作变量解析)。这也是为什么文档中的${ssm:API_GW_DEBUG_ENABLED}这类旧式写法依然能工作。
小结
本指南覆盖了 Serverless Framework 变量系统的完整脉络:${}语法与"只能用于属性值"的约束、Provider/Resolver 两级抽象与"默认 Resolver 始终可用"的规则、自定义多账号 Provider、递归引用与回退默认值、strToBool类型转换、sls核心变量,以及多配置文件拆分技巧。配合packages/sf-core的图驱动解析实现(分阶段受限解析 → 依赖图 → 并行替换),你可以更有把握地设计跨 stage、跨账号的安全动态配置,并在解析失败时依据错误信息与源码快速定位问题。每个 Provider 的细化用法还可继续查阅前文"支持的变量 Provider 清单"中各子文档。
【免费下载链接】serverless⚡ Serverless Framework – Effortlessly build apps that auto-scale, incur zero costs when idle, and require minimal maintenance using AWS Lambda and other managed cloud services.项目地址: https://gitcode.com/GitHub_Trending/se/serverless
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考