AWS API Gateway:HTTP、REST、WebSocket、Stage 与私网治理
路由改了,为什么线上还在访问旧后端
一次接口切换中,团队已经把 /orders 的 integration 改到新 Lambda,控制台也显示保存成功,但调用 prod Stage 时仍返回旧版本。排查半天后才发现:他们改的是 REST API 配置,却没有创建新的 Deployment;prod 仍指向旧快照。另一个环境使用 HTTP API 自动发布,同样的保存动作却会直接影响流量。两个看似相同的按钮,背后是两种完全不同的发布纪律。
更危险的误判发生在网络和身份上:HTTP API 通过 VPC Link 访问了私有 ALB,不代表客户端入口已经私有;API Key 能参与计量和目标限流,也不代表调用者已经完成认证。要把 API Gateway 用成长期入口,必须同时看清 API 类型、发布快照、Stage 引用、调用身份、前后端网络方向和请求证据。
先按交互模型选类型,再比较功能
API Gateway 不是一个可随意切换协议的单一网关。三类 API 有不同资源模型、授权入口、日志能力、配额维度和计费单位,选错后再迁移往往比最初多花一次契约和入口改造。
| 类型 | 首要信号 | 常见能力取舍 | 不成立的判断 |
|---|---|---|---|
| HTTP API | 无状态 HTTP 请求,追求较小能力面与直接 JWT/IAM/Lambda 授权 | 支持自动发布和 Stage access log;高级 REST 能力需逐项复核 | “能连 VPC 后端,所以入口是私有的” |
| REST API | 需要 API Key/Usage Plan、请求校验、缓存、canary、WAF、Private API 或 execution log 等能力 | Deployment 快照与 Stage 切换明确,策略面更丰富 | “原生 JWT authorizer 与 HTTP API 完全相同” |
| WebSocket API | 双向长连接,服务端需要按 connection ID 主动回推 | 以 route key 分发消息,$connect、$disconnect 和连接回调是核心对象 | “每条消息都像一次新的 HTTP 鉴权” |
能力会持续演进,选择时应打开 AWS 的 REST API 与 HTTP API 当前比较页,把必需项逐一映射到目标 Region 和账户,而不是复制一张静态功能表。WebSocket 应先按持久连接、断线恢复和服务端回推需求独立决策,再进入 WebSocket API 的路由与连接模型。
API 类型之外还要单独选择客户端入口。REST API 可以使用 edge-optimized、Regional 或 private endpoint;HTTP API 与 WebSocket API 的公开入口以 Regional 模型为主,不能因为 integration 进入 VPC 就声称 API 本身是 private。自定义域名、Route 53、CloudFront、WAF、mTLS 与 interface VPC endpoint 分别改变 TLS、边缘分发、防护或网络可达性,它们不是同一个“部署模式”字段。选型记录应画出 client -> endpoint -> Stage -> integration -> backend,逐跳标注 DNS、TLS、身份和费用归属。
请求运行时与发布状态是两条链
客户端请求只经过数据面:域名解析和 TLS 结束后,API Gateway 匹配 API、Stage 与 route,执行 authorizer 和流量规则,再调用 Lambda、HTTP endpoint 或 AWS 服务 integration。创建 API、修改 route、关联 authorizer 和切换 Stage 则属于管理动作;CloudTrail 可以留下这些动作的审计记录,但不会替代每次请求的 access log。
REST API 的 Deployment 是配置的不可变快照,只有被 Stage 引用后才可调用。修改 resource、method、integration、authorizer 或 resource policy 后,需要重新创建 Deployment;回滚是让 Stage 重新指向已知旧 Deployment。HTTP API 也有 Deployment 和 Stage,但 Stage 可以开启 automatic deployments,配置变更随即发布。WebSocket API 同样需要 Deployment 与 Stage;修改 $connect authorizer 后,已有连接不会重新握手,验证必须同时观察旧连接和新连接。
Stage 不只是环境名。它还承载日志、限流、变量以及部分 API 类型的缓存或 canary 配置。HTTP API 的 $default Stage 可以让调用 URL 不带 Stage 路径,因此不能把 /{stageName} 当成所有入口的固定格式。对象关系与操作差异可分别对照 REST API 发布说明 和 HTTP API Stage 说明。
REST API canary 在同一个 Stage 内按权重把请求随机分到基础 Deployment 与候选 Deployment,并可使用独立 Stage variables;它不是按租户、用户或请求内容稳定分流。带状态会话、写请求和不可幂等操作不能仅凭百分比就安全灰度,必须让后端能够识别候选流量、控制副作用并准备补偿。发布前记录基础与候选 Deployment ID、权重、变量覆盖和是否使用 Stage cache;回滚时先把 canary 权重归零并验证候选日志停止增长,再解除候选 Deployment。仅看到总体错误率下降,不能证明所有候选请求已经退出。
用隔离栈跑通两个上游与 JWT 拒绝路径
这套实验必须放在专用测试账户执行。执行者需要 AWS CLI、AWS SAM CLI、可用的 OIDC issuer/audience、一个允许创建 CloudFormation、Lambda、API Gateway、IAM role 与 CloudWatch Logs 的最小化部署角色,以及明确的测试 Region。组织 SCP、permission boundary 和服务配额仍可能让模板在目标账户失败,因此先记录调用主体、Region 和失败事件,再判断是模板错误还是组织边界拒绝。
模板中的 AWS SAM Transform 值是正式版本标识,其中的日期形态不是内容发布日期。Lambda runtime 只是实验基线;部署前必须在目标 Region 查询仍受支持的 runtime,并在变更记录中固定实际值。
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31' # 版本标识
Parameters:
StageName:
Type: String
Default: lab
JwtIssuer:
Type: String
Description: OIDC issuer URL, without a trailing slash unless the issuer requires it
JwtAudience:
Type: String
Description: Audience accepted by this API
Resources:
AccessLogs:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: 7
Tags:
- Key: purpose
Value: api-gateway-lab
Gateway:
Type: AWS::Serverless::HttpApi
Properties:
StageName: !Ref StageName
FailOnWarnings: true
AccessLogSettings:
DestinationArn: !GetAtt AccessLogs.Arn
Format: >-
{"requestId":"$context.requestId","routeKey":"$context.routeKey","status":"$context.status","integrationStatus":"$context.integrationStatus","integrationError":"$context.integrationErrorMessage","ip":"$context.identity.sourceIp"}
DefaultRouteSettings:
ThrottlingBurstLimit: 10
ThrottlingRateLimit: 5
Auth:
DefaultAuthorizer: ProjectJwt
Authorizers:
ProjectJwt:
IdentitySource: $request.header.Authorization
AuthorizationScopes:
- orders.read
JwtConfiguration:
issuer: !Ref JwtIssuer
audience:
- !Ref JwtAudience
Tags:
purpose: api-gateway-lab
BlueFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.13
Handler: index.handler
InlineCode: |
import json
def handler(event, context):
return {
"statusCode": 200,
"headers": {"content-type": "application/json"},
"body": json.dumps({"upstream": "blue", "requestId": context.aws_request_id})
}
Events:
BlueRoute:
Type: HttpApi
Properties:
ApiId: !Ref Gateway
Path: /blue
Method: GET
PayloadFormatVersion: '2.0'
Tags:
purpose: api-gateway-lab
GreenFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.13
Handler: index.handler
InlineCode: |
import json
def handler(event, context):
return {
"statusCode": 200,
"headers": {"content-type": "application/json"},
"body": json.dumps({"upstream": "green", "requestId": context.aws_request_id})
}
Events:
GreenRoute:
Type: HttpApi
Properties:
ApiId: !Ref Gateway
Path: /green
Method: GET
PayloadFormatVersion: '2.0'
Tags:
purpose: api-gateway-lab
Outputs:
ApiId:
Value: !Ref Gateway
BaseUrl:
Value: !Sub https://${Gateway}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${StageName}
AccessLogGroup:
Value: !Ref AccessLogs这些字段会直接改变运行结果。StageName 同时进入 Stage 名与调用 URL,但不会自动提供账户级或网络级隔离;FailOnWarnings: true 会让导入警告阻断部署,适合把不完整定义挡在流水线外;DefaultAuthorizer 和 AuthorizationScopes 让两个 route 都要求同一个 JWT authorizer 与 orders.read scope。ThrottlingBurstLimit 是短时突发桶容量,ThrottlingRateLimit 是目标补充速率,它们都不是严格请求或费用上限。PayloadFormatVersion: '2.0' 决定 Lambda 收到的事件结构,现有处理器若按 REST API 或 payload 1.0 读取字段,切换后会在业务代码中失败。RetentionInDays: 7 只是实验日志保留期;生产值要由排障窗口、合规要求、日志量和费用共同决定。
先验证身份与 Region,再构建变更集。不要把长期 access key 放进参数、shell history 或 CI 日志;本地交互优先使用短期 SSO/STS 会话,CI 使用带条件约束的角色。
set -euo pipefail
export AWS_REGION='<lab-region>'
export STACK_NAME='api-gateway-lab'
export JWT_ISSUER='https://<tenant-idp-host>/<issuer-path>'
export JWT_AUDIENCE='api-gateway-lab'
aws --version
aws sts get-caller-identity --query '{Account:Account,Arn:Arn}'
aws configure get region
sam validate --lint --template-file template.yaml
sam build --template-file template.yaml
sam deploy \
--stack-name "$STACK_NAME" \
--region "$AWS_REGION" \
--resolve-s3 \
--capabilities CAPABILITY_IAM \
--parameter-overrides \
StageName=lab \
JwtIssuer="$JWT_ISSUER" \
JwtAudience="$JWT_AUDIENCE" \
--tags purpose=api-gateway-lab owner='<team>' expires='<relative-window>'预期证据不是一句“栈创建成功”,而是四组可关联结果:CloudFormation 栈与变更事件;API、Stage、route 和 authorizer 的脱敏配置;无 token、错误 audience、缺少 orders.read scope、过期 token 与合法 token 的响应差异;access log 中的 request ID、route、gateway status、integration status 与 Lambda request ID。状态码只能作为分类入口,验收时必须保存目标环境的实际响应头、响应体和对应日志,不能拿示例值代替运行证据。
API_ID="$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" --region "$AWS_REGION" \
--query "Stacks[0].Outputs[?OutputKey=='ApiId'].OutputValue" --output text)"
BASE_URL="$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" --region "$AWS_REGION" \
--query "Stacks[0].Outputs[?OutputKey=='BaseUrl'].OutputValue" --output text)"
LOG_GROUP="$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" --region "$AWS_REGION" \
--query "Stacks[0].Outputs[?OutputKey=='AccessLogGroup'].OutputValue" --output text)"
aws apigatewayv2 get-api --api-id "$API_ID"
aws apigatewayv2 get-stage --api-id "$API_ID" --stage-name lab
aws apigatewayv2 get-routes --api-id "$API_ID"
aws apigatewayv2 get-authorizers --api-id "$API_ID"
curl -i "$BASE_URL/blue"
curl -i -H 'Authorization: Bearer <wrong-audience-token>' "$BASE_URL/blue"
curl -i -H 'Authorization: Bearer <valid-token-without-orders.read>' "$BASE_URL/blue"
curl -i -H 'Authorization: Bearer <short-lived-valid-token>' "$BASE_URL/blue"
curl -i -H 'Authorization: Bearer <short-lived-valid-token>' "$BASE_URL/green"
aws logs tail "$LOG_GROUP" --since 10m --region "$AWS_REGION"正向结果应能区分 {"upstream":"blue"} 与 {"upstream":"green"},合法 token 的 claim 满足 issuer、audience 和 orders.read scope。无 token、过期 token 或错误 audience 应在进入 Lambda 前被拒绝;issuer/audience 合法但缺少 scope 的 token 也应被拒绝。反向结果若两个 route 都返回同一上游,先查 route integration;若 Lambda 有日志而 API access log 缺失,查 Stage 日志目标以及部署角色是否具有创建 log delivery 和 resource policy 的权限;若 authorizer 全部放行,查 route 是否真的继承默认 authorizer,而不是只看到 authorizer 资源存在。
Authorizer、API Key 与上游身份不能互相代替
管理 API Gateway 的 IAM action 与调用 API 的 execute-api:Invoke 是两套权限。部署角色可以创建 route,不应因此获得业务调用权;业务调用者可以被允许 invoke,也不应因此能修改 Stage。两类 ARN、主体、session tag 和审计事件应分别建模。
HTTP API 支持 IAM、JWT authorizer 和 Lambda authorizer。JWT authorizer 适合由 OIDC/OAuth 发行方签发并可在网关校验 issuer、audience、scope 的 token;Lambda authorizer 适合需要自定义身份源和策略计算的场景,但要设计超时、错误、缓存键与 TTL。REST API 可组合 IAM、resource policy、Lambda authorizer 和 Cognito user pool authorizer,但它没有与 HTTP API 同构的原生 JWT authorizer。具体组合应从 REST API 访问控制 与 HTTP API 访问控制 分别确认。
WebSocket 的 Lambda authorizer 只能挂在 $connect route,授权发生在握手时。用户被撤权、租户关系改变或权限收窄后,既有连接不会自动重新鉴权;应用必须决定关闭连接、缩短会话、逐消息校验还是接受撤权延迟。测试新规则时,旧连接继续收发而新连接被拒绝,才真正暴露这个差异。
REST API 的 API Key 和 Usage Plan 用于客户端识别、计量、配额与目标限流,不能单独承担用户认证或授权,也不应把机密放进 Key。Authorizer 缓存同样可能扩大越权半径:cache key 若遗漏租户、scope 或资源维度,不同权限请求可能复用同一结果;TTL 越长,撤权生效越慢。正反实验必须改变一个 claim 或租户维度,并观察缓存命中与撤销时间。
Resource policy、VPC endpoint policy、IAM identity policy 与 authorizer 处在不同决策点。Private REST API 至少要同时验证允许的 principal + 允许的 aws:SourceVpce 成功、同 principal 从错误 endpoint 失败、错误 principal 从正确 endpoint 失败;少一组就无法判断究竟是网络边界还是身份边界在放行。策略变更后要重新部署 REST API 才能影响 Stage,请同时保存 Deployment ID、CloudTrail 管理事件和拒绝请求的 access log,避免把“策略 JSON 已保存”误当成运行时授权已生效。
Private REST API 与 VPC Link 解决的是相反方向
Private REST API 解决 client -> API Gateway 的私有入口。客户端经 API Gateway 的 interface VPC endpoint 访问,resource policy 可用 aws:SourceVpc 或 aws:SourceVpce 收窄来源,VPC endpoint policy 还可限制 principal 和 resource。它只能从相关 VPC 或接入该 VPC 的受控网络调用,细节见 Private REST API。
VPC Link 解决 API Gateway -> private backend 的出站路径。HTTP API 可以借此访问 VPC 内的 ALB、NLB 或 Cloud Map 服务,但前端 execute-api endpoint 仍可能是公有 Regional endpoint。REST API 新建私有集成应优先评估 VPC Link V2:它可以连接 ALB,旧 V1 仍受支持但属于 legacy 路径,不应成为新架构的默认选择。REST API、VPC Link 与负载均衡资源还存在同账户等所有权约束,跨账户接入不能先画成一条逻辑线再期待控制面自动打通。它不替代后端安全组、listener、TLS、健康检查和应用授权。把两种路径画成一个“私网开关”,会留下公开入口或无法回程的后端。
Private DNS 是高风险变量。为 execute-api interface endpoint 开启 private DNS 后,VPC 内默认 API Gateway 域名的解析行为会改变,可能让同一 VPC 无法通过默认 endpoint 访问 public API。变更前要保存 resolver、private hosted zone、Host 或 x-apigw-api-id 用法与自定义域名配置;验证必须从 VPC 内、VPC 外和后端子网分别执行 DNS、TLS 与真实请求。
私有 integration 还会改变后端看到的路径。REST API 经 VPC Link 调用时,Stage 名可能被带入后端路径,例如客户端请求 /test/orders,后端收到的路径仍含 test;若应用只注册 /orders,网关连通却会稳定返回 404。应在隔离 Stage 保存后端 access log,先证明实际 path,再用 parameter mapping 明确移除 Stage 前缀,并保留“未映射时 404、映射后 200”的对照。HTTP API private integration 同样要核对 integration URI、payload format、TLS server name、subnet 与 security group;VPC Link 状态变为 AVAILABLE 只证明链路资源就绪,不证明 listener、证书和业务路由正确。
限流是弹性保护,不是成本保险箱
API Gateway 使用 token bucket 限流。account/Region、Stage、route 或 method、Usage Plan/API Key 等层级共同生效,更窄层设置无法突破更宽层上限;账户级额度还会被同 Region 的 HTTP、REST、WebSocket 与 callback 流量共享。Stage/route throttling 和 Usage Plan quota 是 best-effort target,受控压测中可能短暂超过目标;429 Too Many Requests 应触发带抖动的退避,但不能据此声称账单有严格上限或攻击已经被阻断。Service Quotas 中可申请提升的值与不能提升的固定控制面配额要分开记录,发布流水线的创建、更新频率也可能先于数据面 RPS 触顶。
容量设计应从业务并发反推,而不是只填一个 RPS:记录平均与尾延迟、请求大小、Lambda/HTTP 后端并发、连接池、重试次数和超时预算;WebSocket 还要加入连接时长、消息大小、回调流量与断线风暴。账户共享配额会让一个实验影响同 Region 的其他 API,压测前必须设置速率上限、终止阈值和隔离标签。
价格、免费权益、计费阶梯、配额、timeout、payload 和 route 数都可能变化。估算时从 API Gateway 价格页 与 Service Quotas 入口 读取目标 Region、API 类型和账户批准值,并把请求/消息量、连接分钟、缓存、CloudWatch 日志、PrivateLink、WAF、Lambda、负载均衡、数据传输分别列项。限流、预算告警与异常流量检测要同时存在。
用日志回答“谁拒绝、谁超时、谁返回错误”
REST API 同时有 execution logging 与 access logging;HTTP API 的主要入口是 Stage access logging,不能照搬 REST execution log 心智。REST data tracing 可能记录请求体、响应体与 authorizer 数据,生产环境不应为了方便排障而默认开启。日志格式只保留定位所需字段,配合加密、最小访问、短保留期和敏感字段审查。
排障时至少关联客户端时间与 DNS/TLS 结果、gateway request ID、route/method、gateway status、integration status/error、consumer 或 principal、authorizer 结果、Stage/Deployment ID 和上游 request ID。REST API 还应同时保留 $context.requestId 与 $context.extendedRequestId。客户端可影响的 header 不能作为唯一可信关联键。
| 现象 | 第一证据 | 常见分支 |
|---|---|---|
| 没有 API Gateway 日志 | DNS、TLS、自定义域名映射、客户端目标 URL、CloudWatch 指标 | 请求未到、请求过大、特定 429/内部故障或日志配置缺失;“无日志”不等于“无请求” |
| 网关拒绝且未调用上游 | gateway status、authorizer/resource policy、route key、Stage | token issuer/audience/scope、IAM 签名、VPC endpoint policy、API Key 或路由未命中 |
| 网关 5xx 且上游无记录 | integration error、权限、VPC Link 状态、DNS/TLS | Lambda invoke permission、后端连接、证书、listener 或 timeout |
| 上游已有 4xx/5xx | integration status、上游 request ID 与应用日志 | 错误来自业务服务,网关映射可能又改变了对外状态 |
| 修改后仍是旧行为 | API 类型、Stage 的 Deployment ID、AutoDeploy、调用 URL | REST 未 redeploy、Stage 指错快照、HTTP 自动发布关闭或请求命中另一 Stage |
把发布、回滚和项目接入变成同一条流水线
项目仓库应保存 IaC、OpenAPI 契约引用、authorizer 配置、日志 schema、Stage 策略、标签和回滚参数,不保存真实 token、导出的客户请求或完整管理响应。CI 先执行模板静态检查和变更集审阅,再部署隔离 Stage,跑两上游正向请求、错误凭证、错误 route、上游失败和日志关联,最后才允许 Stage 或自定义域名切换。
REST API 的回滚前先记录旧、新 Deployment ID 与 Stage 当前引用,再执行切换。以下命令只允许用于隔离 API/Stage;切错生产 Stage 会立即改变流量。
aws apigateway create-deployment \
--rest-api-id '<rest-api-id>' \
--description 'candidate'
aws apigateway get-stage \
--rest-api-id '<rest-api-id>' \
--stage-name '<stage>' \
--query '{deploymentId:deploymentId,lastUpdatedDate:lastUpdatedDate}'
aws apigateway update-stage \
--rest-api-id '<rest-api-id>' \
--stage-name '<stage>' \
--patch-operations \
op=replace,path=/deploymentId,value='<known-good-deployment-id>'回指旧 Deployment 后必须重新调用正常 route、错误凭证和两个上游探针,并核对 access log 中的 Stage 与配置行为。对于 HTTP API automatic deployments,回滚不能假设存在一次人工 Deployment 的审批窗口;更稳妥的做法是从版本库恢复已知 IaC,再观察新的 Deployment/Stage 状态和运行请求。WebSocket 回滚还要新建连接,因为旧连接无法证明 $connect 配置已经恢复。
清理要追到附属资源与账单
SAM 实验栈可按下面顺序清理。删除前先撤销测试 token 或关闭测试 IdP client;删除后再盘点 API、日志与栈事件。CloudFormation 栈外手工创建的 VPC Link、interface endpoint、WAF association、自定义域名、Route 53 记录、Lambda、ALB/NLB 和 IAM role 不会因为删 API 自动消失。
sam delete --stack-name "$STACK_NAME" --region "$AWS_REGION" --no-prompts
aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" --region "$AWS_REGION" || true
aws apigatewayv2 get-apis --region "$AWS_REGION" \
--query "Items[?Tags.purpose=='api-gateway-lab'].[ApiId,Name]"
aws apigatewayv2 get-vpc-links --region "$AWS_REGION"
aws ec2 describe-vpc-endpoints --region "$AWS_REGION" \
--filters Name=tag:purpose,Values=api-gateway-lab
aws logs describe-log-groups --region "$AWS_REGION" \
--log-group-name-prefix '/aws/'最终退出证据包括:测试域名不再解析或不再映射;API、Stage、Deployment、authorizer、VPC Link 与 endpoint 已删除或有明确保留 owner;日志组、WAF、Lambda、负载均衡和 DNS 已核销;Cost Explorer 或组织成本系统不再出现该标签的持续增长。若计划迁出 API Gateway,还要先导出契约和非敏感配置,建立新旧入口双跑与 request ID 对照,验证身份、限流、超时、状态映射、WebSocket 长连接和回滚窗口,再移除旧入口。
上线前最后过一遍真实证据
API 类型的选择由交互模型和必需能力决定,并已按目标 Region 的当前比较页复核。每次发布都能指出配置来源、Deployment、Stage、自动发布状态和可回指的已知快照。无凭据、错误 audience/scope、撤权缓存和合法凭据形成成组证据,API Key 没有被当成用户认证。
Private REST API 与 VPC Link 的流量方向分别验证,DNS、endpoint policy、resource policy 和后端安全组都有回退记录。日志能关联 gateway status、integration status、route、principal、配置版本与上游 request ID,敏感 body 和 token 不进入常规日志。容量估算包含共享配额、后端并发、重试放大、日志和网络附属成本,预算告警独立于 best-effort quota。
回滚后重新跑过正常、拒绝、错误上游和 WebSocket 新连接;清理后完成资源与费用双重核销。
