Amazon SageMaker Studio에서 삭제하지 않은 리소스 확인 하기

MZC_Global
Cloud Villains
Published in
12 min readDec 7, 2023

들어가며

Amazon SageMaker Studio는 여러 MLOPS기능을 제공 하고 있지만, 여러 리소스를 사용 하는 도중에 리소스 삭제를 잊어버려 과금이 발생하는 경우가 있습니다. (사실 저도 있었습니다.)

특히 Amazon SageMaker Data Wrangler는 편리한 반면, 시간 마다 요금이 발생하기 때문에, 리소스 삭제를 잊게 되면 엄청난 돈이 들게 됩니다.

IDLE상태 리소스가 있는 경우 (자동 셧다운이 아닌) 관리자나 유저가 리소스 삭제를 잊지 않았는지 확인 할 수 있도록 되어 있습니다. 본 게시글에서는 해당 내용에 대해 설명 드리도록 하겠습니다.

개요

  1. 요금에 대해
  2. 환경 가정
  3. Lambda 함수의 생성
  4. 실행 결과

1. 요금에 대해

https://aws.amazon.com/jp/blogs/news/save-costs-by-automatically-shutting-down-idle-resources-within-amazon-sagemaker-studio/

(*번역자 주 : 해당 내용은 한국어 혹은 영어로 게시되어 있지 않은 게시글 이기 때문에 자동 번역 기능을 사용하여 확인 하시는 것을 추천 드립니다)

발생하는 비용은 인스턴스 타입에 근거 하고 있고, 인스턴스 마다 개별 청구 됩니다. 과금은 인스턴스 생성 시에 개시 되어 인스턴스 상 모든 애플리케이션이 셧다운 되거나 인스턴스 다운 된 때에 정지 합니다.

인스턴스 상 실행 되어 있는 노트북을 셧다운 해도 인스턴스를 셧다운 하지 않으면 과금은 계속 됩니다.

여러 노트북을 다른 커널로 기동했다고 해도 같은 인스턴스 타입인 경우 노트북은 같은 인스턴스 상에서 실행 됩니다. 복수의 노트북이 열려있다고 해도 실행 중인 인스턴스가 1대인 경우는 그 1대의 인스턴스 기동 시간에 대해서만 과금 됩니다.

노트북을 셧다운 하면 노트북 자체에서는 삭제 되지 않지만, 저장 되지 않은 데이터를 잃을 수 있습니다.

요금에 대해서는 상기 페이지가 알기 쉬울것으로 생각 됩니다.

이번에는 커널 게이트 웨이 애플리케이션과 SageMaker Data Wrangler리소스를 테스트 해 보겠습니다.

2. 환경 가정

  • Domain

복수 도메인이 같은 리전에 존재 해 있는 환경을 가정 하고 있습니다. 이번에는 테스트로 2가지 도메인을 버지니아 북부 리전으로 생성 해 보겠습니다.

  • User Profile

test-domain-1에는 2가지 유저 프로파일이 존재 합니다.

test-domain-2에는 1개의 유저 프로파일이 존재 합니다.

  • Resource

각각 유저 프로파일이 커널 게이트 웨이 애플리케이션과 Data Wangler리소스를 각각 시작 시키고 처리를 실행 합니다.

test-domain-1에 속하는 user01은 Data Wrangler용 리소스를 셧다운 한 것이지만, 커널 게이트웨이 애플리케이션을 기동한 채로 방치합니다.

test-domain-1에 속하는 user02는 커널 게이트웨이 애플리케이션을 셧다운 한 것이지만, Data Wrangle용 리소스를 기동한 채로 방치합니다.

* 인스턴스 셧다운에 대해

인스턴스를 삭제 하기 위해서는 애플리케이션을 삭제해야 합니다.

커널 게이트웨이 애플리케이션을 삭제하니 ml.t3.medium의 인스턴스가 동시에 셧다운 했습니다.

  • Lambda

이번에는 메인마다 Lambda함수를 준비 합니다.

test-domain-1용의 Lambda함수를 실행하여 리소스를 장시간 IDLE상태인 채로 유저를 특정 합니다.

이번 경우에는 test-domain-1 도메인에 속한 유저 (user01/user02)2명이 리소스를 삭제하는것을 잊었기 때문에 2명의 유저명을 Lambda함수로 취득 할 수 있다면 좋습니다.

  • CloudWatch Logs
  • /aws/sagemaker/studio의 로그 그룹을 확인 합니다.
  • 커널 게이트 웨이/Data Wrangler리소스 어느 쪽도 같은 애플리케이션 타입으로서 로그 스트림이 만들어 집니다.
[domain-id]/[user-profile-name]/[app-type]/[app-name]

상기 로그 스트림이름으로 ‘어느 도메인인지’ ‘어느 유저 프로파일인지’ ‘애플리케이션 타입’ ‘애플리케이션 이름’을 식별 가능합니다.

3. Lambda함수의 생성

※설정

Runtime: Python 3.11

Architecture: x86_64

Lambda함수에 붙이는 IAM롤은 ‘CloudWatch Logs’, ‘SageMaker’의 권한을 적절하게 추가 해 주세요.

※코드

※Parameter섹션의 3가지 파라미터는 환경에 맞춰 변경 해 주세요.

import boto3
from datetime import datetime, timezone, timedelta
jst = timezone(timedelta(hours=9), 'JST')


# Parameter
threshold = 86400 #tolerable time for idle state
target_region = "us-east-1" #region
domain_id = "xxxxxxxxxxxx" #domain id



def list_target_user_profile(specified_domain_id, client):

profiles = []

# Check the UserProfile Name corresponding to the specified domain name and store it in the list
for profile in client.list_user_profiles(DomainIdEquals = specified_domain_id)['UserProfiles']:
name = profile['UserProfileName']
profiles.append(name)

return profiles

def list_target_app(userProfiles, client):

apps = []

# Check Apps per UserProfile and store in list
# Only when the application type is kernelGateway and the status is Inservice, it is the target of storage.
for i in userProfiles:
user = i
for n in client.list_apps(UserProfileNameEquals = i)['Apps']:
if len(n) > 0 and n['AppType'] == 'KernelGateway' and n['Status'] == 'InService':
user = n['UserProfileName']
app = n['AppName']
dict= {}
dict[user] = app
apps.append(dict)

return apps


def search_idle_instace(apps, client):

target_user = []

# Compare current time and log update date
# If the difference is greater than or equal to the threshold, store in the list
for i in apps:
for d in i:
response = client.describe_log_streams(
logGroupName='/aws/sagemaker/studio',
logStreamNamePrefix = f'{domain_id}/{d}/KernelGateway/{i[d]}',
descending=True,
limit = 1
)

modified_time = response['logStreams'][0]['lastEventTimestamp']
time = datetime.fromtimestamp(modified_time/1000, jst)
print(time)

dt = datetime.now(jst)
print(dt)

diff = dt - time
print(diff)

d_diff = diff.days * 86400
sum_diff = d_diff + diff.seconds
message = f"user_name:{d}, diff:{sum_diff}\n"
print(message)


if sum_diff > threshold and d not in target_user:
target_user.append(d)

return target_user


def lambda_handler(event, context):

user_profiles = []

sm_client = boto3.client("sagemaker", target_region)
cw_logs_client = boto3.client('logs', target_region)

# Stores the names of UserProfiles belonging to the domain
user_profiles = list_target_user_profile(domain_id, sm_client)
print(user_profiles)

# Define the correspondence between UserProfiles and running applications
apps = list_target_app(user_profiles, sm_client)
print(apps)

# Check the application log to see which applications are in idle status
target_user = search_idle_instace(apps, cw_logs_client)
print(target_user)

4. 실행 결과

Lambda 함수를 실행 합니다.

24시간(86400초)경과 이후로 실행 하고 있습니다.

# result(list_target_user_profile)
['user02', 'user01']

# result(list_target_app)
[{'user02': 'sagemaker-data-wrang-ml-m5-4xlarge-xxxxxxxxxxxxxxxxxxxxxxxx'}, {'user01': 'sagemaker-data-scienc-ml-t3-medium-xxxxxxxxxxxxxxxxxxxxxxxxx'}]

# result(search_idle_instace)
2023-11-17 11:04:42.316000+09:00
2023-11-18 12:25:04.657602+09:00
1 day, 1:20:22.341602
user_name:user02, diff:91222

2023-11-17 10:25:06.861000+09:00
2023-11-18 12:25:04.689970+09:00
1 day, 1:59:57.828970
user_name:user01, diff:93597

# target_user list
['user02', 'user01']

test-use-1에 소속 해 있고, 또한 IDLE상태의 리소스를 가진 유저 이름이 취득 되어 있습니다.

마지막으로

이번에 준비한 Lambda를 EventBridge + SNS와 연계 시켜 일상적으로 정기 실행 시키면 관리자나 유저가 리소스 삭제를 잊지 않았는지 확인 가능합니다. 다양하게 어레인지 해 보시길 바랍니다.

지금까지 읽어 주셔서 감사합니다 !

원문게시글 : https://zenn.dev/megazone_japan/articles/e365cea61a7af0

메가존 일본 법인 블로그에 업로드 중인 게시글로 작성자 아가(阿河)님의 동의를 얻어 번역한 게시글 입니다.

번역 : 메가존클라우드 Cloud Techonolgy Center 박지은 매니저

--

--

MZC_Global
Cloud Villains

A blog post will be posted from the global branch of MegazoneCloud