Amazon SageMaker AI のリアルタイムエンドポイント設定(インスタンスタイプ、インスタンス数、環境変数の変更)を更新すると、AWS CloudFormation または AWS Cloud Development Kit (AWS CDK) を通じたデプロイが失敗します。SageMaker AI では、エンドポイントを更新する前にスケーラブルターゲットの登録を解除する必要がありますが、登録を解除すると、CloudFormation が AWS Application Auto Scaling リソースを追跡できなくなります。
この記事では、再利用可能な CDK コンストラクトと AWS CloudFormation カスタムリソースを使用して、エンドポイントの更新中にオートスケーリングの登録解除と登録を自動的にオーケストレーションする方法を紹介します。これにより、手動での操作なしに、1 回のデプロイでエンドポイント設定とオートスケーリングパラメータの両方を更新できます。
この問題が発生する理由
ネイティブの CloudFormation オートスケーリングリソースを使用して SageMaker AI エンドポイントをデプロイした後、エンドポイント設定を変更して再デプロイすると、いくつかの失敗パターンが発生します。具体的な失敗内容は、何を変更したか、また問題をどのように回避したかによって異なります。
オートスケーリングが登録された状態でエンドポイント設定を変更する
CloudFormation は、スケーラブルターゲットがまだ登録されている状態でエンドポイントを更新しようとします。SageMaker AI は、以下のエラーで更新を拒否します:
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.
SageMaker AI は、CloudFormation がオートスケーリングリソースに到達する前に、エンドポイントの更新をブロックします。
オートスケーリングを手動で登録解除した後、エンドポイント設定のみを変更する
AWS Command Line Interface (AWS CLI) を使用してスケーラブルターゲットの登録を解除し、エンドポイント設定の変更のみで再デプロイすると、デプロイは成功します。しかし、オートスケーリングが CloudFormation テンプレートに含まれていても、CloudFormation はオートスケーリングを登録しません。CloudFormation テンプレート内のオートスケーリングパラメータが変更されていないため、CloudFormation はスケーラブルターゲットを登録せず、スケーリングポリシーも再適用しません。その結果、エンドポイントはオートスケーリングなしで実行されており、問題が発生したことを示すエラーも表示されません。
オートスケーリングを手動で登録解除した後、オートスケーリングパラメータを変更する
AWS CLI を使用してスケーラブルターゲットの登録を解除し、オートスケーリングパラメータの変更を含めて再デプロイすると、CloudFormation はすでに存在しない ScalableTarget リソースを更新しようとします:
Resource of type 'AWS::ApplicationAutoScaling::ScalableTarget' with identifier 'endpoint/<name>/variant/AllTraffic|sagemaker:variant:DesiredInstanceCount|sagemaker' was not found.
この失敗が特筆すべきなのは、ロールバックも失敗し (UPDATE_ROLLBACK_FAILED)、スタックが手動での復旧を要する壊れた状態になり、CloudFormation コンソールでの手動介入が必要になる点です。
ソリューションの概要
この CDK コンストラクトを使用すると、ネイティブの AWS::ApplicationAutoScaling::ScalableTarget および AWS::ApplicationAutoScaling::ScalingPolicy CloudFormation リソースを 3 つのカスタムリソースに置き換えることができます。これらのカスタムリソースは、CDK 内の node.add_dependency 呼び出しによって強制される明示的な依存関係の順序付けに従い、エンドポイントの更新を挟んでオートスケーリングの登録解除と登録を行います。完全なシーケンスは以下のとおりです:
- Deregister (カスタムリソース): エンドポイントの更新が始まる前にスケーラブルターゲットを削除します。初回デプロイ時にはスケーラブルターゲットがまだ存在しないため、ハンドラーはこのエラーを適切に処理して続行します。
- Endpoint update (CDK 管理): CDK がエンドポイントリソースを通常どおり更新し、新しいエンドポイント設定に切り替えます。
- Register Scalable Target (カスタムリソース): エンドポイントが
InServiceステータスに達するのを待ってから、スケーラブルターゲットを登録します。 - Attach Scaling Policy (カスタムリソース): スケーラブルターゲットがクエリ可能になるのを待ってから、ターゲット追跡ポリシーを適用します。
図 1: CDK 管理のエンドポイント更新を挟んだ、SageMaker AI エンドポイントのオートスケーリングのカスタムリソースによるオーケストレーション。
Register Scalable Target と Attach Scaling Policy は、CDK プロバイダーの非同期完了パターンを使用します。各カスタムリソースには、即座に返す on_event ハンドラーと、 is_complete handler。プロバイダーのウェイターは、 readiness チェックが合格するまで、一定間隔(30秒ごと)で is_complete ハンドラーを一定間隔(30秒ごと)で実行し、準備状況チェックが通過するまで続けます。このプロバイダーの total_timeout (Register Scalable Target は1時間、Attach Scaling Policy は15分)が待機時間を制限するため、単一の Lambda 呼び出しのタイムアウトには制限されません。
A ConfigHash 各カスタムリソースの property により、設定値が変わるたびに CloudFormation が Lambda 関数を再起動します。エンドポイント設定とオートスケーリングのパラメータは、単一の ConfigHash がオートスケーリングのオーケストレーションを駆動するため、一緒に定義されています。
このコンストラクトは3つのカスタムリソースを定義していますが、デプロイされたスタックには3つ以上のLambda関数が含まれています。各カスタムリソースはCDKによってバックアップされています。 cr.Providerそして、プロバイダーフレームワークは独自のヘルパーLambda関数を生成します。同期プロバイダー(Deregister)はフレームワーク関数を1つ追加します。非同期プロバイダー(Register Scalable Target と Attach Scaling Policy)は3つ追加します(「onEvent, isComplete、および onTimeout")に加え、ウェイターを駆動する AWS Step Functions ステートマシンが含まれます。その結果、デプロイされたスタックには、カスタムリソースが3つ、Step Functions ステートマシンが2つ、Lambda 関数が12個含まれることになります。リソースのうち5つは、このコンストラクト自身のハンドラーコードを保持しています(Deregister 用の1つと、 on_event および is_complete (各非同期リソースに対するペア)で、残りの7つはプロバイダーフレームワークによって生成されます。フレームワークが生成した関数を記述したり保守したりすることはありませんが、それらはスタック内に存在するため、デプロイされたリソースを確認する際に考慮しておく価値があります。
ウォークスルー
このチュートリアルでは、提供されたサンプルスタックを使用してオートスケーリング対応のSageMaker AIエンドポイントをデプロイし、その後、設定の更新が正しく機能することを確認します。
このチュートリアルには、以下のステップが含まれています:
- リポジトリをクローンし、依存関係をインストールしてください。
- エンドポイントと自動スケーリングの設定を行います。
- デプロイして検証します。
- アップデートをテストする。
ソースコードは以下で入手できます: リンクされたGitHubリポジトリ.
前提条件
このチュートリアルを実行するには、以下が必要です。
- Amazon SageMaker AI、AWS Lambda、AWS Identity and Access Management(IAM)、Application Auto Scaling、Amazon CloudWatch の権限を持つ AWS アカウント。
- AWS CDK v2 インストールされました。
- Python 3.12 以降。
- uv Pythonの依存関係管理のため。
- Amazon Simple Storage Service (Amazon S3) 内の SageMaker AI モデルアーティファクトと Amazon Elastic Container Registry (Amazon ECR) 内のコンテナイメージ、または SageMaker AI の組み込みアルゴリズムコンテナ。
リポジトリをクローンして依存関係をインストールしてください
- リポジトリをクローンします:
git clone https://github.com/aws-samples/sample-sagemaker-endpoint-autoscaling.git cd sample-sagemaker-endpoint-autoscaling - 依存関係をインストールしてください:
uv sync
エンドポイントとオートスケーリングの設定を行う
エンドポイントとオートスケーリングの設定は、config/ 内で一緒に定義されています。config.yaml なぜなら単一の config_hash オートスケーリングのオーケストレーションを駆動します。どちらかのセクションに変更が加えられると、一連の全処理がトリガーされます。
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
このエンドポイントパラメータは SageMaker AI の CreateModel および CreateEndpointConfig API。オートスケーリングのパラメータは Application Auto Scaling の RegisterScalableTarget および PutScalingPolicy API。
デプロイと検証
- まだの場合は、まずAWS環境をブートストラップしてください:
uv run cdk bootstrap - スタックをデプロイします:
uv run cdk deploy InferenceStack
CloudFormation は SageMaker AI のモデル、エンドポイント設定、エンドポイントを作成し、その後 3 つのカスタムリソースを順番に実行してオートスケーリングを登録します。
- スケーラブルターゲットが登録されたことをデプロイメントが確認します:
aws application-autoscaling describe-scalable-targets --service-namespace sagemaker --resource-ids endpoint/my-model-endpoint/variant/AllTraffic - デプロイがスケーリングポリシーを適用したことを確認します:
aws application-autoscaling describe-scaling-policies --service-namespace sagemaker --resource-id endpoint/my-model-endpoint/variant/AllTraffic
アップデートをテストする
config/ 内の値を変更するconfig.yamlたとえば、インスタンスタイプを次から更新する ml.m5.large へ ml.m5.xlarge、再デプロイします:
uv run cdk deploy InferenceStack
デプロイが完了したら、auto scaling が引き続き登録されており、更新された値が反映されていることを確認します。
aws application-autoscaling describe-scalable-targets
--service-namespace sagemaker
--resource-ids endpoint/my-model-endpoint/variant/AllTraffic
既存のスタックとの統合
新規に始める場合や参照実装が必要な場合は、サンプルスタックを使用してください。SageMaker AI リソースを含む CDK スタックが既にある場合は、コンストラクトを直接使用してください。
既存のスタックと統合するには、 sagemaker_autoscaling/constructs/ ディレクトリをプロジェクトにコピーし、エンドポイントリソースと一緒にコンストラクトを追加します:
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,
),
)
コンストラクトの仕組み
このセクションでは、 SagemakerAutoscalingConstruct.
の重要な実装の詳細を説明します。
Deregister deregister_scalable_target Deregister カスタムリソースは、エンドポイントの更新前に ObjectNotFoundException を呼び出します。スケーラブルターゲットが存在しない初回デプロイ時の
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}"}
を処理します:
props.endpoint.node.add_dependency(deregister)
この依存関係は CDK で以下のように確立されます:
CloudFormation は更新が受け付けられた時点でエンドポイントリソースを完了扱いにしますが、スケーラブルターゲットを登録できるのはエンドポイントが到達した後のみです。 InServiceCloudFormation は更新が受け付けられた時点でエンドポイントリソースを完了とみなしますが、スケーラブルターゲットを登録できるのはエンドポイントが on_event に達した後のみです。その待機を 1 つの Lambda 呼び出しでブロックするのではなく、Register Scalable Target はプロバイダーの非同期完了パターンを使用します。 is_complete ハンドラーは即座に返り、プロバイダーのウェイターが、エンドポイントが query_interval になるまで InService間隔で total_timeout (1 時間) が待機時間を制限します。これは単一の Lambda 呼び出しのタイムアウトではありません:
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}
(1 hour)が待機時間を制限し、単一の Lambda 呼び出しのタイムアウトではありません: on_event スタック削除時には、
ハンドラーがスケーラブルターゲットの登録を解除してクリーンアップします。
Attach Scaling Policy RegisterScalableTarget した直後には、ターゲットがまだ照会できない短い時間枠があります。Attach Scaling Policy も同じ非同期完了パターンを使用します: on_event ハンドラーは即座に返り、 is_complete ハンドラーはスケーラブルターゲットが照会可能になった時点でのみ完了を報告し、その時点でポリシーを適用します。プロバイダーの total_timeout (15 minutes)が待機時間を制限します:
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}
スタック削除時には、 on_event ハンドラーがスケーリングポリシーを削除してクリーンアップします。
クリーンアップ
将来の課金を避けるために、リソースを削除します:
uv run cdk destroy InferenceStack
これにより、SageMaker AI エンドポイント、エンドポイント設定、モデル、Lambda 関数、IAM ロール、ターゲット追跡スケーリングポリシーによって作成された Amazon CloudWatch アラームが削除され、スケーラブルターゲットの登録が解除されます。
結論
この記事では、再利用可能なCDKコンストラクトを使用してAuto Scalingの登録解除と登録を自動化し、SageMaker AIエンドポイントの更新をオーケストレーションする方法をご紹介しました。このコンストラクトは、明示的な依存関係の順序付けを行った3つのCloudFormationカスタムリソースと、 ConfigHash デプロイメントを跨いでもオートスケーリングを維持するためのプロパティです。
始めるには、次をクローンしてください: sample-sagemaker-endpoint-autoscaling リポジトリを開き、セットアップ手順に従ってください。このリポジトリには、直接デプロイできるサンプルスタックが含まれています。また、既存のCDKスタックにこのコンストラクトを統合することもできます。
SageMaker AI の Application Auto Scaling の詳細については、次を参照してください Amazon SageMaker AI モデルの自動スケーリング。CloudFormation カスタムリソースの詳細については、「Custom resources in the AWS CloudFormation ユーザーガイド.