news 2026/9/25 4:08:10

使用 AWS SAM 构建 Serverless 应用:从模板编写、打包到 CloudFormation 部署与内在函数实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 AWS SAM 构建 Serverless 应用:从模板编写、打包到 CloudFormation 部署与内在函数实战指南
  • 后端
  • 云原生
  • IaC

【免费下载链接】serverless-application-model

The AWS Serverless Application Model (AWS SAM) transform is a AWS CloudFormation macro that transforms SAM templates into CloudFormation templates.

项目地址:https://gitcode.com/gh_mirrors/se/serverless-application-model
点击查看免费下载

导读

本文基于 serverless-application-model 仓库的 HOWTO.md 编写,系统讲解如何用 AWS Serverless Application Model(SAM)编写 JSON/YAML 格式的 SAM 模板,并借助aws cloudformation package/aws cloudformation deploy(或对应的sam package/sam deploy)完成从代码打包、S3 上传到 CloudFormation 变更集执行的全流程。同时,我们将深入本仓库的 samtranslator 源码,揭示 SAM 模板在被 CloudFormation 转换(Transform)为原生 CloudFormation 模板时,CodeUri/DefinitionUri是如何被解析、内在函数(Ref、Fn::Sub、Fn::GetAtt、Fn::ImportValue)是如何被求解,以及哪些属性不支持ImportValue等关键底层机制。读完本文,你将能独立编写、打包并安全部署一个生产可用的 SAM 应用,并理解每一步背后的转换原理。

什么是 AWS SAM 与 SAM 模板

AWS Serverless Application Model(AWS SAM)是 AWS CloudFormation 之上的一个 Transform 宏(macro):它允许你用"SAM 模板"(一个 JSON 或 YAML 配置文件)来描述 Serverless 应用中的 Lambda 函数、API 端点以及其他资源。SAM 模板本身不是 CloudFormation 原生模板,而是需要经过Transform: 'AWS::Serverless-2016-10-31'声明的转换过程。

本仓库的核心价值就在于此:samtranslator包正是这个 Transform 宏的参考实现。它的入口函数位于 samtranslator/translator/transform.py,该函数接收 SAM 模板片段(input_fragment)和参数值(parameter_values),完成如下流水线:

  1. Parser()解析并校验模板结构;
  2. 将 SAM 模板交由Translator.translate()处理;
  3. 遍历模板中的每个 SAM 资源(AWS::Serverless::Function、AWS::Serverless::Api等),调用各自宏的to_cloudformation()方法,生成对应的原生 CloudFormation 资源;
  4. 最终返回一份可以被 CloudFormation 直接执行的"纯 CloudFormation 模板"。

你可以通过aws cloudformation或 aws-sam-cli 将 SAM 模板上传到 CloudFormation,CloudFormation 会为应用中的所有资源创建一个CloudFormation Stack(堆栈)统一管理。之后每次更新 SAM 模板,只需重新部署到同一个堆栈,CloudFormation 会负责增量更新其中的各个资源,无需手动逐个创建或删除。

从转换源码看,samtranslator/translator/translator.py 中的translate()方法是核心:它会按特定顺序遍历资源(先处理AWS::Serverless::Function与AWS::Serverless::StateMachine,因为它们可能通过 API 事件源修改对应AWS::Serverless::Api的 Swagger 定义,再处理 API,最后处理其他资源和 Connector),并把转换结果写入新模板副本。转换完成后还会执行插件钩子(after_transform_template)并删除Transform声明,最终产出纯粹的 CloudFormation 模板。

开始使用 SAM 模板

编写 SAM 模板的第一步是了解 SAM 规范本身。仓库内的规范与参考文档包括:

  • samtranslator/schema/schema.json:SAM 资源的 JSON Schema 定义(AWS::Serverless::Function、AWS::Serverless::Api等全部资源类型及其属性);
  • docs/schema.md:面向读者的规范说明;
  • samtranslator/internal/schema_source/aws_serverless_function.py 等源码文件:各个 SAM 资源类型的属性模型与校验逻辑。

要快速生成一个可用的 SAM 项目骨架,可以借助 aws-sam-cli 的sam init命令。HOWTO 中给出的示例是:

$ sam init --runtime python3.14

该命令会基于指定运行时(此处为 Python 3.14)初始化一个 SAM 工程,包含模板文件与示例代码目录。需要注意的是,--runtime取值应与你实际安装的 aws-sam-cli 版本所支持的运行时列表一致。

打包构建产物(Packing Artifacts)

在部署 SAM 模板之前,需要先把两类"本地产物"上传到 S3:

  • Lambda 函数代码(zip、jar 或源码目录);
  • API 的 OpenAPI(Swagger)定义文件。

上传后,需要在模板中把CodeUri和DefinitionUri属性指向 S3 上的文件位置。你可以手动完成上传,也可以使用aws cloudformation packageCLI 命令自动完成"本地产物 → S3"的上传,并返回一份把本地引用替换为 S3 位置的新模板副本。

模板中声明本地路径

在模板中,CodeUri与DefinitionUri先写成相对路径,如下所示(示例来自 HOWTO):

MyLambdaFunction: Type: AWS::Serverless::Function Properties: CodeUri: ./code ... MyApi: Type: AWS::Serverless::Api Properties: DefinitionUri: ./specs/swagger.yaml ...

执行打包命令

使用 AWS CLI 打包(上传产物并生成可直接部署的模板):

$ aws cloudformation package \ --template-file /path_to_template/template.yaml \ --s3-bucket bucket-name \ --s3-prefix appname/branchname/version \ --output-template-file packaged-template.yaml

使用 aws-sam-cli 的等价命令:

$ sam package \ --template-file /path_to_template/template.yaml \ --s3-bucket bucket-name \ --s3-prefix appname/branchname/version \ --output-template-file packaged-template.yaml

各参数含义:

参数说明
--template-file本地 SAM 模板文件的路径;
--s3-bucket用于存放打包产物的 S3 桶名;
--s3-prefix产物在桶内的前缀路径,建议按appname/branchname/version组织,便于版本管理;
--output-template-file打包后输出模板的保存路径,该模板可直接交给 CloudFormation 部署。

打包后的模板形态

打包命令返回的模板会将本地路径替换为 S3 URI,形态大致如下:

MyLambdaFunction: Type: AWS::Serverless::Function Properties: CodeUri: s3://<mybucket>/<my-zipfile-path> ... MyApi: Type: AWS::Serverless::Api Properties: DefinitionUri: s3://<mybucket>/<my-openapi-file-path> ...

底层原理:S3 URI 是如何被解析的

CodeUri/DefinitionUri最终会变成 CloudFormationAWS::Lambda::Function的Code属性(S3Bucket、S3Key、S3ObjectVersion),这一步由 samtranslator/model/s3_utils/uri_parser.py 中的construct_s3_location_object()完成。从源码可以总结出它支持三种输入形式:

  1. 字典形式:{ "Bucket": ..., "Key": ... },必须同时包含Bucket与Key,否则抛出InvalidResourceException(提示'CodeUri' requires Bucket and Key properties to be specified.);
  2. S3 URI 字符串:形如s3://bucket/key,可带?versionId=xxx查询参数,由 parse_s3_uri() 解析,它使用urllib.parse.urlparse把s3://的 netloc 当作 Bucket、path 当作 Key,并支持versionId查询参数转为Version字段;若解析失败会抛出明确错误提示;
  3. 动态引用(dynamic reference):源码对{{resolve:ssm:...}}、{{resolve:ssm-secure:...}}、{{resolve:secretsmanager:...}}模式做了专门检测,若在CodeUri字符串中发现此类引用会直接报错,并建议改用FunctionCode对象格式。

也就是说,aws cloudformation package只是完成了"本地路径 → S3 URI"的替换,而真正把 S3 URI 拆解为S3Bucket/S3Key并注入生成资源的逻辑,位于 samtranslator 的上述源码中。

部署到 AWS CloudFormation

SAM 模板最终通过 CloudFormation 的变更集(ChangeSet)机制部署:先用 SAM 模板创建变更集,再执行变更集。可以把变更集理解为"当前堆栈模板"与"新部署模板"之间的差异(diff)。创建变更集后,你有机会在执行前仔细检查这份差异,避免意外改动。AWS 控制台与 AWS CLI 均提供创建和执行变更集的命令。

使用 deploy 命令一键部署

更便捷的方式是使用aws cloudformation deploy,它在内部自动完成"创建变更集 → 执行变更集 → 等待部署完成",并在部署失败时打印调试提示。部署打包后的模板到名为my-new-stack的堆栈:

$ aws cloudformation deploy \ --template-file /path_to_template/packaged-template.yaml \ --stack-name my-new-stack \ --capabilities CAPABILITY_IAM

aws-sam-cli 的等价命令:

$ sam deploy \ --template-file /path_to_template/packaged-template.yaml \ --stack-name my-new-stack \ --capabilities CAPABILITY_IAM

其中--capabilities CAPABILITY_IAM是必需的:因为 SAM 模板通常会为 Lambda 函数自动创建 IAM 角色(AWS::IAM::Role),而 CloudFormation 出于安全考虑,只有在显式声明CAPABILITY_IAM(或CAPABILITY_NAMED_IAM)时才允许创建此类资源。如果你的模板还使用了命名 IAM 角色,则需改为CAPABILITY_NAMED_IAM。

底层原理:部署时模板经历了什么

从部署视角看,CloudFormation 在创建变更集时会调用 SAM Transform,也就是本仓库 samtranslator/translator/transform.py 中的transform()函数。整个转换链条包括:

  • samtranslator/parser/parser.py:Parser.parse()先校验模板结构(要求必须包含非空的Resources节、每个资源必须是对象等),再触发插件的before_transform_template生命周期钩子;
  • samtranslator/translator/translator.py:Translator.translate()安装插件(含隐式 REST API/HTTP API 插件、Globals插件、策略模板插件、Serverless 应用插件等),逐个把 SAM 资源翻译为 CloudFormation 资源,并收集文档错误;
  • 若存在任何InvalidResourceException/InvalidEventException/InvalidTemplateException,最终会抛出InvalidDocumentException,部署即失败——这也解释了aws cloudformation deploy打印调试提示时你看到错误来源。

部署完成后,后续对 SAM 模板的修改只需再次执行sam deploy(指向同一--stack-name),CloudFormation 会基于新变更集自动增量更新堆栈中的资源。

在 SAM 中使用内在函数(Intrinsic Functions)

CloudFormation 提供了一系列可在运行时生成值的函数,称为内在函数(Intrinsic Functions)。由于 SAM 最终通过 CloudFormation 部署,因此这些内在函数同样可以在 SAM 模板中使用。HOWTO 给出了两个典型场景。

场景一:动态设置 Lambda 代码的 S3 位置

通过Parameters定义输入参数,再用!Ref在运行时取参数值:

Transform: 'AWS::Serverless-2016-10-31' # Parameters are CloudFormation features to pass input # to your template when you create a stack Parameters: BucketName: Type: String CodeKey: Type: String Resources: MyFunction: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: nodejs24.x CodeUri: # !Ref function allows you to fetch value # of parameters and other resources at runtime Bucket: !Ref BucketName Key: !Ref CodeKey

这里CodeUri使用了字典形式(Bucket+Key),两个字段均由!Ref动态填充——这正是上一节提到的"字典形式"的 S3 位置声明,且允许引用模板参数。Runtime: nodejs24.x仅为示例运行时,请以实际支持的运行时列表为准。

场景二:为每个堆栈生成不同的函数名

利用!Sub做字符串替换,把参数后缀拼进函数名:

Transform: 'AWS::Serverless-2016-10-31' # Parameters are CloudFormation features to pass input # to your template when you create a stack Parameters: FunctionNameSuffix: Type: String Resources: MyFunction: Type: AWS::Serverless::Function Properties: # !Sub performs string substitution FunctionName: !Sub "mylambda-${FunctionNameSuffix}" Handler: index.handler Runtime: nodejs24.x CodeUri: s3://bucket/key

当你在不同环境(如 dev/stage/prod)各建一个堆栈、传入不同的FunctionNameSuffix时,就能得到互不冲突的 Lambda 函数名。CodeUri这里直接使用了打包后的 S3 URI 字符串。

底层原理:内在函数是如何被求解的

SAM 转换器中的内在函数求解由 samtranslator/intrinsics/resolver.py 与 samtranslator/intrinsics/actions.py 共同实现:

  • resolver.py 默认注册三类 Action:RefAction、SubAction、GetAttAction(DEFAULT_SUPPORTED_INTRINSICS);
  • 求解过程采用**前序遍历(Pre-Order Traversal)**算法,见 _traverse():遇到形如{"Ref": "foo"}这种"单键字典"时尝试解析,解析不成功就原样保留交给 CloudFormation 处理;
  • RefAction.resolve_parameter_refs() 从参数表中查值并内联;SubAction则用正则\$\{([A-Za-z0-9\.]+|AWS::[A-Z][A-Za-z]*)\}匹配${...}引用并逐个替换(见 _sub_all_refs());
  • 值得注意的细节:actions.py 顶部_get_parameter_value()会跳过以{{IntrinsicFunction:开头的 CloudFormation 内部占位符(在嵌套堆栈变更集场景下出现),避免被 SAM 误解析;
  • 另外,!Ref/!GetAtt还支持引用"派生 SAM 资源"(如MyFunction.Alias、MyApi.Deployment),转换器通过 resource_refs.py 中的SupportedResourceReferences收集这些引用并解析为真实资源名(例如{"Ref": "MyFunction.Alias"}→{"Ref": "MyFunctionAliasLive"})。

注意事项:Fn::ImportValue仅部分支持

Fn::ImportValue允许一个堆栈引用另一个堆栈导出的属性值。由于 SAM 需要解析模板中的某些属性才能生成正确的下游资源,因此ImportValue在绝大多数属性上都可用,但以下三个属性不支持:

资源类型不支持的属性
AWS::Serverless::FunctionRestApiId
AWS::Serverless::FunctionPolicies
AWS::Serverless::ApiStageName

为什么这三个属性特殊

从源码看,这三处限制的根源是 SAM 转换器必须在转换期"读懂"这些属性的具体值:

  • RestApiId(AWS::Serverless::Function的 Api 事件源):samtranslator/model/eventsources/push.py 中的resources_to_link_for_rest_api()需要顺着RestApiId找到同一模板内的AWS::Serverless::Api资源,读取其StageName来构造 Lambda 的调用权限(stage 后缀)。源码注释明确指出:"customers could use !ImportValue, !Ref or other intrinsic functions which can be sometimes impossible to resolve (ie. when it has cross-stack references)"——跨堆栈引用无法在转换期解析,因此只能回退到通配AllStages权限。若RestApiId指向外部堆栈,SAM 无法在模板内找到对应的 API 资源做此类关联处理;
  • Policies(AWS::Serverless::Function):策略需要被解析为具体的 IAM 策略文档(含托管策略 ARN 展开、策略模板展开等),ImportValue的取值在转换期不可知,无法参与这些解析;
  • StageName(AWS::Serverless::Api):阶段名会直接影响生成资源的命名与权限拼接逻辑,同样需要转换期可解析的字面值。

其他内在函数不受影响

除上述三个属性外,!Ref、!Sub、!GetAtt、!If、!FindInMap等内在函数均可正常使用。samtranslator/validator/validator.py 中维护了一份INTRINSIC_ATTR集合,涵盖Fn::And、Fn::Base64、Fn::FindInMap、Fn::GetAtt、Fn::ImportValue、Fn::Join、Fn::Sub、Ref等全部标准内在函数,说明整个模板校验与转换链路对这些函数是一视同仁的,ImportValue的例外仅发生在上述三个需要转换期解析的属性上。

实战小结:一条完整的 SAM 应用生命周期

结合 HOWTO 与源码,一条典型的 SAM 应用从编写到上线的完整链路为:

  1. 编写模板:用sam init --runtime <runtime>初始化工程,或手动编写 SAM 模板(YAML/JSON),在Transform中声明AWS::Serverless-2016-10-31;可参考仓库内 examples/2016-10-31/api_endpointconfiguration/template.yaml 这类示例模板(该示例演示了AWS::Serverless::Api的EndpointConfiguration与!Ref组合用法),以及 docs/faq.rst、docs/globals.rst 等补充文档;
  2. 打包:执行sam package(或aws cloudformation package),把本地CodeUri/DefinitionUri产物上传到 S3,得到packaged-template.yaml;
  3. 部署:执行sam deploy --stack-name my-new-stack --capabilities CAPABILITY_IAM(或aws cloudformation deploy),CloudFormation 内部调用本仓库的transform()完成 SAM → CloudFormation 翻译,创建并执行变更集;
  4. 更新:修改模板后重新执行部署,CloudFormation 依据新变更集增量更新堆栈内资源;
  5. 跨栈复用:在绝大多数属性上可使用Fn::ImportValue引用其他堆栈的导出值,但避开RestApiId、Policies、StageName这三个不支持转换期解析的属性。

至此,你已经掌握了 SAM 模板编写、产物打包、变更集部署以及内在函数使用的完整方法论,并了解了本仓库转换器在处理这些环节时的真实实现逻辑——这正是 SAM "声明式定义 Serverless 应用"能力背后的技术底座。

  • 后端
  • 云原生
  • IaC

【免费下载链接】serverless-application-model

The AWS Serverless Application Model (AWS SAM) transform is a AWS CloudFormation macro that transforms SAM templates into CloudFormation templates.

项目地址:https://gitcode.com/gh_mirrors/se/serverless-application-model
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 4:03:49

嵌入式WASM开发:ESP32上硬件访问的边界与正确姿势

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 4:01:38

免费AI知识管理:从三千条收藏到个人知识库的搭建方法论

1. 为什么我花了一个月做这件事——从三千条收藏到一张知识地图1.1 一个普通人的信息困局先把话说在前面&#xff1a;这不是一篇凡尔赛式的"我教你知识管理"教程&#xff0c;而是一个被信息洪流冲昏头脑的普通人&#xff0c;花了一个月时间自救的真实记录。我的困境可…

作者头像 李华