用CDK解决推理端点扩缩容部署冲突
SageMaker AI端点注册自动扩缩容后,更新操作可能导致CloudFormation和CDK部署失败。文章用可复用CDK构造与自定义资源,在更新前后自动注销、重新注册扩缩容配置。
AI 深度解读
用 AWS CDK 更新 SageMaker AI 实时推理端点,改个实例类型,部署可能失败;手动解除自动扩缩容后再部署,又可能成功了却丢掉扩缩容能力。问题出在端点更新和扩缩容注册之间存在顺序约束,CloudFormation 无法自动补齐这段流程。
AWS 这篇文章给出的办法,是用三个自定义资源,把注销、更新、重新注册和挂载策略串进一次部署。对维护 AI 推理服务的团队,它提供了一份具体实现,也提醒我们:基础设施代码里的「部署完成」,还需要运行状态来验证。
为什么手动注销之后,部署反而更难收拾
原文描述了三条故障路径。
第一条最直接:端点仍注册为可扩缩目标时,CloudFormation 尝试更新端点。SageMaker AI 会拒绝移除相关变体或更新其实例类型,错误中会明确指出 AllTraffic 必须先从 Application Auto Scaling 注销。
另外两条更容易误判。通过 AWS CLI 手动注销后,如果只改端点配置,部署可以成功,但模板里的扩缩容参数没有变化,CloudFormation 就不会重新注册目标,也不会重新应用策略。服务继续运行,却失去了自动扩缩容,而且没有报错提醒。
如果同时改了扩缩容参数,CloudFormation 又会尝试更新已经不存在的 AWS::ApplicationAutoScaling::ScalableTarget,最终报资源找不到。原文指出,这条路径还会导致回滚失败,栈进入 UPDATE_ROLLBACK_FAILED,需要到控制台人工恢复。
模板里声明了扩缩容资源,不代表部署之后扩缩容一定还在。
这也是作者没有选择继续补手工步骤的原因:需要把注销和恢复都纳入部署,而不是让运维操作与 CloudFormation 各自维护一份状态。
三个自定义资源,解决的是顺序和等待
作者用三个 CloudFormation 自定义资源,替换原生的 AWS::ApplicationAutoScaling::ScalableTarget 和 AWS::ApplicationAutoScaling::ScalingPolicy。
完整顺序是:注销可扩缩目标,更新端点,注册可扩缩目标,挂载目标跟踪策略。端点仍由 CDK 正常管理,前后的动作通过 node.add_dependency 明确排序。其中,props.endpoint.node.add_dependency(deregister) 保证注销先于端点更新执行。
第一次部署时,目标还不存在,注销处理器会捕获 ObjectNotFoundException 并继续。这让同一套流程可以同时覆盖创建和更新。
更关键的是等待条件。按照原文描述,CloudFormation 接受端点更新后,端点不一定已经进入可以注册扩缩容的 InService 状态。注册成功后,也存在目标暂时还查不到的窗口。因此,「前一个调用返回了」不能直接当成「下一个动作可以开始」。
下面这段注册处理器,把端点状态检查和注册动作放在一起:
def handler(event, context):
props = event["ResourceProperties"]
resource_id = props["ResourceId"]
endpoint_name = resource_id.split("/")[1]
if event["RequestType"] == "Delete":
return {"IsComplete": True}
status = sm_client.describe_endpoint(EndpointName=endpoint_name)["EndpointStatus"]
if status == "Failed":
raise Exception("Endpoint in Failed state")
if status != "InService":
return {"IsComplete": False}
as_client.register_scalable_target(
ServiceNamespace="sagemaker",
ResourceId=resource_id,
ScalableDimension="sagemaker:variant:DesiredInstanceCount",
MinCapacity=int(props["MinCapacity"]),
MaxCapacity=int(props["MaxCapacity"]),
)
return {"IsComplete": True}
要看的是 status != "InService" 这一行:它返回未完成,让框架稍后再查;端点进入 Failed 时则直接报错。
注册目标和挂载策略都采用 CDK provider 的异步完成模式。on_event 立即返回,框架每隔 30 秒调用一次 is_complete。注册目标的 total_timeout 是 1 小时,挂载策略是 15 分钟,等待过程不依赖单次 Lambda 调用持续运行。
挂载策略时,处理器先调用 describe_scalable_targets,确认目标可查询,再调用 put_scaling_policy,使用 TargetTrackingScaling 策略类型。
这套封装也有资源成本:最终部署包含三个自定义资源、两个 Step Functions 状态机和十二个 Lambda 函数。其中五个函数承载作者的处理逻辑,其余七个由 provider 框架生成。代码入口看着少,实际部署清单并不小。
为什么只改扩缩容参数,也要触发整套流程
CloudFormation 需要看到资源属性发生变化,才会再次调用自定义资源。作者为此加入 ConfigHash,将端点配置和扩缩容参数共同纳入变更标识。
示例将两部分配置集中在 config/config.yaml:
endpoint:
endpoint_name: "my-model-endpoint"
image: "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-model:latest"
model_data_url: "s3://my-bucket/models/model.tar.gz"
instance_type: "ml.m5.large"
initial_instance_count: 1
environment: {}
autoscaling:
min_capacity: 1
max_capacity: 10
target_value: 100
scale_in_cooldown: 600
scale_out_cooldown: 120
要看的是两部分配置的共同参与:任意一部分变化,都会触发整套编排。端点参数对应 CreateModel、CreateEndpointConfig,扩缩容参数对应 RegisterScalableTarget、PutScalingPolicy。
接入已有栈时,可以复制 sagemaker_autoscaling/constructs/,通过 SagemakerAutoscalingConstruct、SagemakerAutoscalingProps 和 AutoscalingConfig 关联已有 CfnEndpoint,并传入包含两部分配置的 config_hash。
这里有两个边界。第一,原文没有说明 target_value: 100 对应什么指标,不能直接把它理解成 CPU 使用率或固定请求量。第二,已有栈的 json.dumps 示例没有包含 YAML 中的 environment;如果团队需要靠环境变量变更触发编排,就应把它也纳入变更标识。
运行示例需要 AWS CDK v2、Python 3.12 或更高版本和 uv,还需要相应 AWS 权限、S3 模型产物,以及 ECR 镜像或内置算法容器。仓库获取命令是 git clone https://github.com/aws-samples/sample-sagemaker-endpoint-autoscaling.git,进入目录后执行 uv sync。
环境初始化使用 uv run cdk bootstrap,部署使用 uv run cdk deploy InferenceStack。原文给出的更新验证,是把 ml.m5.large 改成 ml.m5.xlarge 后再次部署,再检查扩缩容状态。
验证应覆盖两个查询:aws application-autoscaling describe-scalable-targets 使用 --service-namespace sagemaker --resource-ids endpoint/my-model-endpoint/variant/AllTraffic;describe-scaling-policies 则使用同一命名空间和单数参数 --resource-id。目标和策略都要确认。
我的判断
我认可这套方案对问题的拆分:用依赖控制顺序,用状态查询处理等待,用配置变更驱动重复执行。它比手动注销后凭记忆恢复更容易纳入日常部署,也能避免只看栈成功状态造成的误判。
但这是特定 AWS 部署链路的实现方案。对于只调用托管模型 API 的前端或 Node 服务,直接引入它没有必要。已有 CDK 和 SageMaker AI 端点的团队,才是主要适用对象。
文章提供了代码和验证步骤,但没有给出部署耗时、故障率或与独立流水线编排的对照数据,也没有报告完整的故障注入结果。可以判断它针对上述故障设计了处理机制,不能据此认定所有更新和回滚路径都已经验证可靠。
还有一个取舍:只改扩缩容参数也会触发完整注销和恢复流程。规则统一,维护更直接,但原文没有说明期间的容量风险,也没承诺更新全程无中断。生产接入前,这部分需要结合自己的流量和容量策略确认。
能拿来做什么
- 如果你们已有 CDK 管理的 SageMaker AI 端点,可以先用示例栈验证实例类型更新,再把 construct 接入现有栈,并确认所有相关配置都进入
config_hash。 - 如果你负责 Node 推理网关背后的部署流水线,可以把可扩缩目标和策略查询加入部署验收,避免服务可调用就被判定为发布完成。
- 如果你让 AI 编程助手修改这套基础设施代码,应要求它同时检查依赖顺序、
InService等待、目标可查询条件,以及创建、更新、删除事件的处理。 - 如果只是试跑示例,结束后执行
uv run cdk destroy InferenceStack,清理端点、模型、Lambda、IAM 角色、策略创建的 CloudWatch 告警及扩缩容注册,避免持续计费。