首页
/ Serverless Framework 接入 AWS CloudFront Lambda@Edge:事件定义、缓存策略与边界约束实战指南

Serverless Framework 接入 AWS CloudFront Lambda@Edge:事件定义、缓存策略与边界约束实战指南

2026-09-08 15:14:42作者:魏侃纯Zoe

本文基于 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-requestviewer-response 上限为 128MB 内存、5 秒超时;origin-requestorigin-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_SIZELAMBDA_EDGE_UNSUPPORTED_TIMEOUT_VALUE 错误。
  • 运行时与函数体积限制:Lambda@Edge 仅支持 Python 3.9/3.8/3.7Node.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: 128timeout: 5;并在编译阶段移除 Lambda 资源上的 VpcConfigEnvironment(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 内含 clientIpmethoduriheaders 等字段;当函数作用于响应阶段时,则读取 cf.responseviewer-* 阶段触发的函数可以修改请求/响应对象但不能访问 VPC 内资源,origin-* 阶段则可代替源站处理并返回完整响应。

origin:字符串简写与 CloudFormation 对象两种写法

当需要更精细的源站配置时,origin 可以写成一个遵循 CloudFormation 语法的对象(不是简单的 key-value 映射)。其中 DomainName 必填,且 CustomOriginConfigS3OriginConfig 必须二选一:

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 会这样处理:

  1. 若你的 serverless.yml 中没有配置空 pathPattern 的行为,框架会自动额外创建一个 pathPattern 为空、指向已定义源站的行为作为默认行为;
  2. 当配置了多个不同源站时,必须通过 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'

对照源码中的 behaviorObjectSchemabehavior 对象还可配置更多字段:Compress(自动压缩)、SmoothStreamingTrustedSigners / TrustedKeyGroups(签名 URL/Cookie 可信账户或密钥组)、FieldLevelEncryptionIdOriginRequestPolicyIdResponseHeadersPolicyId,以及旧的 TTL 字段 MaxTTL / MinTTL / DefaultTTLForwardedValuesAllowedMethods 组合被限制为三种合法集合:GET+HEADGET+HEAD+OPTIONS,或完整的 GET/HEAD/OPTIONS/PUT/PATCH/POST/DELETECachedMethods 只允许前两种集合。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.jsgetCloudFrontCachePolicyName() 实现:${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.idbehavior.CachePolicyId 同时指定时,采用 cachePolicy.id;当 cachePolicy.namebehavior.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::PermissionAction: lambda:InvokeFunctionPrincipal: edgelambda.amazonaws.comSourceArn 指向本分发,并绑定到函数版本的 ARN(cloud-front.js);
  • 角色信任策略:向默认执行角色 IamRoleLambdaExecution 的 AssumeRolePolicyDocument 追加 edgelambda.amazonaws.com 服务主体,使边缘节点可以代入该角色(cloud-front.js);当服务未使用默认角色时,则会打印提示要求手动补充 Lambda@Edge 权限;
  • 日志权限:为角色追加 logs:CreateLogGrouplogs:CreateLogStreamlogs:PutLogEventslogs: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 事件(含 eventTypeorigin)→ 需要路由细分时追加 pathPattern 并正确标记 isDefaultOrigin → 按需用 behaviorcachePolicy 细化缓存策略 → 需要自定义域名 / 计费区域时通过 resources.CloudFrontDistribution 补充顶层配置。框架会自动完成版本绑定、DeletionPolicy、边缘区域日志权限与调用授权等繁杂环节,同时把「us-east-1 区域、各事件类型的内存/超时上限、pathPattern 全局唯一、每行为事件类型唯一」等 AWS 硬约束前移到打包前的 schema 与编译期校验中。

最后再次提醒两点部署注意事项:Lambda@Edge 涉及 CDN 全球传播,部署与移除通常需要等待约 30 分钟;由于函数被设置为 DeletionPolicy: Retain,执行 serverless remove 之后请务必到 AWS 控制台手动清理遗留的 Lambda@Edge 函数(框架会在 remove 时通过 logRemoveReminder() 打印该提醒)。若想进一步扩展,可继续阅读同一目录下的 event-bridge.mds3.md 等事件文档,或参考仓库中对 cloudFront 事件 schema 的完整定义 获取全部可配置项。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391