Amazon Web Services ブログ

高度な Amazon CloudWatch メトリクスと AWS Lambda を使用したアイドルチェックとリソースの自動終了による Amazon EMR コストの最適化

多くのお客様が、その開発環境で Apache Spark および Apache Hive のクエリなどのビッグデータワークロードを実行するために Amazon EMR を利用しておられます。データアナリストとデータサイエンティストは、分析 EMR クラスターとして知られるこれらのタイプのクラスターを頻繁に使用しますが、ユーザーは、作業終了後にクラスターを終了し忘れることがしばしばあります。これはクラスターのアイドル実行につながり、不必要なコストが生じることになります。

このオーバーヘッドを避けるため、EMR クラスターのアイドル状態を追跡し、アイドル状態で長時間にわたって実行される場合にクラスターを終了する必要があります。YARN ジョブが実行されているかどうかをチェックすることによってクラスターのアイドル状態を判断する Amazon EMR ネイティブの IsIdle Amazon CloudWatch メトリックがありますが、クラスターがアイドル状態かどうかを判断するためにも、接続されている SSH ユーザー、または実行中の Presto ジョブなどの追加のメトリクスを検討するべきです。また、Apache Zeppelin で Spark ジョブを実行する場合、ジョブの実行が終了した後でも、IsIdle メトリックが長時間アクティブ (1) 状態のままとなります。このような場合、IsIdle メトリックはクラスターの非アクティブ状態を判断するには適切ではありません。

このブログ記事では、このオーバーヘッドコストを削減するソリューションを提案します。このため、EMR クラスターのマスターノードにインストールされるバッシュスクリプトを実装しました。このスクリプトは 5 分ごとに実行されるようにスケジュールします。スクリプトはクラスターを監視し、5 分ごとにカスタムメトリック「EMR-INUSE」 (0 = 非アクティブ、1 = アクティブ) を CloudWatch に送信します。CloudWatch が事前定義されたデータポイントセットの一部に対して 0 (非アクティブ) を受け取ると、CloudWatch がアラームをトリガーし、アラームがクラスターを終了する AWS Lambda 関数を実行します。

前提条件

このフレームワークを作成してデプロイするには、以下が必要です。

注意: このソリューションは追加機能として設計されており、スケジューラスクリプト (この記事で後ほど説明します) を EMR ステップとして実行することによって、任意の既存 EMR クラスターに適用できます。このソリューションを将来のクラスターのための必須機能として実装したい場合は、この EMR ステップをクラスターデプロイメントの一環として含めることができます。このソリューションは、AWS CloudFormation、AWS CLI、および AWS マネジメントコンソール経由でスピンアップされる EMR クラスターに適用することもできます。

主要コンポーネント

以下はこのソリューションの主要コンポーネントです。

分析 EMR クラスター

Amazon EMR は、動的にスケーラブルな Amazon EC2 インスタンス全体の大量のデータを簡単に処理できるようにする、マネージド型 Apache Hadoop フレームワークを提供します。データサイエンティストは、データ分析、ノートブックアプリケーション (Apache Zeppelin または JupyterHub など) を使用する機械学習、および Apache Spark、Presto などに基づくビッグデータワークロードの実行に分析 EMR クラスターを使用します。

スケジューラスクリプト

schedule_script.sh は、Amazon EMR ステップとして実行されるシェルスクリプトです。実行されると、このスクリプトは Amazon S3 アーティファクトフォルダからモニタリングスクリプトをコピーし、このモニタリングスクリプトを 5 分ごとに実行されるようにスケジュールします。モニタリングスクリプトの S3 ロケーションは、引数として渡される必要があります。

モニタリングスクリプト

pushShutDownMetrin.sh スクリプトは、シェルコマンドを使用して実装されるモニタリングスクリプトです。これは、Amazon EMR ステップとして EMR クラスターのマスターノードにインストールする必要があります。このスクリプトは 5 分ごとに実行されるようにスケジュールされ、クラスターのアクティビティステータスを CloudWatch に送信します。 

JupyterHub API トークンスクリプト

jupyterhub_addAdminToken.sh スクリプトは、JupyterHub がクラスターで有効化されている場合に Amazon EMR ステップとして実行されるシェルスクリプトです。今回の設計では、モニタリングスクリプトが JupyterHub 提供の REST API を使用して、アプリケーションが使用中かどうかをチェックします。

リクエストを JupyterHub に送信するには、リクエストと共に API トークンを渡す必要があります。デフォルトで、アプリケーションは API トークンを生成しません。このスクリプトは API トークンを生成し、それを admin ユーザーに割り当てます。これは次に、アプリケーションのアクティビティを追跡するためにモニタリングスクリプト内の jupyterhub モジュールによってピックアップされます。

カスタム CloudWatch メトリック

すべての Amazon EMR クラスターは、いくつかのメトリクスに関するデータを CloudWatch に送信します。メトリクスは 5 分ごとに更新され、自動的に収集されて、CloudWatch にプッシュされます。このユースケースでは、EMR-INUSE という Amazon EMR メトリックを作成しました。このメトリックは、モニタリングスクリプトに実装されたモジュールチェックに基づくクラスターのアクティブステータスを表します。メトリックは、クラスターが非アクティブのときは 0、アクティブのときは 1 に設定されます。

Amazon CloudWatch

CloudWatch は、自動化されたアクションを実行するための高解像度アラームの設定に使用できるモニタリングサービスです。今回の場合は、CloudWatch が設定された時間数の間に連続して 0 を受け取ると、アラームをトリガーします。

AWS Lambda

Lambda は、サーバーをプロビジョニングまたは管理することなくコードを実行できるサーバーレステクノロジーです。Lambda では、実質上すべてのタイプのアプリケーションまたはバックエンドサービスに対してコードを実行でき、管理は一切不要です。コードは、他の AWS のサービスから自動的にトリガーされるようにセットアップできます。今回の場合は、先ほど説明したトリガーされた CloudWatch アラームが、クラスターを終了するよう Lambda に信号を送ります。

アーキテクチャ図

以下の図は、このソリューションが有効化されているときの一連のイベントを説明するもので、AWS CloudFormation 経由でスピンアップされた EMR クラスターに何が起こるかを示しています。

 

この図は以下のステップを表しています。

  1. EMR クラスターをスピンアップするために AWS CloudFormation スタックが起動される。
  2. Amazon EMR ステップが実行される (pushShutDownMetric.sh をインストールしてから、それを 5 分ごとに実行される cron ジョブとしてスケジュール)。
  3. EMR クラスターがアクティブ (ジョブを実行している) ならば、マスターノードが EMR-INUSE メトリックを 1 に設定し、それを CloudWatch に送信する。
  4. EMR クラスターが非アクティブならば、マスターノードが EMR-INUSE メトリックを 0 に設定し、それを CloudWatch に送信する。
  5. 事前設定されたデータポイント数に対して 0 を受け取ると、CloudWatch が CloudWatch アラームをトリガーする。
  6. CloudWatch アラームが、クラスターを終了するための通知を AWS Lambda に送信する。
  7. AWS Lambda が Lambda 関数を実行する。
  8. その後、Lambda 関数がクラスターに関連付けられたスタックリソースをすべて削除する。
  9. 最後に、EMR クラスターが終了され、AWS CloudFormation からスタック ID が削除される。

モニタリングスクリプト内のモジュール

以下は、モニタリングスクリプト (pushShutDownMetric.sh) に実装された異なるアクティビティチェックです。このスクリプトは、コア機能を変更することなく新しいモジュールを簡単に含めることができるように、モジュラー形式で設計されています。

ActiveSSHCheck

ActiveSSHCheck モジュールは、マスターノードへのアクティブな SSH 接続があるかどうかをチェックします。アクティブな SSH 接続があり、それが 10 分以上アイドル状態にならない場合、この機能は EMR-INUSE メトリックを 1 に設定し、それを CloudWatch にプッシュします。

YARNCheck

Apache Hadoop YARN は、EMR Hadoop エコシステムのリソースマネージャーです。すべての Spark Submits および Hive のクエリは、まず YARN に到達します。その後、YARN がこれらのジョブをスケジュールし、処理します。YARNCheck モジュールは、YARN に実行中のジョブがあるか、または過去 5 分の間に完了したジョブがあるかどうかをチェックします。そのようなジョブを見つけた場合、この機能は EMR-INUSE メトリックを 1 に設定し、それを CloudWatch にプッシュします。チェックは YARN によって公開された REST API を呼び出すことによって実行されます。

実行中のジョブを取得するための API は http://localhost:8088/ws/v1/cluster/apps?state=RUNNING です。

完了したジョブを取得するための API は

http://localhost:8088/ws/v1/cluster/apps?state=FINISHED です。

PRESTOCheck

Presto はインタラクティブな分析クエリを実行するための、オープンソースの分散クエリエンジンで、EMR のリリースバージョン 5.0.0 以降に包含されています。PRESTOCheck モジュールは、実行中の Presto クエリがあるかどうか、または過去 5 分の間に完了されたクエリがあるかどうかをチェックします。そのようなクエリがある場合、この機能は EMR-INUSE メトリックを 1 に設定し、それを CloudWatch にプッシュします。これらのチェックは、Presto サーバーによって公開された REST API を呼び出すことによって実行されます。

Presto ジョブを取得するための API は http://localhost:8889/v1/query です。

ZeppelinCheck

Amazon EMR ユーザーは、インタラクティブなデータ探索のためのノートブックとして Apache Zeppelin を使用します。ZeppelinCheck モジュールは、実行中のジョブがあるかどうか、または過去 5 分の間に完了されたジョブがあるかどうかをチェックします。そのようなジョブがある場合、この機能は EMR-INUSE メトリックを 1 に設定し、それを CloudWatch にプッシュします。これらのチェックは、Zeppelin によって公開された REST API を呼び出すことによって実行されます。

ノートブック ID のリストを取得するための API は http://localhost:8890/api/notebook です。

各ノートブック ID 内の各セルのステータスを取得するための API は http://localhost:8890/api/notebook/job/$notebookID です。

JupyterHubCheck

Jupyter Notebook は、ライブコード、方程式、視覚化、および説明テキストが含まれるドキュメントを作成して共有するために使用できる、オープンソースのウェブアプリケーションです。JupyterHub は、シングルユーザー Jupyter ノートブックサーバーの複数インスタンスをホストすることを可能します。JupyterHubCheck モジュールは、現在使用中の Jupyter ノートブックがあるかどうかをチェックします。

この機能は Jupyter ノートブックユーザーのリストを取得し、個々のノートブックサーバーに関するデータを収集するために、JupyterHub によって公開された REST API を使用します。応答からサーバーの最後のアクティビティ時間を抽出し、過去 5 分の間に使用されたサーバーがあるかどうかをチェックします。そのようなサーバーがある場合、この機能は EMR-INUSE メトリックを 1 に設定し、それを CloudWatch にプッシュします。jupyterhub_addAdminToken.sh スクリプトは、スケジューラスクリプトを有効化する前に EMR ステップとして実行される必要があります。

ノートブックユーザーのリストを取得するための API は https://localhost:9443/hub/api/users -H "Authorization: token $admin_token" です。

個々のサーバーの情報を取得するための API は https://localhost:9443/hub/api/users/$user -H "Authorization: token $admin_token です。

これらのチェックのいずれかが失敗すると、クラスターは非アクティブであると見なされ、モニタリングスクリプトが EMR-INUSE メトリックを 0 に設定して、それを CloudWatch にプッシュします。

注意:

スケジューラスクリプトは、5 分ごとに実行されるようにモニタリングスクリプト (pushShutDownMetric.sh) をスケジュールします。数分ごとに実行される内部 cron ジョブは、EMR-INUSE メトリックのキャリブレーションでは考慮されません。

各コンポーネントのデプロイメント

このセクションのステップに従って、提案された設計の各コンポーネントをデプロイします。

ステップ 1.Lambda 関数と SNS サブスクリプションを作成する

Lambda 関数と SNS サブスクリプションは、この設計のコアとなるコンポーネントです。これらのコンポーネントは最初にセットアップする必要があり、これらはすべてのクラスターで共通です。以下は、これらのコンポーネントのために作成する AWS のリソースです。

  • Lambda 関数のための実行ロール
  • アイドル EMR を終了する Lambda 関数
  • SNS トピックと Lambda サブスクリプション

ワンステップデプロイメントでは、この AWS CloudFormation テンプレートを使ってリソースを一度に起動し、設定します。

このテンプレートで利用できるパラメータは次のとおりです。

パラメータ デフォルト 説明
s3Bucket emr-shutdown-blogartifacts Lambda ファイルが含まれる S3 バケットの名前
s3Key EMRTerminate.zip Lambda ファイルの Amazon S3 キー

手動でのデプロイメントでは、AWS マネジメントコンソールでこれらのステップを行ってください。

Lambda 関数のための実行ロール

  1. AWS Identity and Access Management (IAM) コンソールを開き、[ポリシー]、[ポリシーの作成] と選択します。
  2. [JSON] タブを選択し、以下のポリシーテキストを張り付けて、[ポリシーの確認] を選択します。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:HeadBucket",
                "s3:ListObjects",
                "s3:GetObject",
                "cloudformation:ListStacks",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeStacks",
                "cloudformation:ListStackResources",
                "elasticmapreduce:TerminateJobFlows"
            ],
            "Resource": "*",
            "Effect": "Allow",
            "Sid": "GenericAccess"
        },
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*",
            "Effect": "Allow",
            "Sid": "LogAccess"
        }
    ]
}
  1. [名前] に TerminateEMRPolicy を入力し、[ポリシーの作成] を選択します。
  2. [ロール]、[ロールの作成] と選択します。
  3. [このロールを使用するサービスを選択] で [Lambda] を選択してから、[次のステップ: アクセス権限] を選択します。
  4. [Attach アクセス権限ポリシー] で [ポリシーのフィルタ] の横にある矢印を選択し、ドロップダウンリストで [ユーザーによる管理] を選択します。
  5. 先ほど作成したばかりの TerminateEMRPolicy ポリシーをアタッチし、[次のステップ: 確認] を選択します。
  6. [ロール名] に TerminateEMRLambdaRole を入力してから、[ロールの作成] を選択します。

アイドル EMR の終了 – Lambda 関数

今回は、この関数と共に使用するデプロイメントパッケージを作成しました。

  1. Lambda コンソールを開き、[関数の作成] を選択します。
  2. [一から作成] を選択して、以下のスクリーンショットにある通りに詳細情報を提供します。
  • 関数名: lambdaTerminateEMR
  • ランタイム: Python 2.7
  • 実行ロール: 既存のロールを使用する
  • 既存のロール: TerminateEMRLambdaRole

  1. [関数の作成] を選択します。
  2. [関数コード] セクションで、[コードエントリタイプ] に [Amazon S3 からのファイルのアップロード]、[ランタイム] に [Python 2.7] を選択します。

Lambda 関数の S3 リンクの URL は

s3://emr-shutdown-blogartifacts/EMRTerminate.zip です。

関数へのリンク: https://s3.amazonaws.com/emr-shutdown-blogartifacts/EMRTerminate.zip

この Lambda 関数は CloudWatch アラームによってトリガーされます。これは入力イベントを解析し、JobFlowId を取得して、対応する JobFlowId の AWS CloudFormation スタックを削除します。

SNS トピックと Lambda サブスクリプション

この後の段階における CloudWatch アラームの設定では、実行するように前述の Lambda 関数に通知する Amazon SNS トピックを作成する必要があります。これらのステップに従って SNS トピックを作成し、Lambda エンドポイントを設定してください。

  1. Amazon SNS コンソールに移動して、[トピックの作成] を選択します。
  2. [トピック名] と [表示名] を入力して [トピックの作成] を選択します。

  1. トピックが作成され、[トピック] に表示されます。
  2. このトピックを選択して、[アクション]、[トピックへのサブスクリプション] と選択します。

  1. [サブスクリプションの作成] で [AWS Lambda ] を選択します。エンドポイントとして lambdaterminateEMR を選択し、[サブスクリプションの作成] を選択します。

ステップ 2.JupyterHub API トークンスクリプトを EMR ステップとして実行する

このステップは、クラスターで JupyterHub が有効化されている場合にのみ必要です。

監視する EMR クラスターに移動して、スケジューラスクリプトを EMR ステップとして実行します。

コマンド: s3://emr-shutdown-blogartifacts/jupyterhub_addAdminToken.sh

このスクリプトは API トークンを作成し、それを admin ユーザーに割り当てます。これは次に、アプリケーションのアクティビティを追跡するためにモニタリングスクリプト内の jupyterhub モジュールによってピックアップされます。

ステップ 3.スケジューラスクリプトを EMR ステップとして実行する

監視する EMR クラスターに移動して、スケジューラスクリプトを EMR ステップとして実行します。

注意:

クラスターで終了保護が無効化されていることを確認してください。終了保護フラグは Lambda 関数が失敗する原因になります。

コマンド: s3://emr-shutdown-blogartifacts/schedule_script.sh

パラメータ: s3://emr-shutdown-blogartifacts/pushShutDownMetrin.sh

このステップ機能は pushShutDownMetric.sh スクリプトをマスターノードにコピーし、それが 5 分ごとに実行されるようにスケジュールします。

schedule_script.shhttps://s3.amazonaws.com/emr-shutdown-blogartifacts/schedule_script.sh にあります。

pushShutDownMetrin.shhttps://s3.amazonaws.com/emr-shutdown-blogartifacts/pushShutDownMetrin.sh にあります。

ステップ 4.CloudWatch アラームを作成する

シングルステップデプロイメントでは、この AWS CloudFormation テンプレートを使ってリソースを一度に起動し、設定します。

このテンプレートで利用できるパラメータは次のとおりです。

パラメータ デフォルト 説明
AlarmName TerminateIDLE-EMRAlarm アラームの名前。
EMRJobFlowID 入力必須 クラスターの Jobflowid
EvaluationPeriod 入力必須 アイドルタイムアウト値 ‐ 入力は、データポイントにする必要があります (1 データポイント = 5 分)。例えば、クラスターが 20 分間アイドル状態であった場合にそのクラスターを終了するには、入力を 4 にする必要があります。
SNSSubscribeTopic 入力必須 アラームでトリガーされる SNS トピックの Amazon リソースネーム (ARN)。

 

AWS CloudFormation CLI コマンドは以下の通りです。

aws cloudformation create-stack --stack-name EMRAlarmStack \
      --template-body s3://emr-shutdown-blogartifacts/Cloudformation/alarm.json \
      --parameters AlarmName=TerminateIDLE-EMRAlarm,EMRJobFlowID=<Input>,                 EvaluationPeriod=4,SNSSubscribeTopic=<Input>

手動デプロイメントでは、これらのステップに従ってアラームを作成します。

  1. Amazon CloudWatch コンソールを開き、[アラーム] を選択します。
  2. [アラームの作成] を選択します。
  3. [メトリクスの選択] ページの [カスタムメトリクス] で [EMRShutdown/Cluster-Metric] を選択します。

  1. EMR JobFlowId の [isEMRUsed ] メトリックを選択してから、[次へ] を選択します。

  1. 必要に応じてアラームを定義します。今回の場合は、CloudWatch が過去 2 時間のすべてのデータポイントに対して 0 の IsEMRUsed メトリックを受け取るときに、SNS トピック shutDownEMRTest に通知を送信するようアラームが設定されています。

  1. [アラームの作成] を選択します。

まとめ

この記事では、EMR クラスターのアイドル実行が原因で発生する可能性がある追加コストを削減するためのフレームワークの構築に注目しました。シェルスクリプトに実装されたモジュール、Spark スクリプトの実行ステータスの追跡、および軽量 REST API コールを使用した Hive/Presto クエリは、このアプローチを効率的なソリューションにしてくれます。

ご質問またはご提案については、以下でコメントを残してください。

 


著者について

Praveen Krishnamoorthy Ravikumar は、アマゾンウェブサービスのアソシエイトビッグデータコンサルタントです