GPUインスタンスを自動で停止してコストを削減する


機械学習、特にディープラーニングではGPUを搭載したマシンを使って学習を行うことが当たり前になっています。最近はクラウド上のGPUインスタンスが使われることが多くなっていますが、GPUインスタンスは高額なので、学習していないときに動かしっぱなしだとコストの無駄です。そのような場合、自動で停止させるとコストを削減できます。

本記事ではAWSを対象に、アイドリング状態の高額なインスタンスを自動で停止する方法を紹介します。概要としては、インスタンスの起動を検知したら使用状態を監視するアラームを自動で設定し、アラームがあがったらインスタンスを自動で停止します。この動作は Lambda と CloudWatch を使うことでサーバレスで実現できます。全体像は以下のようになります。

<img width="633" alt="スクリーンショット 2018-07-27 8.24.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/fdea10b7-5396-4c2f-7b30-194e075acd15.png">

以下の手順を踏んで実現しましょう。

* IAM ロールの作成
* Lambda 関数の作成
* CloudWatch Event の設定

# IAM ロールを作成する

まずは Lambda 関数から EC2 を操作できるロールを作成しましょう。

IAMのロールの作成ページへ移動し、「このロールを使用するサービスを選択」で Lambda を選択したら「次のステップ」を選択します。

<img width="970" alt="スクリーンショット 2018-07-27 6.56.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/5a9223ba-f55b-984c-dc7b-f51933f6f88d.png">

次にロールにアタッチするポリシーを選択します。今回はEC2インスタンスに対する操作を行いたいので「AmazonEC2FullAccess」をつけておきます。つけたら「次のステップ」を選択します。「最低限の権限だけ設定したい！」という場合は自分でポリシーを作成して付与しましょう。

<img width="629" alt="スクリーンショット 2018-07-27 6.59.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/87bc7cc8-7093-1938-dbbf-0bd62ab47360.png">

最後にロール名を入力したら「ロールの作成」を選択してロールを作成します。ロール名は「AWSLambdaEC2FullAccess」とでもしておきます。


# Lambda 関数を作成する

次に Lambda 関数を作成します。Lambda関数の中では以下の2つを行います。

* インスタンスIDの取得
* インスタンスタイプの判断
* アラームの作成と付与

Lambda のページに移動したら関数の設定画面で以下のように設定します。ロールは先程作成したものを付与します。

<img width="744" alt="スクリーンショット 2018-07-27 6.48.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/8484d4f2-ca42-d654-ca30-171ed7593357.png">

関数を作成したら中身のコードを書いていきます。boto3を使ってインスタンスに対してCloudWatchのアラームを付与します。以下のように書くことができます。

```python
import boto3


def put_cpu_alarm(instance_id):
    cloudWatch   = boto3.client('cloudwatch')
    cloudWatch.put_metric_alarm(
        AlarmName          = f'CPU_ALARM_{instance_id}',
        AlarmDescription   = 'Alarm when server CPU does not exceed 10%',
        AlarmActions       = ['arn:aws:automate:us-east-1:ec2:stop'],
        MetricName         = 'CPUUtilization',
        Namespace          = 'AWS/EC2' ,
        Statistic          = 'Average',
        Dimensions         = [{'Name': 'InstanceId', 'Value': instance_id}],
        Period             = 300,
        EvaluationPeriods  = 3,
        Threshold          = 10,
        ComparisonOperator = 'LessThanOrEqualToThreshold',
        TreatMissingData   = 'notBreaching'
    )


def lambda_handler(event, context):
    instance_id = event['detail']['instance-id']
    ec2 = boto3.resource('ec2')
    instance = ec2.Instance(instance_id)
    if instance.instance_type.endswith('xlarge'):
        put_cpu_alarm(instance_id)
```

`put_cpu_alarm`は、CloudWatchアラームを作成して指定したインスタンスに付与します。アラームの内容は、CPU使用率(`CPUUtilization`)を監視して、しきい値以下の使用率(10%以下)になったらインスタンスを停止するということが書いてあります。このあたりの設定はご自分の環境に合わせて変更しましょう。その際は、以下にあげた資料が役に立つと思います。

* [Amazon CloudWatch アラームの作成](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html)
* [Creating Alarms in Amazon CloudWatch](https://boto3.readthedocs.io/en/latest/guide/cw-example-creating-alarms.html)
* [Docs/Available Services/CloudWatch](https://boto3.readthedocs.io/en/latest/reference/services/cloudwatch.html)

`lambda_handler`はインスタンスの起動を検知したら呼び出されるエントリポイントです。内部では、起動したインスタンスタイプを取得し、タイプ名が`xlarge`で終わる場合に`put_cpu_alarm`関数を呼び出してアラームを付与しています。ここは条件の指定を変えることで特定のインスタンスタイプだけにアラームを付与することができます。

今回は監視対象としてCPU使用率を選択しましたが、カスタムメトリクスを追加することでGPU使用率を監視することもできます。設定方法は以下の資料に詳しいです。

* [Amazon CloudWatch で GPU 使用率をモニタリング](https://aws.amazon.com/jp/blogs/news/monitoring-gpu-utilization-with-amazon-cloudwatch/)

# CloudWatch Events を作成する

最後に、インスタンスの起動を検知したら Lambda 関数を呼び出す設定をしましょう。Lambda 関数のページから「トリガーの追加」で「CloudWatch Events」を選択しましょう。

<img width="523" alt="スクリーンショット 2018-07-27 7.19.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/9716cb65-df62-8124-2bc0-9ac729d21e60.png">

トリガーを追加したら、追加した CloudWatch Events を選択します。そうするとページ下部にトリガーの設定を行うためのフォームが現れます。「新規ルール」の作成を選択したら以下のように設定します。このように設定することで、インスタンスが起動したときにLambda関数を呼び出すことができます。

<img width="612" alt="スクリーンショット 2018-07-27 7.23.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/ab34db5d-381e-d326-9939-9cb57fde66cf.png">

以上で設定は完了です。

<!--
ただ、インスタンスIDは起動するたびに変わるので、このままだとアラームの数がどんどん増えていってしまいます。それを防ぐ仕組みが一つの課題です。
-->

# 参考資料

* [Amazon CloudWatch アラームの作成](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html)
* [Creating Alarms in Amazon CloudWatch](https://boto3.readthedocs.io/en/latest/guide/cw-example-creating-alarms.html)
* [Docs/Available Services/CloudWatch](https://boto3.readthedocs.io/en/latest/reference/services/cloudwatch.html)
* [Amazon CloudWatch で GPU 使用率をモニタリング](https://aws.amazon.com/jp/blogs/news/monitoring-gpu-utilization-with-amazon-cloudwatch/)