Orquestrando Auto Scaling de Endpoints do SageMaker AI com AWS CDK

O problema: por que atualizar endpoints com auto scaling falha

Quem trabalha com endpoints de inferência em tempo real no Amazon SageMaker AI já deve ter se deparado com uma situação frustrante: ao tentar atualizar a configuração do endpoint — seja mudando o tipo de instância, a quantidade de instâncias ou variáveis de ambiente — o deploy via AWS CloudFormation ou Kit de Desenvolvimento em Nuvem da AWS (AWS CDK) simplesmente falha.

O motivo é uma exigência do próprio SageMaker AI: antes de atualizar o endpoint, é necessário desregistrar o scalable target do Auto Scaling de Aplicações da AWS (Application Auto Scaling). O problema é que, ao fazer isso, o CloudFormation perde o rastreamento desse recurso — e aí começa uma série de falhas em cascata.

A AWS documentou em detalhes os três modos de falha mais comuns nesse cenário:

  • Atualizar a configuração com o auto scaling ainda registrado: o CloudFormation tenta atualizar o endpoint enquanto o scalable target ainda está ativo. O SageMaker AI rejeita a operação com o erro: The variant(s) "[AllTraffic]" must be deregistered as scalable targets with Application Auto Scaling before they can be removed or have their instance type updated.
  • Desregistrar manualmente e atualizar apenas a configuração do endpoint: o deploy funciona, mas o CloudFormation não reregistra o auto scaling — porque os parâmetros de auto scaling no template não mudaram. O endpoint fica rodando sem auto scaling, sem qualquer erro visível. Um problema silencioso e perigoso.
  • Desregistrar manualmente e alterar também os parâmetros de auto scaling: o CloudFormation tenta atualizar um recurso AWS::ApplicationAutoScaling::ScalableTarget que não existe mais, gerando falha. O pior: o rollback também falha (UPDATE_ROLLBACK_FAILED), deixando a stack em estado corrompido que exige intervenção manual no console do CloudFormation.

A solução: um construct CDK com recursos personalizados do CloudFormation

Para resolver esse problema de forma robusta e reutilizável, a AWS propõe substituir os recursos nativos AWS::ApplicationAutoScaling::ScalableTarget e AWS::ApplicationAutoScaling::ScalingPolicy por três recursos personalizados do CloudFormation (CloudFormation custom resources), orquestrados por um construct do CDK.

Esses recursos personalizados gerenciam o ciclo completo de deregistro e reregistro do auto scaling em torno da atualização do endpoint, com ordenação explícita de dependências via chamadas node.add_dependency no CDK. A sequência completa de execução é:

  • Deregistro (custom resource): remove o scalable target antes de qualquer atualização do endpoint. No primeiro deploy, como ainda não existe nenhum scalable target, o handler trata o erro graciosamente e continua.
  • Atualização do endpoint (gerenciada pelo CDK): o CDK atualiza o recurso de endpoint normalmente, aplicando a nova configuração.
  • Registro do Scalable Target (custom resource): aguarda o endpoint atingir o status InService e então registra o scalable target.
  • Aplicação da Scaling Policy (custom resource): aguarda o scalable target estar disponível para consulta e então aplica a política de rastreamento de alvo (target tracking policy).

Os dois últimos recursos utilizam o padrão de conclusão assíncrona do provedor CDK: cada um tem um handler on_event que retorna imediatamente e um handler is_complete que é reinvocado em intervalos de 30 segundos até que a verificação de prontidão seja aprovada. O timeout total é de 1 hora para o registro do scalable target e 15 minutos para a aplicação da política — limites que não dependem do timeout de nenhuma invocação individual do AWS Lambda.

Uma propriedade chamada ConfigHash em cada recurso personalizado força o CloudFormation a reinvocar as funções Lambda sempre que qualquer valor de configuração muda. Por isso, a configuração do endpoint e os parâmetros de auto scaling são definidos juntos: um único ConfigHash dispara toda a orquestração.

O que é implantado na stack

Embora o construct defina três recursos personalizados, a stack implantada contém mais recursos do que isso. Cada recurso personalizado é suportado por um cr.Provider do CDK, e o framework do provedor gera suas próprias funções auxiliares Lambda. No total, a stack implantada contém:

  • Três recursos personalizados
  • Duas máquinas de estado do AWS Step Functions
  • Doze funções Lambda (cinco com código próprio do construct, sete geradas pelo framework do provedor)

As funções geradas pelo framework não precisam ser escritas nem mantidas manualmente, mas é importante contabilizá-las ao revisar os recursos implantados.

Passo a passo: implantando e testando a solução

O código-fonte completo está disponível no repositório GitHub de exemplo. O walkthrough cobre quatro etapas principais.

Pré-requisitos

Para seguir o passo a passo, são necessários:

  • Uma conta AWS com permissões para Amazon SageMaker AI, AWS Lambda, Gerenciamento de Identidade e Acesso (IAM), Application Auto Scaling e Amazon CloudWatch
  • AWS CDK v2 instalado
  • Python 3.12 ou superior
  • uv para gerenciamento de dependências Python
  • Um artefato de modelo SageMaker AI no Amazon S3 e uma imagem de container no Amazon ECR, ou um container de algoritmo nativo do SageMaker AI

Clonando o repositório e instalando dependências

git clone https://github.com/aws-samples/sample-sagemaker-endpoint-autoscaling.git
cd sample-sagemaker-endpoint-autoscaling
uv sync

Configurando o endpoint e os parâmetros de auto scaling

As configurações do endpoint e do auto scaling são definidas juntas no arquivo config/config.yaml, pois um único config_hash controla toda a orquestração. Qualquer alteração em qualquer uma das seções dispara a sequência completa.

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

Os parâmetros de endpoint mapeiam para as APIs CreateModel e CreateEndpointConfig do SageMaker AI. Os parâmetros de auto scaling mapeiam para as APIs RegisterScalableTarget e PutScalingPolicy do Application Auto Scaling.

Implantando e verificando

uv run cdk bootstrap
uv run cdk deploy InferenceStack

Após o deploy, é possível verificar se o scalable target foi registrado corretamente:

aws application-autoscaling describe-scalable-targets \
  --service-namespace sagemaker \
  --resource-ids endpoint/my-model-endpoint/variant/AllTraffic

E verificar se a política de escalonamento foi aplicada:

aws application-autoscaling describe-scaling-policies \
  --service-namespace sagemaker \
  --resource-id endpoint/my-model-endpoint/variant/AllTraffic

Testando uma atualização

Para testar, basta alterar um valor no config.yaml — por exemplo, mudar o tipo de instância de ml.m5.large para ml.m5.xlarge — e reimplantar:

uv run cdk deploy InferenceStack

Após o deploy, a verificação com o comando describe-scalable-targets deve confirmar que o auto scaling continua registrado e com os valores atualizados.

Integrando com uma stack existente

Quem já possui uma stack CDK com recursos SageMaker AI pode integrar o construct diretamente, sem precisar usar o exemplo completo. Basta copiar o diretório sagemaker_autoscaling/constructs/ para o projeto e adicionar o construct junto aos recursos de endpoint existentes:

import json
from sagemaker_autoscaling.constructs.sagemaker_autoscaling_construct import (
    AutoscalingConfig,
    SagemakerAutoscalingConstruct,
    SagemakerAutoscalingProps,
)

# Both endpoint and auto scaling values must be in config_hash so that
# a change to either triggers the auto scaling orchestration.
config_hash = json.dumps({
    "endpoint": {
        "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-model:latest",
        "modelDataUrl": "s3://my-bucket/models/model.tar.gz",
        "instanceType": "ml.m5.large",
        "initialInstanceCount": 1,
    },
    "autoscaling": {
        "minCapacity": 1,
        "maxCapacity": 10,
        "targetValue": 100,
        "scaleInCooldown": 600,
        "scaleOutCooldown": 120,
    },
})

SagemakerAutoscalingConstruct(
    self,
    "Autoscaling",
    props=SagemakerAutoscalingProps(
        endpoint=your_endpoint,  # your CfnEndpoint
        autoscaling=AutoscalingConfig(
            min_capacity=1,
            max_capacity=10,
            target_value=100,
            scale_in_cooldown=600,
            scale_out_cooldown=120,
        ),
        config_hash=config_hash,
    ),
)

Como o construct funciona internamente

Deregistro

O recurso personalizado de deregistro chama deregister_scalable_target antes da atualização do endpoint. Ele trata a exceção ObjectNotFoundException para o caso de primeiro deploy, onde nenhum scalable target existe ainda:

def handler(event, context):
    resource_id = event["ResourceProperties"]["ResourceId"]
    if event["RequestType"] in ("Create", "Update"):
        try:
            client.deregister_scalable_target(
                ServiceNamespace="sagemaker",
                ResourceId=resource_id,
                ScalableDimension="sagemaker:variant:DesiredInstanceCount",
            )
        except client.exceptions.ObjectNotFoundException:
            pass  # No scalable target on first deploy
    return {"PhysicalResourceId": f"deregister-{resource_id}"}

Registro do Scalable Target

Mesmo que o CloudFormation considere o recurso de endpoint como completo assim que a atualização é aceita, o scalable target só pode ser registrado depois que o endpoint atinge o status InService. O handler on_event retorna imediatamente, e o waiter do provedor reinvoca o is_complete em intervalos até que o endpoint esteja pronto:

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}

Na exclusão da stack, o handler on_event desregistra o scalable target para limpeza dos recursos.

Aplicação da Scaling Policy

Existe uma janela breve após o retorno de RegisterScalableTarget em que o target ainda não está disponível para consulta. O mesmo padrão assíncrono é usado: o handler on_event retorna imediatamente, e o is_complete reporta conclusão apenas quando o scalable target está consultável, aplicando então a política:

def handler(event, context):
    props = event["ResourceProperties"]
    resource_id = props["ResourceId"]

    if event["RequestType"] == "Delete":
        return {"IsComplete": True}

    resp = client.describe_scalable_targets(
        ServiceNamespace="sagemaker",
        ResourceIds=[resource_id],
        ScalableDimension="sagemaker:variant:DesiredInstanceCount",
    )
    if not resp.get("ScalableTargets"):
        return {"IsComplete": False}

    client.put_scaling_policy(
        PolicyName=props["PolicyName"],
        ServiceNamespace="sagemaker",
        ResourceId=resource_id,
        ScalableDimension="sagemaker:variant:DesiredInstanceCount",
        PolicyType="TargetTrackingScaling",
        TargetTrackingScalingPolicyConfiguration=json.loads(props["ScalingPolicyConfig"]),
    )
    return {"IsComplete": True}

Limpeza dos recursos

Para evitar cobranças desnecessárias após os testes, basta destruir a stack:

uv run cdk destroy InferenceStack

Esse comando remove o endpoint do SageMaker AI, a configuração de endpoint, o modelo, as funções Lambda, as roles do IAM, os alarmes do Amazon CloudWatch criados pela política de rastreamento de alvo e desregistra o scalable target.

Conclusão

A solução apresentada pela AWS resolve de forma elegante um problema real e recorrente: manter o auto scaling funcionando corretamente em endpoints SageMaker AI durante atualizações de configuração, sem exigir intervenção manual. O construct CDK reutilizável encapsula toda a orquestração com três recursos personalizados, ordenação explícita de dependências e um mecanismo de ConfigHash que garante que qualquer mudança — seja no endpoint ou nos parâmetros de escalonamento — dispare o fluxo completo.

Para mais informações sobre o Application Auto Scaling para SageMaker AI, consulte a documentação Escalonamento automático de modelos Amazon SageMaker AI. Para mais detalhes sobre recursos personalizados do CloudFormation, consulte o Guia do Usuário do AWS CloudFormation.

Fonte

Orchestrating SageMaker AI endpoint auto scaling with AWS CDK (https://aws.amazon.com/blogs/machine-learning/orchestrating-sagemaker-ai-endpoint-auto-scaling-with-aws-cdk/)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *