Serverless Framework 接入 AWS CloudFront Lambda@Edge:事件定义、缓存策略与边界约束实战指南
本文基于 Serverless Framework 官方文档 docs/sf/providers/aws/events/cloudfront.md 及仓库内的实现源码、单元测试撰写。AWS CloudFront 是亚马逊的 CDN(内容分发网络)服务,它允许 Lambda 函数运行在全球边缘节点上,这一能力即 Lambda@Edge。本文面向希望借助 Serverless Framework 以声明式
serverless.yml快速配置 Lambda@Edge 触发器的开发者:你将掌握 cloudFront 事件的完整配置语法(含 origin、behavior、cachePolicy、includeBody 等参数)、四种触发时机的区别与限制、以及从部署、移除到资源清理的完整边界约束。
CloudFront 的分发配置(Distribution Configuration)由 Origins(源站)与 Behaviors(行为)两部分构成,两者共同定义了内容如何被缓存和分发:Origin 是被分发服务的端点定义(如 S3 存储桶或某个网站域名);Behavior 决定 CloudFront 在请求命中该服务时的行为,Lambda@Edge 函数正是挂载在 Behavior 上执行的。Serverless Framework 会将你在 events.cloudFront 中声明的配置自动编译成 AWS::CloudFront::Distribution 等 CloudFormation 资源(核心实现在 cloud-front.js),并自动补齐 IAM 角色、调用权限与 Lambda 版本绑定等底层细节。
Lambda@Edge 的四个触发时机与硬性限制
Lambda@Edge 中的 Lambda 函数共有四种被触发的情形,Serverless Framework 通过 eventType 字段指定:
| eventType | 触发时机 |
|---|---|
viewer-request |
CloudFront 首次从客户端收到请求时 |
origin-request |
向源站发起请求之前 |
origin-response |
CloudFront 从源站收到响应之后 |
viewer-response |
响应返回给客户端之前 |
实际使用中,最常见的组合是 viewer-request + origin-request(修改发往源站的请求)以及 origin-response + viewer-response(改写返回给客户端的响应)。
在编写配置前必须牢记以下四类边界(原文档「NOTE / IMPORTANT」区块的原文约束):
- 部署耗时:由于 CloudFront CDN 需要全球边缘节点传播配置,部署与移除操作最长可能耗时约 30 分钟。
- 资源删除策略:由于 Lambda@Edge 的限制,使用
cloudFront事件的 AWS Lambda 函数必须设置DeletionPolicy: Retain。Serverless Framework 会自动为这些函数加上该策略,因此当你通过serverless remove移除服务后,必须手动删除那些被保留的 Lambda@Edge 函数,否则会产生残留资源。 - 内存与超时上限:
viewer-request与viewer-response上限为 128MB 内存、5 秒超时;origin-request与origin-response上限更高。值得留意的是,原文档记录 AWS 官方早期的限制为 3008MB 内存与 30 秒超时,而仓库源码 cloud-front.js 中框架内部维护的校验上限已更新为:origin 类事件{ maxTimeout: 30, maxMemorySize: 10240 }、viewer 类事件{ maxTimeout: 5, maxMemorySize: 128 }——即框架按当前 AWS 上限(10240MB / 30s)对 origin 类事件做打包前的本地校验。校验逻辑在 validate() 中,超限会抛出LAMBDA_EDGE_UNSUPPORTED_MEMORY_SIZE或LAMBDA_EDGE_UNSUPPORTED_TIMEOUT_VALUE错误。 - 运行时与函数体积限制:Lambda@Edge 仅支持
Python 3.9/3.8/3.7、Node.js 16.x/14.x/12.x/10.x等运行时;单个函数压缩包不得超过 10KB。若你的 Node.js 函数体积超限,可通过打包插件(如serverless-esbuild)进行 bundle 与 minify 来缩减体积——这也是 Serverless Framework 官方文档推荐的做法。
另外,框架在 prepareFunctions() 中会对所有挂了 cloudFront 事件的函数自动完成几件事:强制开启函数版本化(versionFunction: true,因为 Lambda@Edge 只能引用已发布的版本);未显式配置时默认补 memorySize: 128 与 timeout: 5;并在编译阶段移除 Lambda 资源上的 VpcConfig 与 Environment(Lambda@Edge 不支持 VPC 与环境变量,见 cloud-front.js)。同时,所有带 cloudFront 事件的函数必须部署在 us-east-1 区域,否则框架会直接报 CLOUDFRONT_INVALID_REGION 错误(cloud-front.js)。
最简单的 cloudFront 事件定义
下面是最小可用的配置:把名为 myLambdaAtEdge 的函数的 handler 挂到 CloudFront 的 viewer-response 上,并让函数通过 S3 源站路径访问 s3://bucketname.s3.amazonaws.com/files:
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
origin: s3://bucketname.s3.amazonaws.com/files
对应的 handler 实现如下,它在响应头中写入当前时间戳:
// index.handler
'use strict'
module.exports.handler = (event, context, callback) => {
const response = event.Records[0].cf.response
const headers = response.headers
headers['x-serverless-time'] = [
{ key: 'x-serverless-time', value: Date.now().toString() },
]
return callback(null, response)
}
Lambda@Edge 的事件结构与普通 API Gateway 事件不同:它统一包在 event.Records[0].cf 之下。仓库中为 Java 运行时准备的测试样例 cloud-front-event.json 展示了典型结构——cf.config.distributionId 是分发 ID,cf.request 内含 clientIp、method、uri、headers 等字段;当函数作用于响应阶段时,则读取 cf.response。viewer-* 阶段触发的函数可以修改请求/响应对象但不能访问 VPC 内资源,origin-* 阶段则可代替源站处理并返回完整响应。
origin:字符串简写与 CloudFormation 对象两种写法
当需要更精细的源站配置时,origin 可以写成一个遵循 CloudFormation 语法的对象(不是简单的 key-value 映射)。其中 DomainName 必填,且 CustomOriginConfig 与 S3OriginConfig 必须二选一:
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
pathPattern: /docs*
origin:
DomainName: serverless.com
OriginPath: /framework
CustomOriginConfig:
OriginProtocolPolicy: match-viewer
框架源码 cloud-front.js 中的 createOrigin() 说明了字符串简写是如何被解析成对象语法的:
- 字符串形式的
origin会被URL解析:hostname映射为DomainName; - 若 URL 带有路径(pathname 长度大于 1),该路径会被抽取为
OriginPath; - 协议为
s3:时补上S3OriginConfig: {},其余协议(如https:)自动补CustomOriginConfig: { OriginProtocolPolicy: 'match-viewer' }; - 每个 origin 会被分配一个由 naming.js 生成的唯一
Id(S3 源站以s3开头、自定义源站以custom开头)。
源码里 originObjectSchema(config-schema)对该对象支持的字段做了完整约束,常用的有:
| 字段 | 说明与取值范围 |
|---|---|
DomainName |
源站域名,必填 |
CustomOriginConfig.OriginProtocolPolicy |
连接协议,枚举 http-only / match-viewer / https-only,必填 |
CustomOriginConfig.OriginReadTimeout / OriginKeepaliveTimeout |
读超时 / keepalive 超时(秒),范围 1–60 |
CustomOriginConfig.HTTPPort / HTTPSPort |
自定义源站端口(0–65535) |
CustomOriginConfig.OriginSSLProtocols |
允许的 TLS/SSL 版本,如 TLSv1.2 |
S3OriginConfig.OriginAccessIdentity |
旧版 OAI 标识,格式形如 origin-access-identity/cloudfront/<id>,支持 CloudFormation 函数引用 |
OriginAccessControlId |
新版 OAC(Origin Access Control)ID |
OriginPath |
附加到源站请求上的路径前缀 |
OriginCustomHeaders |
转发给源站的自定义 Header 数组,每项含 HeaderName / HeaderValue |
pathPattern、空路径默认行为与 isDefaultOrigin
CloudFront 的一项硬性要求是:分发配置中必须存在一条 pathPattern 为空的默认行为(Default Cache Behavior)。Serverless Framework 会这样处理:
- 若你的
serverless.yml中没有配置空pathPattern的行为,框架会自动额外创建一个 pathPattern 为空、指向已定义源站的行为作为默认行为; - 当配置了多个不同源站时,必须通过
isDefaultOrigin: true显式指明哪个源站是默认源站,框架无法自动推断。若出现多个isDefaultOrigin: true或多个源站但无默认源站的情况,会分别抛出CLOUDFRONT_MULTIPLE_DEFAULT_ORIGIN_EVENTS错误(见 cloud-front.js 与 单元测试)。
两个函数的配置示例如下——/files* 行为指向 S3 源站并声明为默认源站,/docs* 行为指向自定义源站:
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
pathPattern: /files*
isDefaultOrigin: true
origin: s3://bucketname.s3.amazonaws.com/files
mySecondLambdaAtEdge:
handler: mySecondLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
pathPattern: /docs*
origin:
DomainName: serverless.com
OriginPath: /framework
CustomOriginConfig:
OriginProtocolPolicy: match-viewer
从编译实现看,框架会先把带 PathPattern 的行为排在后面、把空 pathPattern 的行为放到 behaviors[0] 作为 DefaultCacheBehavior;当显式声明了带 PathPattern 的行为时,则取出第一个行为克隆为去掉 PathPattern 的默认行为(cloud-front.js)。因此一条经验是:把真正的「兜底」源站(默认路径)用 isDefaultOrigin: true 标记,其余按 pathPattern 走 Cache Behaviors。
同一行为绑定多函数,同一函数挂多行为
不同函数若指向同一 origin,框架会把它们合并进同一条 behavior(TargetOriginId 相同即合并),因此你可以按事件类型拆成多个 handler,让同一个源站在不同阶段由不同函数处理:
functions:
myLambdaAtEdgeViewerRequest:
handler: myLambdaAtEdgeViewerRequest.handler
events:
- cloudFront:
eventType: viewer-request
origin: ${self:custom.origins.myWebsiteOrigin}
myLambdaAtEdgeViewerResponse:
handler: myLambdaAtEdgeViewerResponse.handler
events:
- cloudFront:
eventType: viewer-response
origin: ${self:custom.origins.myWebsiteOrigin}
custom:
origins:
myWebsiteOrigin:
DomainName: serverless.com
OriginPath: /framework
CustomOriginConfig:
OriginProtocolPolicy: match-viewer
注意这里 origin 对象可以抽到 custom 变量中,通过 ${self:custom.origins.myWebsiteOrigin} 引用,避免重复粘贴长配置。
反过来,若希望同一个函数被多个 pathPattern 行为复用,就在同一函数下声明多个 cloudFront 事件;每个事件里还可以通过 includeBody: true 让 CloudFront 把请求体一并传给函数(默认不包含):
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
includeBody: true
origin: s3://bucketname.s3.amazonaws.com/files
- cloudFront:
eventType: viewer-response
pathPattern: /docs*
origin:
DomainName: serverless.com
OriginPath: /framework
CustomOriginConfig:
OriginProtocolPolicy: match-viewer
需要强调:同一条 behavior 内每种 eventType 只能出现一次。框架会在编译时校验 LambdaFunctionAssociations 中事件类型不重复(错误码 CLOUDFRONT_EVENT_TYPE_NON_UNIQUE_CACHE_BEHAVIOR,见 cloud-front.js),这正是上例中两个事件必须通过不同 pathPattern 区分成两条行为的原因。
Cache Behavior 配置
未显式配置时,框架生成的默认行为只带一个默认值:
ViewerProtocolPolicy: allow-all
若需要完整控制行为的缓存语义,可在事件中叠加 behavior 对象(遵循 CloudFormation CacheBehavior 语法)。例如下面的配置把协议策略收紧为 https-only,并显式声明允许与缓存的 HTTP 方法:
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
origin: s3://bucketname.s3.amazonaws.com/files
behavior:
ViewerProtocolPolicy: https-only
AllowedMethods:
- 'GET'
- 'HEAD'
- 'OPTIONS'
- 'PUT'
- 'PATCH'
- 'POST'
- 'DELETE'
CachedMethods:
- 'GET'
- 'HEAD'
- 'OPTIONS'
对照源码中的 behaviorObjectSchema,behavior 对象还可配置更多字段:Compress(自动压缩)、SmoothStreaming、TrustedSigners / TrustedKeyGroups(签名 URL/Cookie 可信账户或密钥组)、FieldLevelEncryptionId、OriginRequestPolicyId、ResponseHeadersPolicyId,以及旧的 TTL 字段 MaxTTL / MinTTL / DefaultTTL 与 ForwardedValues。AllowedMethods 组合被限制为三种合法集合:GET+HEAD、GET+HEAD+OPTIONS,或完整的 GET/HEAD/OPTIONS/PUT/PATCH/POST/DELETE;CachedMethods 只允许前两种集合。schema 层会拒绝非法组合,保证生成的模板必然能被 CloudFormation 接受。
缓存策略:自定义 Cache Policy 与 AWS 托管策略
较新的配置方式是使用 CloudFront 的 Cache Policy 来统一定义缓存键与 TTL。框架支持两级用法:
第一级:在 provider.cloudFront.cachePolicies 中声明自定义策略(遵循 CloudFormation AWS::CloudFront::CachePolicy 语法),然后在事件里用 cachePolicy.name 引用:
provider:
cloudFront:
cachePolicies:
myCachePolicy:
MinTTL: 0
MaxTTL: 86000
DefaultTTL: 3600
...
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
origin: s3://bucketname.s3.amazonaws.com/files
cachePolicy:
name: myCachePolicy
这条配置会创建一个命名规则为 servicename-stage-myCachePolicy 的 Cache Policy(该命名规则由 naming.js 的 getCloudFrontCachePolicyName() 实现:${serviceName}-${stage}-${cachePolicyName},逻辑 ID 则为 CloudFrontCachePolicy<名称>)。若声明了 cachePolicies 却没有任何事件引用,框架会在打包时输出警告提示存在未使用的策略。
第二级:直接引用 AWS 托管策略 ID,使用 cachePolicy.id 而不再需要自定义 name。AWS 托管策略具有全局唯一 ID,例如官方「Managed-CachingOptimized」的 ID 为 658327ea-f89d-4fab-a63d-7e88639e58f6:
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
origin: s3://bucketname.s3.amazonaws.com/files
cachePolicy:
id: 658327ea-f89d-4fab-a63d-7e88639e58f6 # references AWS Managed Policy named Managed-CachingOptimized
此外,也可以把 CachePolicyId 写在 behavior 对象里:
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-response
origin: s3://bucketname.s3.amazonaws.com/files
behavior:
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # references AWS Managed Policy named Managed-CachingOptimized
优先级规则:当 cachePolicy.id 与 behavior.CachePolicyId 同时指定时,采用 cachePolicy.id;当 cachePolicy.name 与 behavior.CachePolicyId 同时指定时,采用 cachePolicy.name(源码逻辑见 cloud-front.js)。同时注意:一旦你在 behavior 里显式指定了 ForwardedValues / MaxTTL / MinTTL / DefaultTTL 等旧式字段,框架就不会再自动附加默认 Cache Policy(避免新旧两套缓存配置冲突)。当事件完全没有指定任何缓存策略时,框架会默认附加 Managed-CachingOptimized(ID 正是 658327ea-f89d-4fab-a63d-7e88639e58f6,见 cloud-front.js)。引用了一个未在 provider.cloudFront.cachePolicies 中声明的 name 会直接报 UNRECOGNIZED_CLOUDFRONT_CACHE_POLICY。
在 resources 中自定义 Distribution 顶层配置
如果需要对分发本身做全局设置——例如限定边缘站点计费区域、绑定自定义域名与 ACM 证书——可以在 resources 中直接声明名为 CloudFrontDistribution 的资源,框架会将其与函数事件编译出的行为合并:
resources:
Resources:
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
PriceClass: PriceClass_100
Aliases:
- mysite.example.com
ViewerCertificate:
AcmCertificateArn: arn:aws:acm:us-east-1:000000000000:certificate/eb96757c-c78e-4843-bb17-2f09747b6f0d
SslSupportMethod: sni-only
这里的 PriceClass_100 表示只使用北美与欧洲的边缘节点以降低成本;Aliases + ViewerCertificate 用于 HTTPS 自定义域名(ACM 证书需创建在 us-east-1)。框架内部生成分发的逻辑 ID 固定为 CloudFrontDistribution(见 naming.js),并在模板 Outputs 中输出 CloudFrontDistribution(分发 ID)与 CloudFrontDistributionDomainName(形如 dxxxxx.cloudfront.net 的域名,见 cloud-front.js),serverless info 可直接展示这两个输出。
框架自动补齐的底层设施
如果你好奇「除了行为列表,还需要什么才能让 Lambda@Edge 真正跑起来」,compileCloudFrontEvents() 给出了完整答案,框架会一次性自动生成:
DeletionPolicy: Retain:对每个 Lambda@Edge 函数资源强制 Retain,规避 CloudFormation 删除时副本尚未清空导致的失败(cloud-front.js);- 调用权限
AWS::Lambda::Permission:Action: lambda:InvokeFunction、Principal: edgelambda.amazonaws.com、SourceArn指向本分发,并绑定到函数版本的 ARN(cloud-front.js); - 角色信任策略:向默认执行角色
IamRoleLambdaExecution的 AssumeRolePolicyDocument 追加edgelambda.amazonaws.com服务主体,使边缘节点可以代入该角色(cloud-front.js);当服务未使用默认角色时,则会打印提示要求手动补充 Lambda@Edge 权限; - 日志权限:为角色追加
logs:CreateLogGroup、logs:CreateLogStream、logs:PutLogEvents、logs:TagResource(资源为arn:...:logs:*:*:*),因为 Lambda@Edge 会在离执行点最近的区域写 CloudWatch Logs(日志组形如/aws/lambda/us-east-1.<function-name>),跨区域日志必须授权到任意区域(cloud-front.js); AWS::Lambda::Version引用:每个 LambdaFunctionAssociations 的LambdaFunctionARN引用的是函数版本而非函数本体。
上述行为均有对应单元测试覆盖,例如 cloud-front.test.js(S3 origin 生成 Distribution)、L235-L271(向 IAM Assume 策略追加 edgelambda.amazonaws.com)、以及 memorySize/timeout/region/pathPattern 冲突等一系列错误分支测试,可作为理解框架行为的参考。
常见陷阱:pathPattern 必须全局唯一
CloudFront 要求每条 behavior 的 pathPattern 能无歧义地匹配请求。因此下面这种配置是非法的:两个事件都未声明 pathPattern,框架会为两者都生成空 pathPattern,从而产生两条互相冲突的默认行为(重复的 PathPattern 会触发 CLOUDFRONT_MULTIPLE_BEHAVIORS_FOR_SINGLE_PATH_PATTERN,见 cloud-front.js):
# 错误示例:两条事件都将生成空 pathPattern 的默认行为
functions:
myLambdaAtEdge:
handler: myLambdaAtEdge.handler
events:
- cloudFront:
eventType: viewer-request
origin: s3://bucketname.s3.amazonaws.com/files
- cloudFront:
eventType: viewer-request
origin: s3://bucketname.s3.amazonaws.com/other
同一事件类型且都不带 pathPattern 时,应合并到同一条 behavior 的不同 origin 处理,或至少为其中一条指定不同的 pathPattern,让每条 behavior 都能唯一捕获请求。此外,若声明了多个源站而未指定 isDefaultOrigin,或同一函数在不同行为中重复使用相同事件类型,同样会被编译期校验拒绝——配置阶段尽早暴露这些错误,正是框架把这些校验放进 package:compileEvents 钩子(hooks 定义见 cloud-front.js)的目的。
小结与部署提醒
归纳起来,用 Serverless Framework 配置 CloudFront Lambda@Edge 的关键路径是:在函数 events 下声明 cloudFront 事件(含 eventType 与 origin)→ 需要路由细分时追加 pathPattern 并正确标记 isDefaultOrigin → 按需用 behavior、cachePolicy 细化缓存策略 → 需要自定义域名 / 计费区域时通过 resources.CloudFrontDistribution 补充顶层配置。框架会自动完成版本绑定、DeletionPolicy、边缘区域日志权限与调用授权等繁杂环节,同时把「us-east-1 区域、各事件类型的内存/超时上限、pathPattern 全局唯一、每行为事件类型唯一」等 AWS 硬约束前移到打包前的 schema 与编译期校验中。
最后再次提醒两点部署注意事项:Lambda@Edge 涉及 CDN 全球传播,部署与移除通常需要等待约 30 分钟;由于函数被设置为 DeletionPolicy: Retain,执行 serverless remove 之后请务必到 AWS 控制台手动清理遗留的 Lambda@Edge 函数(框架会在 remove 时通过 logRemoveReminder() 打印该提醒)。若想进一步扩展,可继续阅读同一目录下的 event-bridge.md、s3.md 等事件文档,或参考仓库中对 cloudFront 事件 schema 的完整定义 获取全部可配置项。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00