这篇 AWS 机器学习博客讨论了在 Amazon SageMaker AI 实时终端节点上使用 Application Auto Scaling 时,CloudFormation 或 AWS CDK 更新配置会失败的常见问题。问题源于 SageMaker 的要求:在删除终端节点变体或修改其实例类型前,必须先在 Application Auto Scaling 中注销对应的可扩缩目标;但一旦在 CloudFormation 之外手动注销,CloudFormation 就无法继续跟踪这些扩缩资源。
文章归纳了三种失败情形。第一,如果可扩缩目标仍处于注册状态就修改终端节点配置,CloudFormation 会先尝试更新端点,而 SageMaker 会拒绝操作,并提示 AllTraffic 变体必须先注销为可扩缩目标,之后才能移除或更新实例类型。第二,如果用户通过 AWS CLI 手动注销目标,然后只修改终端节点配置,部署虽能成功,但 CloudFormation 不会重新注册扩缩目标,也不会重新应用扩缩策略,因为模板中的扩缩参数没有变化;结果是端点静默失去自动扩缩能力。第三,如果手动注销后又修改扩缩参数,CloudFormation 会尝试更新已不存在的 ScalableTarget 资源并报错;更严重的是回滚也可能失败,堆栈进入 UPDATE_ROLLBACK_FAILED 状态,需要在控制台人工恢复。
解决方案是一个可复用的 CDK construct。它不再直接使用原生 AWS::ApplicationAutoScaling::ScalableTarget 和 ScalingPolicy 资源,而是用三个 CloudFormation 自定义资源,并通过 CDK 的 add_dependency 明确编排顺序:先注销可扩缩目标,再由 CDK 更新终端节点,然后等待端点进入 InService 后注册可扩缩目标,最后等待目标可查询后附加目标跟踪扩缩策略。首次部署时还没有可扩缩目标,注销处理程序会捕获 ObjectNotFoundException 并继续。
注册目标和附加策略采用 CDK provider 的异步完成模式。每个自定义资源的 on_event 处理器立即返回,is_complete 处理器则由 provider 的 waiter 每隔约 30 秒重新调用,直到就绪检查通过。Register Scalable Target 的总超时为 1 小时,Attach Scaling Policy 为 15 分钟,因此等待不受单次 Lambda 调用超时限制。每个自定义资源还带有 ConfigHash 属性;只要端点配置或扩缩参数变化,CloudFormation 就会重新调用相关 Lambda。文章强调,端点配置和扩缩参数必须放在同一个 ConfigHash 中,因为一次哈希变化会驱动整套扩缩编排。
部署后的堆栈并不只有三个 Lambda。每个自定义资源背后都有一个 CDK cr.Provider,provider 框架会生成辅助函数:同步 provider 的 Deregister 增加一个框架函数;异步 provider 的 Register Scalable Target 和 Attach Scaling Policy 各增加 onEvent、isComplete、onTimeout 三个函数,并由 AWS Step Functions 状态机驱动 waiter。因此,堆栈实际包含三个自定义资源、两个 Step Functions 状态机和十二个 Lambda 函数。其中五个资源承载 construct 自身的处理代码,另外七个由 provider 框架生成;这些函数无需用户维护,但在审查部署资源时应计入。
文章给出了完整演练。用户需要具备 SageMaker AI、Lambda、IAM、Application Auto Scaling 和 CloudWatch 权限的 AWS 账户,安装 AWS CDK v2、Python 3.12 或更高版本以及 uv,并准备 S3 中的模型构件和 ECR 镜像,或使用 SageMaker 内置算法容器。演练先克隆 GitHub 示例仓库并运行 uv sync,然后在 config/config.yaml 中同时配置 endpoint 和 autoscaling。端点参数对应 CreateModel 和 CreateEndpointConfig API,扩缩参数对应 RegisterScalableTarget 和 PutScalingPolicy API。随后执行 cdk bootstrap 和 cdk deploy InferenceStack,并用 describe-scalable-targets 与 describe-scaling-policies 检查可扩缩目标和策略是否注册成功。
更新测试只需修改 config/config.yaml,例如把实例类型从 ml.m5.large 改为 ml.m5.xlarge,再重新部署;部署完成后再次查询,确认自动扩缩仍然注册并反映新值。若已有 CDK 堆栈,可把 sagemaker_autoscaling/constructs/ 目录复制到项目中,在端点资源旁实例化 SagemakerAutoscalingConstruct,并传入端点、AutoscalingConfig 以及同时包含端点与扩缩参数的 config_hash。
实现细节方面,Deregister 自定义资源在端点更新前调用 deregister_scalable_target,并针对首次部署没有目标的情况捕获 ObjectNotFoundException;CDK 通过 endpoint.node.add_dependency(deregister) 建立依赖。Register Scalable Target 不能只依赖 CloudFormation 认为端点资源已完成,因为可扩缩目标必须在端点进入 InService 后才能注册;因此它使用异步完成模式,on_event 立即返回,is_complete 持续查询端点状态,若状态为 Failed 则抛出异常。Attach Scaling Policy 同样等待可扩缩目标可查询后再应用策略。原文在 Register 处理程序的条件检查处被截断,但上述编排逻辑和配置要点已经完整说明了方案。