はじめに
ご覧いただきありがとうございます。
前回の記事で、EC2インスタンスの長時間稼働を検知して、自動通知を送る検証を行いました。
今回SageMakerノートブックインスタンスの長時間稼働を検知して、自動通知を送るようにします。
前回のようにRunCommandを使った検知はできなかったので、CloudWatchのログを利用した検知方法を検証します。
概要
(★)がついているセクションは、手を動かして頂く項目です。
- 今回の構成(★)
- SageMakerの環境構築(★)
- SNSトピック/サブスクリプションの準備(★)
- Lambda実装(連続稼働の検知)(★)
- EventBridgeで検知を自動化する(★)
- 挙動確認(★)
事前準備
- AWSアカウント作成
- AdministratorAccessを付与したIAMユーザーの作成
1.今回の構成
- Amazon SageMaker
- AWS Lambda
- Amazon SNS
- Amazon EventBridge
2.SageMakerの環境構築
S3バケットの作成
- 任意のバケット名("sagemaker"という文字列を含める)
- 任意のリージョン(今回は東京リージョンを選択)
SageMakerノートブックインスタンスの作成
ノートブックインスタンスを作成します。
今回自動検知の検証がメインなので、インスタンスタイプは最低限にします。
- 任意のノートブックインスタンス名
- インスタンスタイプ: ml.t2.medium
- IAMロール: 新しいロール
IAMロールで「新しいロール」を選ぶと、ロール作成画面が表示されます。
先程作成したS3バケットを指定して、ロールを作成します。
その他はデフォルトでノートブックインスタンスを作成します。
作成したノートブックインスタンスのステータスが「InService」になるのを待ちます。
なお今回作成したIAMロールは以下の通りです。
以下はExecutionPolicy
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:ListBucket"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::xxxxxxxxxxxxxxxx"
]
},
{
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::xxxxxxxxxxxxxxxx/*"
]
}
]
}
合わせてCloudWatchのページを確認します。
ロググループ ⇒ /aws/sagemaker/NotebookInstacesの配下を確認すると、「※ノートブックインタスタンス名/jupyter.log」という名前のログストリームがあります。
今回こちらのログを活用しますので、把握しておいてください。
3. SNSトピック/サブスクリプションの準備
Amazon SNSのページに移動します。
まずトピックを作成します。
- タイプ: スタンダード
- 名前: 任意の名前
次にサブスクリプションを作成します。
- プロトコル: Eメール
- エンドポイント: ※通知先Eメールアドレス
サブスクリプションを作成すると、登録したメールアドレスに確認メールが届きます。
メール文中の「Confirm subscription」をクリックします。
これで通知メールの設定は完了です。
次の工程でLambdaと連携させます。
4. Lambda実装(連続稼働の検知)
やりたいこと
今回やりたいことは「SageMakerノートブックインスタンスが、使っていないのにも関わらず一定時間以上稼働していたときに、SNS通知を行うこと」ことです。
EventBridgeでLambdaを定期実行します。
LambdaはCloudWatch⇒ロググループ(/aws/sagemaker/NotebookInstaces/)⇒ログストリーム(※ノートブックインタスタンス名/jupyter.log)のログを確認します。
ノートブックインスタンスを「InService」のまま放置した状態です。
特にこれ以降の更新はありません。
もしSageMakerノートブックインスタンスの状態が「InService」であるにも関わらず、ログに更新がなければ、ノートブックインスタンスを停止し忘れていると判断してSNS通知を行います。
具体的には現在時刻とログ最終更新時間の差分をとります。
タスク実行中で長時間稼働している可能性があるので、あくまでSNS通知のみで、自動停止の処理は行いません。
Lambda関数の作成
Lambdaのページに移行します。
- 任意の関数名
- ランタイム: Python3.8
- アーキテクチャ: x86_64
- 実行ロール: 既存のロールを使用
今回は「AmazonSageMakerFullAccess」「CloudWatchLogsFullAccess」「AmazonSNSFullAccess」をつけたIAMロールを作成して、付与しました。
関数を作成したら、一般設定で
- タイムアウト: 3秒 ⇒ 1分
に変更します。
コードを記述
Parameterの部分は、各自の環境に合わせてください。
index_timeの数値を変えることで、SNS通知を送る判断をする基準となる時間を変更できます。今回は検証作業のため、1時間に設定しています。
import boto3
import json
from datetime import datetime, timezone, timedelta
jst = timezone(timedelta(hours=9), 'JST')
# 指標時間
index_time = 1
# 連続稼働の上限(秒)
limit_time = 60 * 60 * index_time
# Parameter
input_topic_arn = "xxxxxxxxxxxxxxxx"
address = "xxxxxxxxxxxxxxxx"
target_region = "ap-northeast-1"
# Client
sns_client = boto3.client('sns', target_region)
sm_client = boto3.client('sagemaker', target_region)
cw_logs_client = boto3.client('logs', target_region)
def lambda_handler(event, context):
in_operation_longtime = []
for instance in sm_client.list_notebook_instances()['NotebookInstances']:
i_name = instance['NotebookInstanceName']
last_modified_time = instance['LastModifiedTime']
if instance['NotebookInstanceStatus'] != 'InService':
continue
try:
logs = cw_logs_client.get_log_events(
logGroupName="/aws/sagemaker/NotebookInstances",
logStreamName=f"{i_name}/jupyter.log",
limit=10,
startFromHead=False,
startTime=int(last_modified_time.timestamp()) * 1000,
endTime=int(datetime.now().timestamp()) * 1000,
)['events']
except:
continue
if len(logs) == 0:
timestamp = last_modified_time.timestamp()
else:
target_logs = logs[-1]
timestamp = int(str(target_logs['timestamp'])[:10])
diff = datetime.now() - datetime.fromtimestamp(timestamp)
if diff.seconds >= limit_time:
in_operation_longtime.append({
"instance_name": i_name,
"last_modified_time": (last_modified_time.astimezone(tz=jst)).strftime("%Y-%m-%d %H:%I:%S"),
"seconds": diff.seconds
})
if len(in_operation_longtime) >= 1:
topic_arn = input_topic_arn
title = f"Amazon SageMakerノートブックが{index_time}時間以上起動中です"
message = f"{index_time}時間以上起動中です"
sns_client.publish(
TopicArn=topic_arn,
Message=message,
Subject=title
)
return True
まず「in_operation_longtime」という空リストを作る。
list_notebook_instancesは、SageMaker notebookインスタンスの一覧を返します。
インスタンスの数だけ以下の処理を行います。
-
ノートブックインスタンスの情報の中で、「NotebookInstanceName」「LastModifiedTime(ノートブックインスタンスが最後に変更された時刻)」を取得します。
-
インスタンスが「InService」であるなら、以降の処理に進む。
-
get_log_eventsで、指定されたログストリームからのログイベントを一覧表示します。リファレンスに書いてあるパラメータに沿って、「ロググループ」「ログストリーム」「ログを取得する時間範囲」を指定します。
-
タイムスタンプを取得します。
-
現在時刻と最終更新時刻の差分を取得。
-
差分がlimit_timeより大きい場合は、「in_operation_longtime」に情報を加える。
-
「in_operation_longtime」に情報が入っていれば、メール通知を送ります。
上記のコードをLambdaに記述します。
Lambdaの実行します。
ノートブックインスタンスは1時間以上稼働しているため、メール通知が実行されました。
5.EventBridgeで検知を自動化する
Lambdaを定期実行します。
EventBridgeのページに移動してください。
「ルールを作成」をクリック。
- 任意の名前
- イベントバス: default
- ルールタイプ: スケジュール
次のページに進みます。
- スケジュールパターン: 通常レート
- レート式: 30分
ターゲットに、今回作成したLambda関数を選択します。
あとはデフォルトで設定します。
Lambdaのトリガーとして、EventBridgeが設定されました。
6. 挙動確認
EventBridgeを設定後に、メール通知が自動でくるかを確認します。
今回はもう1台SageMakerノートブックインスタンスをたてました。
検証①
現在のノートブックインスタンスの状態は以下の通りです。
- 1台目(InServiceのまま数時間放置)
- 2台目(InService 立ち上げてすぐの状態)
時間を追って、結果を確認していきます。
EventBridge設定後、すぐにメールが届きました。1台目が1時間以上起動しているからです。
メール確認後、1台目を停止して、再起動します。
検証②
- 1台目(InService 立ち上げてすぐの状態)
- 2台目(InService 立ち上げて30分経過)
メールは届きませんでした。
ここでコードを一部追加してみました。
if len(in_operation_longtime) >= 1:
topic_arn = input_topic_arn
title = f"Amazon SageMakerノートブックが{index_time}時間以上起動中です"
message = f"{index_time}時間以上起動中です"
# コード追加
for notification in in_operation_longtime:
message += f"インスタンス名: {notification['instance_name']}\n"
sns_client.publish(
TopicArn=topic_arn,
Message=message,
Subject=title
)
検知されたインスタンスの名前が分かるようにします。
検証③
- 1台目(InService 立ち上げて30分経過)
- 2台目(InService 立ち上げて1時間経過)
インスタンス名から2台目が起動しているのが分かります。
検証④
- 1台目(InService 立ち上げて1時間以上経過)
- 2台目(InService 立ち上げて1時間以上経過)
変わらずメールが届きます。
インスタンス名から、2台とも起動しているのが分かります。
メールの確認ができたら、2台とも停止します。
検証⑤
- 1台目(InService 停止)
- 2台目(InService 停止)
メールが届きませんでした。
狙い通りの挙動になっています。
さいごに
以上、LambdaからCloudWatchログを参照することでノートブックインスタンスの稼働のチェックが可能です。
御覧いただき ありがとうございました!